From d1b9b8e3a3c3d2bdfa70127ce71d0b41f7deca0f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 30 Jan 2017 18:27:13 +0300 Subject: [PATCH] Alpha 1.0.4: Click and drag to reorder pinned chats. --- Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/application.cpp | 4 +- Telegram/SourceFiles/boxes/sessionsbox.cpp | 2 +- Telegram/SourceFiles/boxes/stickers_box.cpp | 2 +- Telegram/SourceFiles/core/version.h | 4 +- .../dialogs/dialogs_indexed_list.cpp | 19 + .../dialogs/dialogs_indexed_list.h | 3 + Telegram/SourceFiles/dialogs/dialogs_list.cpp | 14 - Telegram/SourceFiles/dialogs/dialogs_list.h | 1 - Telegram/SourceFiles/dialogswidget.cpp | 370 +++++++++++++++++- Telegram/SourceFiles/dialogswidget.h | 46 ++- Telegram/SourceFiles/history.cpp | 18 +- Telegram/SourceFiles/history.h | 2 + Telegram/SourceFiles/mainwidget.cpp | 18 +- Telegram/SourceFiles/media/media_audio.cpp | 2 +- .../SourceFiles/ui/style/style_core_font.cpp | 2 +- Telegram/SourceFiles/ui/text/text.cpp | 12 +- Telegram/SourceFiles/ui/text/text_block.cpp | 2 - Telegram/build/version | 6 +- 20 files changed, 473 insertions(+), 70 deletions(-) diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index a27163ca7..20762902c 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,3,0 - PRODUCTVERSION 1,0,3,0 + FILEVERSION 1,0,4,0 + PRODUCTVERSION 1,0,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -51,10 +51,10 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "Telegram Messenger LLP" - VALUE "FileVersion", "1.0.3.0" + VALUE "FileVersion", "1.0.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.0.3.0" + VALUE "ProductVersion", "1.0.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 23022ba42..0bf29c3b9 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,3,0 - PRODUCTVERSION 1,0,3,0 + FILEVERSION 1,0,4,0 + PRODUCTVERSION 1,0,4,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Updater" - VALUE "FileVersion", "1.0.3.0" + VALUE "FileVersion", "1.0.4.0" VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.0.3.0" + VALUE "ProductVersion", "1.0.4.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index 9a3a90637..c2c5082da 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1069,8 +1069,8 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000003) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Audio device is opened only when some sound is played.\n\xe2\x80\x94 On Windows Vista and later audio device should switch after the system default changes."); + if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000004) { + versionFeatures = QString::fromUtf8("\xe2\x80\x94 Click and drag to reorder pinned chats."); } else if (!(cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000002) { versionFeatures = langNewVersionText(); } else { diff --git a/Telegram/SourceFiles/boxes/sessionsbox.cpp b/Telegram/SourceFiles/boxes/sessionsbox.cpp index 0403bc7b2..9dc9ef29e 100644 --- a/Telegram/SourceFiles/boxes/sessionsbox.cpp +++ b/Telegram/SourceFiles/boxes/sessionsbox.cpp @@ -330,7 +330,7 @@ void SessionsBox::Inner::onTerminateAll() { _terminateBox->closeBox(); _terminateBox = nullptr; } -// MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); + MTP::send(MTPauth_ResetAuthorizations(), rpcDone(&Inner::terminateAllDone), rpcFail(&Inner::terminateAllFail)); emit terminateAll(); })), KeepOtherLayers); } diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index d0f11dc20..d3256d31c 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -860,7 +860,7 @@ void StickersBox::Inner::onUpdateSelected() { } _rows[_dragging]->yadd = anim::value(local.y() - _dragStart.y(), local.y() - _dragStart.y()); _animStartTimes[_dragging] = 0; - _a_shifting.step(getms(), true); + _a_shifting.step(ms, true); auto countDraggingScrollDelta = [this, local] { if (local.y() < _visibleTop) { diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index ec6a4727e..3330da0ba 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 1000003; -constexpr str_const AppVersionStr = "1.0.3"; +constexpr int AppVersion = 1000004; +constexpr str_const AppVersionStr = "1.0.4"; constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp index b7aaace08..b6873b103 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.cpp @@ -81,6 +81,25 @@ void IndexedList::moveToTop(PeerData *peer) { } } +void IndexedList::movePinned(Row *row, int deltaSign) { + auto swapPinnedIndexWith = find(row); + t_assert(swapPinnedIndexWith != cend()); + if (deltaSign > 0) { + ++swapPinnedIndexWith; + } else { + t_assert(swapPinnedIndexWith != cbegin()); + --swapPinnedIndexWith; + } + auto history1 = row->history(); + auto history2 = (*swapPinnedIndexWith)->history(); + t_assert(history1->isPinnedDialog()); + t_assert(history2->isPinnedDialog()); + auto index1 = history1->getPinnedIndex(); + auto index2 = history2->getPinnedIndex(); + history1->setPinnedIndex(index2); + history2->setPinnedIndex(index1); +} + void IndexedList::peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars) { t_assert(_sortMode != SortMode::Date); if (_sortMode == SortMode::Name) { diff --git a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h index 27bf258d1..f4d95f24c 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_indexed_list.h @@ -36,6 +36,9 @@ public: void adjustByPos(const RowsByLetter &links); void moveToTop(PeerData *peer); + // row must belong to this indexed list all(). + void movePinned(Row *row, int deltaSign); + // For sortMode != SortMode::Date void peerNameChanged(PeerData *peer, const PeerData::Names &oldNames, const PeerData::NameFirstChars &oldChars); diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.cpp b/Telegram/SourceFiles/dialogs/dialogs_list.cpp index 22f7d444d..1e80c121b 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_list.cpp @@ -47,20 +47,6 @@ void List::adjustCurrent(int32 y, int32 h) const { } } -void List::paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground, TimeMs ms) const { - adjustCurrent(hFrom, st::dialogsRowHeight); - - Row *row = _current; - p.translate(0, row->_pos * st::dialogsRowHeight); - while (row != _end && row->_pos * st::dialogsRowHeight < hTo) { - bool active = (row->history()->peer == act) || (row->history()->peer->migrateTo() && row->history()->peer->migrateTo() == act); - bool selected = (row->history()->peer == sel); - Layout::RowPainter::paint(p, row, w, active, selected, onlyBackground, ms); - row = row->_next; - p.translate(0, st::dialogsRowHeight); - } -} - Row *List::addToEnd(History *history) { Row *result = new Row(history, _end->_prev, _end, _end->_pos); _end->_pos++; diff --git a/Telegram/SourceFiles/dialogs/dialogs_list.h b/Telegram/SourceFiles/dialogs/dialogs_list.h index abd7a6cd2..bd5e4adc9 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_list.h +++ b/Telegram/SourceFiles/dialogs/dialogs_list.h @@ -51,7 +51,6 @@ public: return *i; } - void paint(Painter &p, int32 w, int32 hFrom, int32 hTo, PeerData *act, PeerData *sel, bool onlyBackground, TimeMs ms) const; Row *addToEnd(History *history); Row *adjustByName(const PeerData *peer); Row *addByName(History *history); diff --git a/Telegram/SourceFiles/dialogswidget.cpp b/Telegram/SourceFiles/dialogswidget.cpp index b4a02dd6f..9ae4ceee6 100644 --- a/Telegram/SourceFiles/dialogswidget.cpp +++ b/Telegram/SourceFiles/dialogswidget.cpp @@ -44,10 +44,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/input_fields.h" #include "window/window_theme.h" #include "autoupdater.h" +#include "observer_peer.h" namespace { constexpr auto kHashtagResultsLimit = 5; +constexpr auto kStartReorderThreshold = 30; } // namespace @@ -73,6 +75,7 @@ DialogsInner::DialogsInner(QWidget *parent, QWidget *main) : SplittedWidget(pare , _dialogs(std_::make_unique(Dialogs::SortMode::Date)) , _contactsNoDialogs(std_::make_unique(Dialogs::SortMode::Name)) , _contacts(std_::make_unique(Dialogs::SortMode::Name)) +, _a_pinnedShifting(animation(this, &DialogsInner::step_pinnedShifting)) , _addContactLnk(this, lang(lng_add_contact_button)) , _cancelSearchInPeer(this, st::dialogsCancelSearchInPeer) { if (Global::DialogsModeEnabled()) { @@ -101,6 +104,10 @@ DialogsInner::DialogsInner(QWidget *parent, QWidget *main) : SplittedWidget(pare } }); + subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(Notify::PeerUpdate::Flag::PinnedChanged, [this](const Notify::PeerUpdate &update) { + stopReorderPinned(); + })); + refresh(); } @@ -135,18 +142,60 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO auto fullWidth = getFullWidth(); auto ms = getms(); if (_state == DefaultState) { - QRect dialogsClip = r; + auto rows = shownDialogs(); + auto dialogsClip = r; if (_dialogsImportant) { auto selected = isPressed() ? _importantSwitchPressed : _importantSwitchSelected; Dialogs::Layout::paintImportantSwitch(p, Global::DialogsMode(), fullWidth, selected, paintingOther); dialogsClip.translate(0, -st::dialogsImportantBarHeight); p.translate(0, st::dialogsImportantBarHeight); } - auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; + auto otherStart = rows->size() * st::dialogsRowHeight; auto active = App::main()->activePeer(); auto selected = _menuPeer ? _menuPeer : (isPressed() ? (_pressed ? _pressed->history()->peer : nullptr) : (_selected ? _selected->history()->peer : nullptr)); if (otherStart) { - shownDialogs()->all().paint(p, fullWidth, dialogsClip.top(), dialogsClip.top() + dialogsClip.height(), active, selected, paintingOther, ms); + auto reorderingPinned = (_aboveIndex >= 0 && !_pinnedRows.isEmpty()); + auto &list = rows->all(); + if (reorderingPinned) { + dialogsClip = dialogsClip.marginsAdded(QMargins(0, st::dialogsRowHeight, 0, st::dialogsRowHeight)); + } + + auto i = list.cfind(dialogsClip.top(), st::dialogsRowHeight); + if (i != list.cend()) { + auto lastPaintedPos = (*i)->pos(); + + // If we're reordering pinned chats we need to fill this area background first. + if (reorderingPinned) { + p.fillRect(0, 0, fullWidth, st::dialogsRowHeight * _pinnedRows.size(), st::dialogsBg); + } + + p.translate(0, lastPaintedPos * st::dialogsRowHeight); + for (auto e = list.cend(); i != e; ++i) { + auto row = (*i); + lastPaintedPos = row->pos(); + if (lastPaintedPos * st::dialogsRowHeight >= dialogsClip.top() + dialogsClip.height()) { + break; + } + + // Skip currently dragged chat to paint it above others after. + if (lastPaintedPos != _aboveIndex) { + paintDialog(p, row, fullWidth, active, selected, paintingOther, ms); + } + + p.translate(0, st::dialogsRowHeight); + } + + // Paint the dragged chat above all others. + if (_aboveIndex >= 0) { + auto i = list.cfind(_aboveIndex, 1); + auto pos = (i == list.cend()) ? -1 : (*i)->pos(); + if (pos == _aboveIndex) { + p.translate(0, (pos - lastPaintedPos) * st::dialogsRowHeight); + paintDialog(p, *i, fullWidth, active, selected, paintingOther, ms); + p.translate(0, (lastPaintedPos - pos) * st::dialogsRowHeight); + } + } + } } if (!otherStart) { p.fillRect(dialogsClip, st::dialogsBg); @@ -294,6 +343,19 @@ void DialogsInner::paintRegion(Painter &p, const QRegion ®ion, bool paintingO } } +void DialogsInner::paintDialog(Painter &p, Dialogs::Row *row, int fullWidth, PeerData *active, PeerData *selected, bool onlyBackground, TimeMs ms) { + auto pos = row->pos(); + auto xadd = 0, yadd = 0; + if (pos < _pinnedRows.size()) { + yadd = qRound(_pinnedRows[pos].yadd.current()); + } + if (xadd || yadd) p.translate(xadd, yadd); + auto isActive = (row->history()->peer == active) || (row->history()->peer->migrateTo() && row->history()->peer->migrateTo() == active); + auto isSelected = (row->history()->peer == selected); + Dialogs::Layout::RowPainter::paint(p, row, fullWidth, isActive, isSelected, onlyBackground, ms); + if (xadd || yadd) p.translate(-xadd, -yadd); +} + void DialogsInner::paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const { QRect fullRect(0, 0, fullWidth, st::dialogsRowHeight); p.fillRect(fullRect, active ? st::dialogsBgActive : (selected ? st::dialogsBgOver : st::dialogsBg)); @@ -399,7 +461,12 @@ void DialogsInner::clearIrrelevantState() { } void DialogsInner::updateSelected(QPoint localPos) { - if (!_mouseSelection) return; + if (updateReorderPinned(localPos)) { + return; + } + if (!_mouseSelection) { + return; + } int w = width(), mouseY = localPos.y(); clearIrrelevantState(); @@ -495,6 +562,7 @@ void DialogsInner::mousePressEvent(QMouseEvent *e) { row->addRipple(e->pos() - QPoint(0, dialogsOffset() + _pressed->pos() * st::dialogsRowHeight), QSize(getFullWidth(), st::dialogsRowHeight), [row] { row->history()->updateChatListEntry(); }); + _dragStart = e->pos(); } else if (_hashtagPressed >= 0 && _hashtagPressed < _hashtagResults.size() && !_hashtagDeletePressed) { auto row = &_hashtagResults[_hashtagPressed]->row; row->addRipple(e->pos(), QSize(getFullWidth(), st::mentionHeight), [this, index = _hashtagPressed] { @@ -523,11 +591,233 @@ void DialogsInner::mousePressEvent(QMouseEvent *e) { } } +void DialogsInner::checkReorderPinnedStart(QPoint localPosition) { + if (_pressed != nullptr && !_dragging && _state == DefaultState) { + if (qAbs(localPosition.y() - _dragStart.y()) >= convertScale(kStartReorderThreshold)) { + _dragging = _pressed; + if (updateReorderIndexGetCount() < 2) { + _dragging = nullptr; + } else { + _pinnedOrder = App::histories().getPinnedOrder(); + _pinnedRows[_draggingIndex].yadd = anim::value(0, localPosition.y() - _dragStart.y()); + _pinnedRows[_draggingIndex].animStartTime = getms(); + _a_pinnedShifting.start(); + } + } + } +} + +int DialogsInner::shownPinnedCount() const { + auto result = 0; + for_const (auto row, *shownDialogs()) { + if (!row->history()->isPinnedDialog()) { + break; + } + ++result; + } + return result; +} + +int DialogsInner::countPinnedIndex(Dialogs::Row *ofRow) { + if (!ofRow || !ofRow->history()->isPinnedDialog()) { + return -1; + } + auto result = 0; + for_const (auto row, *shownDialogs()) { + if (!row->history()->isPinnedDialog()) { + break; + } else if (row == ofRow) { + return result; + } + ++result; + } + return -1; +} + +void DialogsInner::savePinnedOrder() { + auto newOrder = App::histories().getPinnedOrder(); + if (newOrder.size() != _pinnedOrder.size()) { + return; // Something has changed in the set of pinned chats. + } + + auto peers = QVector(); + peers.reserve(newOrder.size()); + for_const (auto history, newOrder) { + if (_pinnedOrder.indexOf(history) < 0) { + return; // Something has changed in the set of pinned chats. + } + peers.push_back(history->peer->input); + } + auto flags = MTPmessages_ReorderPinnedDialogs::Flag::f_force; + MTP::send(MTPmessages_ReorderPinnedDialogs(MTP_flags(qFlags(flags)), MTP_vector(peers))); +} + +void DialogsInner::finishReorderPinned() { + auto wasDragging = (_dragging != nullptr); + if (wasDragging) { + savePinnedOrder(); + _dragging = nullptr; + } + _draggingIndex = -1; + if (!_a_pinnedShifting.animating()) { + _pinnedRows.clear(); + _aboveIndex = -1; + } + if (wasDragging) { + emit draggingScrollDelta(0); + } +} + +void DialogsInner::stopReorderPinned() { + _a_pinnedShifting.stop(); + finishReorderPinned(); +} + +int DialogsInner::updateReorderIndexGetCount() { + auto index = countPinnedIndex(_dragging); + if (index < 0) { + finishReorderPinned(); + return 0; + } + + auto count = shownPinnedCount(); + t_assert(index < count); + if (count < 2) { + stopReorderPinned(); + return 0; + } + + _draggingIndex = index; + _aboveIndex = _draggingIndex; + while (count > _pinnedRows.size()) { + _pinnedRows.push_back(PinnedRow()); + } + while (count < _pinnedRows.size()) { + _pinnedRows.pop_back(); + } + return count; +} + +bool DialogsInner::updateReorderPinned(QPoint localPosition) { + checkReorderPinnedStart(localPosition); + auto pinnedCount = updateReorderIndexGetCount(); + if (pinnedCount < 2) { + return false; + } + + auto yaddWas = _pinnedRows[_draggingIndex].yadd.current(); + auto shift = 0; + auto ms = getms(); + auto rowHeight = st::dialogsRowHeight; + if (_dragStart.y() > localPosition.y() && _draggingIndex > 0) { + shift = -floorclamp(_dragStart.y() - localPosition.y() + (rowHeight / 2), rowHeight, 0, _draggingIndex); + for (auto from = _draggingIndex, to = _draggingIndex + shift; from > to; --from) { + shownDialogs()->movePinned(_dragging, -1); + std_::swap_moveable(_pinnedRows[from], _pinnedRows[from - 1]); + _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() - rowHeight, 0); + _pinnedRows[from].animStartTime = ms; + } + } else if (_dragStart.y() < localPosition.y() && _draggingIndex + 1 < pinnedCount) { + shift = floorclamp(localPosition.y() - _dragStart.y() + (rowHeight / 2), rowHeight, 0, pinnedCount - _draggingIndex - 1); + for (auto from = _draggingIndex, to = _draggingIndex + shift; from < to; ++from) { + shownDialogs()->movePinned(_dragging, 1); + std_::swap_moveable(_pinnedRows[from], _pinnedRows[from + 1]); + _pinnedRows[from].yadd = anim::value(_pinnedRows[from].yadd.current() + rowHeight, 0); + _pinnedRows[from].animStartTime = ms; + } + } + if (shift) { + _draggingIndex += shift; + _aboveIndex = _draggingIndex; + _dragStart.setY(_dragStart.y() + shift * rowHeight); + if (!_a_pinnedShifting.animating()) { + _a_pinnedShifting.start(); + } + } + _aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current()); + _pinnedRows[_draggingIndex].yadd = anim::value(yaddWas - shift * rowHeight, localPosition.y() - _dragStart.y()); + if (!_pinnedRows[_draggingIndex].animStartTime) { + _pinnedRows[_draggingIndex].yadd.finish(); + } + _a_pinnedShifting.step(ms, true); + + auto countDraggingScrollDelta = [this, localPosition] { + if (localPosition.y() < _visibleTop) { + return localPosition.y() - _visibleTop; + } + return 0; + }; + + emit draggingScrollDelta(countDraggingScrollDelta()); + return true; +} + +void DialogsInner::step_pinnedShifting(TimeMs ms, bool timer) { + auto animating = false; + auto updateMin = -1; + auto updateMax = 0; + for (auto i = 0, l = _pinnedRows.size(); i != l; ++i) { + auto start = _pinnedRows[i].animStartTime; + if (start) { + if (updateMin < 0) updateMin = i; + updateMax = i; + if (start + st::stickersRowDuration > ms && ms >= start) { + _pinnedRows[i].yadd.update(float64(ms - start) / st::stickersRowDuration, anim::sineInOut); + animating = true; + } else { + _pinnedRows[i].yadd.finish(); + _pinnedRows[i].animStartTime = 0; + } + } + } + if (timer) { + updateReorderIndexGetCount(); + if (_draggingIndex >= 0) { + if (updateMin < 0 || updateMin > _draggingIndex) { + updateMin = _draggingIndex; + } + if (updateMax < _draggingIndex) updateMax = _draggingIndex; + } + if (updateMin >= 0) { + auto top = _dialogsImportant ? st::dialogsImportantBarHeight : 0; + auto updateFrom = top + st::dialogsRowHeight * (updateMin - 1); + auto updateHeight = st::dialogsRowHeight * (updateMax - updateMin + 3); + if (_aboveIndex >= 0 && _aboveIndex < _pinnedRows.size()) { + // Always include currently dragged chat in its current and old positions. + auto aboveRowBottom = top + (_aboveIndex + 1) * st::dialogsRowHeight; + auto aboveTopShift = qCeil(_pinnedRows[_aboveIndex].yadd.current()); + accumulate_max(updateHeight, (aboveRowBottom - updateFrom) + _aboveTopShift); + accumulate_max(updateHeight, (aboveRowBottom - updateFrom) + aboveTopShift); + _aboveTopShift = aboveTopShift; + } + update(0, updateFrom, getFullWidth(), updateHeight); + } + } + if (!animating) { + _aboveIndex = _draggingIndex; + _a_pinnedShifting.stop(); + } +} + void DialogsInner::mouseReleaseEvent(QMouseEvent *e) { mousePressReleased(e->button()); } void DialogsInner::mousePressReleased(Qt::MouseButton button) { + auto wasDragging = (_dragging != nullptr); + if (wasDragging) { + updateReorderIndexGetCount(); + if (_draggingIndex >= 0) { + auto localPosition = mapFromGlobal(QCursor::pos()); + _pinnedRows[_draggingIndex].yadd.start(0.); + _pinnedRows[_draggingIndex].animStartTime = getms(); + if (!_a_pinnedShifting.animating()) { + _a_pinnedShifting.start(); + } + } + finishReorderPinned(); + } + auto importantSwitchPressed = _importantSwitchPressed; setImportantSwitchPressed(false); auto pressed = _pressed; @@ -542,8 +832,11 @@ void DialogsInner::mousePressReleased(Qt::MouseButton button) { setPeerSearchPressed(-1); auto searchedPressed = _searchedPressed; setSearchedPressed(-1); + if (wasDragging) { + updateSelected(); + } updateSelectedRow(); - if (button == Qt::LeftButton) { + if (!wasDragging && button == Qt::LeftButton) { if (importantSwitchPressed && importantSwitchPressed == _importantSwitchSelected) { choosePeer(); } else if (pressed && pressed == _selected) { @@ -633,6 +926,13 @@ void DialogsInner::onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRo if (_pressed == oldRow) { setPressed(newRow); } + if (_dragging == oldRow) { + if (newRow) { + _dragging = newRow; + } else { + stopReorderPinned(); + } + } } void DialogsInner::createDialog(History *history) { @@ -711,7 +1011,12 @@ void DialogsInner::removeDialog(History *history) { void DialogsInner::dlgUpdated(Dialogs::Mode list, Dialogs::Row *row) { if (_state == DefaultState) { if (Global::DialogsMode() == list) { - update(0, dialogsOffset() + row->pos() * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + auto position = row->pos(); + auto top = dialogsOffset(); + if (position >= 0 && position < _pinnedRows.size()) { + top += qRound(_pinnedRows[position].yadd.current()); + } + update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } else if (_state == FilteredState || _state == SearchedState) { if (list == Dialogs::Mode::All) { @@ -736,7 +1041,12 @@ void DialogsInner::updateDialogRow(PeerData *peer, MsgId msgId, QRect updateRect if (_state == DefaultState) { if (sections & UpdateRowSection::Default) { if (auto row = shownDialogs()->getRow(peer->id)) { - updateRow(dialogsOffset() + row->pos() * st::dialogsRowHeight); + auto position = row->pos(); + auto top = dialogsOffset(); + if (position >= 0 && position < _pinnedRows.size()) { + top += qRound(_pinnedRows[position].yadd.current()); + } + updateRow(top + position * st::dialogsRowHeight); } } } else if (_state == FilteredState || _state == SearchedState) { @@ -782,9 +1092,14 @@ void DialogsInner::enterEvent(QEvent *e) { void DialogsInner::updateSelectedRow(PeerData *peer) { if (_state == DefaultState) { if (peer) { - if (History *h = App::historyLoaded(peer->id)) { + if (auto h = App::historyLoaded(peer->id)) { if (h->inChatList(Global::DialogsMode())) { - update(0, dialogsOffset() + h->posInChatList(Global::DialogsMode()) * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); + auto position = h->posInChatList(Global::DialogsMode()); + auto top = dialogsOffset(); + if (position >= 0 && position < _pinnedRows.size()) { + top += qRound(_pinnedRows[position].yadd.current()); + } + update(0, top + position * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } } else if (_selected) { @@ -810,7 +1125,6 @@ void DialogsInner::updateSelectedRow(PeerData *peer) { update(0, searchedOffset() + _searchedSelected * st::dialogsRowHeight, getFullWidth(), st::dialogsRowHeight); } } - } void DialogsInner::leaveEvent(QEvent *e) { @@ -1081,9 +1395,10 @@ PeerData *DialogsInner::updateFromParentDrag(QPoint globalPos) { } void DialogsInner::setVisibleTopBottom(int visibleTop, int visibleBottom) { - _visibleAreaHeight = visibleBottom - visibleTop; - loadPeerPhotos(visibleTop); - if (visibleTop + PreloadHeightsCount * (visibleBottom - visibleTop) >= height()) { + _visibleTop = visibleTop; + _visibleBottom = visibleBottom; + loadPeerPhotos(); + if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) >= height()) { if (_loadMoreCallback) { _loadMoreCallback(); } @@ -1347,8 +1662,9 @@ void DialogsInner::refresh(bool toTop) { } setHeight(h); if (toTop) { + stopReorderPinned(); emit mustScrollTo(0, 0); - loadPeerPhotos(0); + loadPeerPhotos(); } Global::RefDialogsListDisplayForced().set(_searchInPeer || !_filter.isEmpty(), true); update(); @@ -1563,11 +1879,11 @@ void DialogsInner::selectSkipPage(int32 pixels, int32 direction) { update(); } -void DialogsInner::loadPeerPhotos(int visibleTop) { +void DialogsInner::loadPeerPhotos() { if (!parentWidget()) return; - auto yFrom = visibleTop; - auto yTo = visibleTop + _visibleAreaHeight * (PreloadHeightsCount + 1); + auto yFrom = _visibleTop; + auto yTo = _visibleTop + (_visibleBottom - _visibleTop) * (PreloadHeightsCount + 1); MTP::clearLoaderPriorities(); if (_state == DefaultState) { auto otherStart = shownDialogs()->size() * st::dialogsRowHeight; @@ -1945,6 +2261,7 @@ DialogsWidget::DialogsWidget(QWidget *parent) : TWidget(parent) , _lockUnlock(this, st::dialogsLock) , _scroll(this, st::dialogsScroll) { _inner = _scroll->setOwnedWidget(object_ptr(this, parent)); + connect(_inner, SIGNAL(draggingScrollDelta(int)), this, SLOT(onDraggingScrollDelta(int))); connect(_inner, SIGNAL(mustScrollTo(int,int)), _scroll, SLOT(scrollToY(int,int))); connect(_inner, SIGNAL(dialogMoved(int,int)), this, SLOT(onDialogMoved(int,int))); connect(_inner, SIGNAL(searchMessages()), this, SLOT(onNeedSearchMessages())); @@ -2262,6 +2579,25 @@ bool DialogsWidget::dialogsFailed(const RPCError &error, mtpRequestId requestId) return true; } +void DialogsWidget::onDraggingScrollDelta(int delta) { + _draggingScrollDelta = _scroll ? delta : 0; + if (_draggingScrollDelta) { + if (!_draggingScrollTimer) { + _draggingScrollTimer.create(this); + _draggingScrollTimer->setSingleShot(false); + connect(_draggingScrollTimer, SIGNAL(timeout()), this, SLOT(onDraggingScrollTimer())); + } + _draggingScrollTimer->start(15); + } else { + _draggingScrollTimer.destroy(); + } +} + +void DialogsWidget::onDraggingScrollTimer() { + auto delta = (_draggingScrollDelta > 0) ? qMin(_draggingScrollDelta * 3 / 20 + 1, int32(MaxScrollSpeed)) : qMax(_draggingScrollDelta * 3 / 20 - 1, -int32(MaxScrollSpeed)); + _scroll->scrollToY(_scroll->scrollTop() + delta); +} + bool DialogsWidget::onSearchMessages(bool searchCache) { QString q = _filter->getLastText().trimmed(); if (q.isEmpty()) { diff --git a/Telegram/SourceFiles/dialogswidget.h b/Telegram/SourceFiles/dialogswidget.h index 6b24aedd5..5cc2d237e 100644 --- a/Telegram/SourceFiles/dialogswidget.h +++ b/Telegram/SourceFiles/dialogswidget.h @@ -131,6 +131,7 @@ public slots: void onMenuDestroyed(QObject*); signals: + void draggingScrollDelta(int delta); void mustScrollTo(int scrollToTop, int scrollToBottom); void dialogMoved(int movedFrom, int movedTo); void searchMessages(); @@ -165,7 +166,7 @@ private: updateSelected(mapFromGlobal(QCursor::pos())); } void updateSelected(QPoint localPos); - void loadPeerPhotos(int visibleTop); + void loadPeerPhotos(); void setImportantSwitchPressed(bool pressed); void setPressed(Dialogs::Row *pressed); void setHashtagPressed(int pressed); @@ -196,9 +197,9 @@ private: int peerSearchOffset() const; int searchedOffset() const; - void paintDialog(QPainter &p, Dialogs::Row *dialog); - void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int32 w, bool active, bool selected, bool onlyBackground, TimeMs ms) const; - void paintSearchInPeer(Painter &p, int32 w, bool onlyBackground) const; + void paintDialog(Painter &p, Dialogs::Row *row, int fullWidth, PeerData *active, PeerData *selected, bool onlyBackground, TimeMs ms); + void paintPeerSearchResult(Painter &p, const PeerSearchResult *result, int fullWidth, bool active, bool selected, bool onlyBackground, TimeMs ms) const; + void paintSearchInPeer(Painter &p, int fullWidth, bool onlyBackground) const; void clearSelection(); void clearSearchResults(bool clearPeerSearchResults = true); @@ -208,6 +209,16 @@ private: return (Global::DialogsMode() == Dialogs::Mode::Important) ? _dialogsImportant.get() : _dialogs.get(); } + void checkReorderPinnedStart(QPoint localPosition); + int shownPinnedCount() const; + int updateReorderIndexGetCount(); + bool updateReorderPinned(QPoint localPosition); + void finishReorderPinned(); + void stopReorderPinned(); + int countPinnedIndex(Dialogs::Row *ofRow); + void savePinnedOrder(); + void step_pinnedShifting(TimeMs ms, bool timer); + DialogsList _dialogs; DialogsList _dialogsImportant; @@ -223,7 +234,23 @@ private: Dialogs::Row *_selected = nullptr; Dialogs::Row *_pressed = nullptr; - int _visibleAreaHeight = 0; + Dialogs::Row *_dragging = nullptr; + int _draggingIndex = -1; + int _aboveIndex = -1; + QPoint _dragStart; + struct PinnedRow { + anim::value yadd; + TimeMs animStartTime = 0; + }; + std_::vector_of_moveable _pinnedRows; + BasicAnimation _a_pinnedShifting; + QList _pinnedOrder; + + // Remember the last currently dragged row top shift for updating area. + int _aboveTopShift = -1; + + int _visibleTop = 0; + int _visibleBottom = 0; QString _filter, _hashtagFilter; HashtagResults _hashtagResults; @@ -322,6 +349,8 @@ signals: void cancelled(); public slots: + void onDraggingScrollDelta(int delta); + void onCancel(); void onListScroll(); void activate(); @@ -338,8 +367,10 @@ public slots: void onChooseByDrag(); -#ifndef TDESKTOP_DISABLE_AUTOUPDATE private slots: + void onDraggingScrollTimer(); + +#ifndef TDESKTOP_DISABLE_AUTOUPDATE void onCheckUpdateStatus(); #endif // TDESKTOP_DISABLE_AUTOUPDATE @@ -427,4 +458,7 @@ private: QPixmap _widthAnimationCache; + object_ptr _draggingScrollTimer = { nullptr }; + int _draggingScrollDelta = 0; + }; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 660c55cb9..db1a457f4 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -754,6 +754,19 @@ int Histories::pinnedCount() const { return _pinnedDialogs.size(); } +QList Histories::getPinnedOrder() const { + QMap sorter; + for_const (auto pinned, _pinnedDialogs) { + sorter.insert(pinned->getPinnedIndex(), pinned); + } + QList result; + for (auto i = sorter.cend(), e = sorter.cbegin(); i != e;) { + --i; + result.push_back(i.value()); + } + return result; +} + HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, bool detachExistingItem) { auto msgId = MsgId(0); switch (msg.type()) { @@ -2129,7 +2142,10 @@ void History::updateChatListEntry() const { } void History::setPinnedDialog(bool isPinned) { - auto pinnedIndex = isPinned ? (++GlobalPinnedIndex) : 0; + setPinnedIndex(isPinned ? (++GlobalPinnedIndex) : 0); +} + +void History::setPinnedIndex(int pinnedIndex) { if (_pinnedIndex != pinnedIndex) { auto wasPinned = isPinnedDialog(); _pinnedIndex = pinnedIndex; diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 519a19bef..93d69e8ad 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -82,6 +82,7 @@ public: void setIsPinned(History *history, bool isPinned); void clearPinned(); int pinnedCount() const; + QList getPinnedOrder() const; struct SendActionAnimationUpdate { History *history; @@ -288,6 +289,7 @@ public: return (_pinnedIndex > 0); } void setPinnedDialog(bool isPinned); + void setPinnedIndex(int newPinnedIndex); int getPinnedIndex() const { return _pinnedIndex; } diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index c8ea787d7..a506810eb 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -656,12 +656,12 @@ void MainWidget::hiderLayer(object_ptr h) { } if (_dialogs->isHidden()) { _dialogs->show(); - resizeEvent(0); + updateControlsGeometry(); _dialogs->showAnimated(Window::SlideDirection::FromLeft, animationParams); } } else { _hider->show(); - resizeEvent(0); + updateControlsGeometry(); _dialogs->activate(); } } @@ -2293,7 +2293,7 @@ void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, Ui::Show } else { if (noPeer) { _topBar->hide(); - resizeEvent(0); + updateControlsGeometry(); } else if (wasActivePeer != activePeer()) { if (activePeer()->isChannel()) { activePeer()->asChannel()->ptsWaitingForShortPoll(WaitForChannelGetDifference); @@ -2421,6 +2421,8 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool if (!back) { saveSectionInStack(); } + + setFocus(); // otherwise dialogs widget could be focused. if (_overview) { _overview->hide(); _overview->clear(); @@ -2435,7 +2437,7 @@ void MainWidget::showMediaOverview(PeerData *peer, MediaOverviewType type, bool _overview.create(this, peer, type); _mediaTypeMask = 0; _topBar->show(); - resizeEvent(nullptr); + updateControlsGeometry(); // Send a fake update. Notify::PeerUpdate update(peer); @@ -2574,6 +2576,8 @@ void MainWidget::showNewWideSection(const Window::SectionMemento *memento, bool if (saveInStack) { saveSectionInStack(); } + + setFocus(); // otherwise dialogs widget could be focused. if (_overview) { _overview->hide(); _overview->clear(); @@ -2588,7 +2592,7 @@ void MainWidget::showNewWideSection(const Window::SectionMemento *memento, bool } _wideSection = std_::move(newWideSection); _topBar->hide(); - resizeEvent(0); + updateControlsGeometry(); _history->finishAnimation(); _history->showHistory(0, 0); _history->hide(); @@ -2924,7 +2928,7 @@ void MainWidget::showAll() { _player->show(); _playerHeight = _player->contentHeight(); } - resizeEvent(0); + updateControlsGeometry(); App::wnd()->checkHistoryActivation(); } @@ -3206,7 +3210,7 @@ void MainWidget::onHistoryShown(History *history, MsgId atMsgId) { } else { _topBar->hide(); } - resizeEvent(0); + updateControlsGeometry(); if (_a_show.animating()) { _topBar->hide(); } diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index 443f510a9..e095fc4d9 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -298,7 +298,7 @@ void InitAudio() { PrepareNotifySound(); auto loglevel = getenv("ALSOFT_LOGLEVEL"); - LOG(("OpenAL Logging Level: ").arg(loglevel ? loglevel : "(not set)")); + LOG(("OpenAL Logging Level: %1").arg(loglevel ? loglevel : "(not set)")); EnumeratePlaybackDevices(); EnumerateCaptureDevices(); diff --git a/Telegram/SourceFiles/ui/style/style_core_font.cpp b/Telegram/SourceFiles/ui/style/style_core_font.cpp index 443404609..479f7084c 100644 --- a/Telegram/SourceFiles/ui/style/style_core_font.cpp +++ b/Telegram/SourceFiles/ui/style/style_core_font.cpp @@ -76,7 +76,7 @@ FontData::FontData(int size, uint32 flags, int family, Font *other) : f(fontFami ascent = m.ascent(); descent = m.descent(); spacew = width(QLatin1Char(' ')); - elidew = width(QLatin1Char('.')) * 3; + elidew = width(qsl("...")); } Font FontData::bold(bool set) const { diff --git a/Telegram/SourceFiles/ui/text/text.cpp b/Telegram/SourceFiles/ui/text/text.cpp index 00df3b266..e8c5d3529 100644 --- a/Telegram/SourceFiles/ui/text/text.cpp +++ b/Telegram/SourceFiles/ui/text/text.cpp @@ -1555,7 +1555,7 @@ public: line.length = lineLength; eShapeLine(line); - int32 elideWidth = _f->width(_Elide); + auto elideWidth = _f->elidew; _wLeft = _w - elideWidth - _elideRemoveFromEnd; int firstItem = engine.findItem(line.from), lastItem = engine.findItem(line.from + line.length - 1); @@ -1654,15 +1654,21 @@ public: // COPIED FROM qtextengine.cpp AND MODIFIED void eShapeLine(const QScriptLine &line) { - int item = _e->findItem(line.from), end = _e->findItem(line.from + line.length - 1); + int item = _e->findItem(line.from); if (item == -1) return; +#ifdef OS_MAC_OLD + auto end = _e->findItem(line.from + line.length - 1); +#else // OS_MAC_OLD + auto end = _e->findItem(line.from + line.length - 1, item); +#endif // OS_MAC_OLD + int blockIndex = _lineStartBlock; ITextBlock *currentBlock = _t->_blocks[blockIndex]; ITextBlock *nextBlock = (++blockIndex < _blocksSize) ? _t->_blocks[blockIndex] : 0; eSetFont(currentBlock); - for (item = _e->findItem(line.from); item <= end; ++item) { + for (; item <= end; ++item) { QScriptItem &si = _e->layoutData->items[item]; while (nextBlock && nextBlock->from() <= _localFrom + si.position) { currentBlock = nextBlock; diff --git a/Telegram/SourceFiles/ui/text/text_block.cpp b/Telegram/SourceFiles/ui/text/text_block.cpp index b43bb05dd..d7597f850 100644 --- a/Telegram/SourceFiles/ui/text/text_block.cpp +++ b/Telegram/SourceFiles/ui/text/text_block.cpp @@ -335,8 +335,6 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi SignalHandlers::setCrashAnnotationRef("CrashString", &part); QStackTextEngine engine(part, blockFont->f); - engine.itemize(); - QTextLayout layout(&engine); layout.beginLayout(); layout.createLine(); diff --git a/Telegram/build/version b/Telegram/build/version index 47c451a74..6f8460cdc 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 1000003 +AppVersion 1000004 AppVersionStrMajor 1.0 -AppVersionStrSmall 1.0.3 -AppVersionStr 1.0.3 +AppVersionStrSmall 1.0.4 +AppVersionStr 1.0.4 AlphaChannel 1 BetaVersion 0