mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Initial version of channels/recommendations.
This commit is contained in:
parent
2ab8bb13c5
commit
b2d8e2a1e6
11 changed files with 807 additions and 72 deletions
BIN
Telegram/Resources/animations/noresults.tgs
Normal file
BIN
Telegram/Resources/animations/noresults.tgs
Normal file
Binary file not shown.
|
@ -5123,6 +5123,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_recent_hide_sure" = "Are you sure you want to clear and disable frequent contacts list?\n\nYou can always turn this feature back on in Settings > Privacy > Suggest Frequent Contacts.";
|
"lng_recent_hide_sure" = "Are you sure you want to clear and disable frequent contacts list?\n\nYou can always turn this feature back on in Settings > Privacy > Suggest Frequent Contacts.";
|
||||||
"lng_recent_hide_button" = "Hide";
|
"lng_recent_hide_button" = "Hide";
|
||||||
"lng_recent_none" = "Recent search results\nwill appear here.";
|
"lng_recent_none" = "Recent search results\nwill appear here.";
|
||||||
|
"lng_recent_chats" = "Chats";
|
||||||
|
"lng_recent_channels" = "Channels";
|
||||||
|
"lng_channels_none_title" = "No channels yet...";
|
||||||
|
"lng_channels_none_about" = "You are not currently subscribed to any channels.";
|
||||||
|
"lng_channels_your_title" = "Channels you joined";
|
||||||
|
"lng_channels_your_more" = "Show more";
|
||||||
|
"lng_channels_your_less" = "Show less";
|
||||||
|
"lng_channels_recommended" = "Recommended channels";
|
||||||
|
|
||||||
// Wnd specific
|
// Wnd specific
|
||||||
|
|
||||||
|
|
|
@ -25,5 +25,6 @@
|
||||||
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
<file alias="collectible_username.tgs">../../animations/collectible_username.tgs</file>
|
||||||
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
<file alias="collectible_phone.tgs">../../animations/collectible_phone.tgs</file>
|
||||||
<file alias="search.tgs">../../animations/search.tgs</file>
|
<file alias="search.tgs">../../animations/search.tgs</file>
|
||||||
|
<file alias="noresults.tgs">../../animations/noresults.tgs</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -212,7 +212,7 @@ void ApplyBotsList(
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||||
not_null<ChannelData*> channel,
|
not_null<Main::Session*> session,
|
||||||
const MTPmessages_Chats &chats) {
|
const MTPmessages_Chats &chats) {
|
||||||
auto result = ChatParticipants::Channels();
|
auto result = ChatParticipants::Channels();
|
||||||
std::vector<not_null<ChannelData*>>();
|
std::vector<not_null<ChannelData*>>();
|
||||||
|
@ -220,13 +220,13 @@ void ApplyBotsList(
|
||||||
const auto &list = data.vchats().v;
|
const auto &list = data.vchats().v;
|
||||||
result.list.reserve(list.size());
|
result.list.reserve(list.size());
|
||||||
for (const auto &chat : list) {
|
for (const auto &chat : list) {
|
||||||
const auto peer = channel->owner().processChat(chat);
|
const auto peer = session->data().processChat(chat);
|
||||||
if (const auto channel = peer->asChannel()) {
|
if (const auto channel = peer->asChannel()) {
|
||||||
result.list.push_back(channel);
|
result.list.push_back(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constexpr (MTPDmessages_chatsSlice::Is<decltype(data)>()) {
|
if constexpr (MTPDmessages_chatsSlice::Is<decltype(data)>()) {
|
||||||
if (channel->session().premiumPossible()) {
|
if (session->premiumPossible()) {
|
||||||
result.more = data.vcount().v - data.vchats().v.size();
|
result.more = data.vcount().v - data.vchats().v.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,6 +234,12 @@ void ApplyBotsList(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ChatParticipants::Channels ParseSimilar(
|
||||||
|
not_null<ChannelData*> channel,
|
||||||
|
const MTPmessages_Chats &chats) {
|
||||||
|
return ParseSimilar(&channel->session(), chats);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ChatParticipant::ChatParticipant(
|
ChatParticipant::ChatParticipant(
|
||||||
|
@ -351,7 +357,8 @@ QString ChatParticipant::rank() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatParticipants::ChatParticipants(not_null<ApiWrap*> api)
|
ChatParticipants::ChatParticipants(not_null<ApiWrap*> api)
|
||||||
: _api(&api->instance()) {
|
: _session(&api->session())
|
||||||
|
, _api(&api->instance()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatParticipants::requestForAdd(
|
void ChatParticipants::requestForAdd(
|
||||||
|
@ -769,4 +776,29 @@ auto ChatParticipants::similarLoaded() const
|
||||||
return _similarLoaded.events();
|
return _similarLoaded.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatParticipants::loadRecommendations() {
|
||||||
|
if (_recommendationsLoaded.current() || _recommendations.requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_recommendations.requestId = _api.request(
|
||||||
|
MTPchannels_GetChannelRecommendations(
|
||||||
|
MTP_flags(0),
|
||||||
|
MTP_inputChannelEmpty())
|
||||||
|
).done([=](const MTPmessages_Chats &result) {
|
||||||
|
_recommendations.requestId = 0;
|
||||||
|
auto parsed = ParseSimilar(_session, result);
|
||||||
|
_recommendations.channels = std::move(parsed);
|
||||||
|
_recommendations.channels.more = 0;
|
||||||
|
_recommendationsLoaded = true;
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatParticipants::Channels &ChatParticipants::recommendations() const {
|
||||||
|
return _recommendations.channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<> ChatParticipants::recommendationsLoaded() const {
|
||||||
|
return _recommendationsLoaded.changes() | rpl::to_empty;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
class ApiWrap;
|
class ApiWrap;
|
||||||
class ChannelData;
|
class ChannelData;
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class Show;
|
class Show;
|
||||||
} // namespace Ui
|
} // namespace Ui
|
||||||
|
@ -134,12 +138,18 @@ public:
|
||||||
[[nodiscard]] auto similarLoaded() const
|
[[nodiscard]] auto similarLoaded() const
|
||||||
-> rpl::producer<not_null<ChannelData*>>;
|
-> rpl::producer<not_null<ChannelData*>>;
|
||||||
|
|
||||||
|
void loadRecommendations();
|
||||||
|
[[nodiscard]] const Channels &recommendations() const;
|
||||||
|
[[nodiscard]] rpl::producer<> recommendationsLoaded() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct SimilarChannels {
|
struct SimilarChannels {
|
||||||
Channels channels;
|
Channels channels;
|
||||||
mtpRequestId requestId = 0;
|
mtpRequestId requestId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
using PeerRequests = base::flat_map<PeerData*, mtpRequestId>;
|
using PeerRequests = base::flat_map<PeerData*, mtpRequestId>;
|
||||||
|
@ -165,6 +175,9 @@ private:
|
||||||
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
base::flat_map<not_null<ChannelData*>, SimilarChannels> _similar;
|
||||||
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
rpl::event_stream<not_null<ChannelData*>> _similarLoaded;
|
||||||
|
|
||||||
|
SimilarChannels _recommendations;
|
||||||
|
rpl::variable<bool> _recommendationsLoaded = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
|
@ -624,6 +624,26 @@ recentPeersList: PeerList(defaultPeerList) {
|
||||||
}
|
}
|
||||||
recentPeersSpecialNamePosition: point(64px, 19px);
|
recentPeersSpecialNamePosition: point(64px, 19px);
|
||||||
|
|
||||||
|
dialogsSearchTabs: SettingsSlider(defaultSettingsSlider) {
|
||||||
|
height: 33px;
|
||||||
|
barTop: 30px;
|
||||||
|
barSkip: 0px;
|
||||||
|
barStroke: 6px;
|
||||||
|
barRadius: 2px;
|
||||||
|
barFg: transparent;
|
||||||
|
barSnapToLabel: true;
|
||||||
|
strictSkip: 34px;
|
||||||
|
labelTop: 7px;
|
||||||
|
labelStyle: semiboldTextStyle;
|
||||||
|
labelFg: windowSubTextFg;
|
||||||
|
labelFgActive: lightButtonFg;
|
||||||
|
rippleBottomSkip: 1px;
|
||||||
|
rippleBg: windowBgOver;
|
||||||
|
rippleBgActive: lightButtonBgOver;
|
||||||
|
ripple: defaultRippleAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
dialogsStoriesList: DialogsStoriesList {
|
dialogsStoriesList: DialogsStoriesList {
|
||||||
small: dialogsStories;
|
small: dialogsStories;
|
||||||
full: dialogsStoriesFull;
|
full: dialogsStoriesFull;
|
||||||
|
|
|
@ -1151,7 +1151,9 @@ void Widget::updateSuggestions(anim::type animated) {
|
||||||
|
|
||||||
rpl::merge(
|
rpl::merge(
|
||||||
_suggestions->topPeerChosen(),
|
_suggestions->topPeerChosen(),
|
||||||
_suggestions->recentPeerChosen()
|
_suggestions->recentPeerChosen(),
|
||||||
|
_suggestions->myChannelChosen(),
|
||||||
|
_suggestions->recommendationChosen()
|
||||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||||
chosenRow({
|
chosenRow({
|
||||||
.key = peer->owner().history(peer),
|
.key = peer->owner().history(peer),
|
||||||
|
|
|
@ -7,11 +7,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "dialogs/ui/dialogs_suggestions.h"
|
#include "dialogs/ui/dialogs_suggestions.h"
|
||||||
|
|
||||||
|
#include "api/api_chat_participants.h"
|
||||||
|
#include "apiwrap.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/peer_list_box.h"
|
#include "boxes/peer_list_box.h"
|
||||||
#include "data/components/recent_peers.h"
|
#include "data/components/recent_peers.h"
|
||||||
#include "data/components/top_peers.h"
|
#include "data/components/top_peers.h"
|
||||||
#include "data/data_changes.h"
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_chat.h"
|
||||||
|
#include "data/data_folder.h"
|
||||||
#include "data/data_peer_values.h"
|
#include "data/data_peer_values.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
@ -23,9 +28,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/boxes/confirm_box.h"
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/discrete_sliders.h"
|
||||||
#include "ui/widgets/elastic_scroll.h"
|
#include "ui/widgets/elastic_scroll.h"
|
||||||
#include "ui/widgets/labels.h"
|
#include "ui/widgets/labels.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/delayed_activation.h"
|
#include "ui/delayed_activation.h"
|
||||||
|
@ -43,6 +50,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
namespace Dialogs {
|
namespace Dialogs {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kCollapsedChannelsCount = 5;
|
||||||
|
constexpr auto kProbablyMaxChannels = 1000;
|
||||||
|
constexpr auto kProbablyMaxRecommendations = 100;
|
||||||
|
|
||||||
class RecentRow final : public PeerListRow {
|
class RecentRow final : public PeerListRow {
|
||||||
public:
|
public:
|
||||||
explicit RecentRow(not_null<PeerData*> peer);
|
explicit RecentRow(not_null<PeerData*> peer);
|
||||||
|
@ -110,6 +121,76 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MyChannelsController final
|
||||||
|
: public PeerListController
|
||||||
|
, public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
explicit MyChannelsController(
|
||||||
|
not_null<Window::SessionController*> window);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<int> count() const {
|
||||||
|
return _count.value();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const {
|
||||||
|
return _chosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepare() override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) override;
|
||||||
|
Main::Session &session() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupDivider();
|
||||||
|
void appendRow(not_null<ChannelData*> channel);
|
||||||
|
void fill();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _window;
|
||||||
|
std::vector<not_null<History*>> _channels;
|
||||||
|
rpl::variable<Ui::RpWidget*> _toggleExpanded = nullptr;
|
||||||
|
rpl::variable<int> _count = 0;
|
||||||
|
rpl::variable<bool> _expanded = false;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _chosen;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class RecommendationsController final
|
||||||
|
: public PeerListController
|
||||||
|
, public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
explicit RecommendationsController(
|
||||||
|
not_null<Window::SessionController*> window);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<int> count() const {
|
||||||
|
return _count.value();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<not_null<PeerData*>> chosen() const {
|
||||||
|
return _chosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepare() override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) override;
|
||||||
|
Main::Session &session() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupDivider();
|
||||||
|
void appendRow(not_null<ChannelData*> channel);
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _window;
|
||||||
|
rpl::variable<int> _count;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _chosen;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
struct EntryMenuDescriptor {
|
struct EntryMenuDescriptor {
|
||||||
not_null<Window::SessionController*> controller;
|
not_null<Window::SessionController*> controller;
|
||||||
not_null<PeerData*> peer;
|
not_null<PeerData*> peer;
|
||||||
|
@ -189,6 +270,20 @@ RecentRow::RecentRow(not_null<PeerData*> peer)
|
||||||
, _history(peer->owner().history(peer)) {
|
, _history(peer->owner().history(peer)) {
|
||||||
if (peer->isSelf() || peer->isRepliesChat()) {
|
if (peer->isSelf() || peer->isRepliesChat()) {
|
||||||
setCustomStatus(u" "_q);
|
setCustomStatus(u" "_q);
|
||||||
|
} else if (const auto chat = peer->asChat()) {
|
||||||
|
if (chat->count > 0) {
|
||||||
|
setCustomStatus(
|
||||||
|
tr::lng_chat_status_members(tr::now, lt_count, chat->count));
|
||||||
|
}
|
||||||
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
|
if (channel->membersCountKnown()) {
|
||||||
|
setCustomStatus((channel->isBroadcast()
|
||||||
|
? tr::lng_chat_status_subscribers
|
||||||
|
: tr::lng_chat_status_members)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
channel->membersCount()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
refreshBadge();
|
refreshBadge();
|
||||||
}
|
}
|
||||||
|
@ -426,6 +521,259 @@ void RecentsController::subscribeToEvents() {
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MyChannelsController::MyChannelsController(
|
||||||
|
not_null<Window::SessionController*> window)
|
||||||
|
: _window(window) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyChannelsController::prepare() {
|
||||||
|
setupDivider();
|
||||||
|
|
||||||
|
_channels.reserve(kProbablyMaxChannels);
|
||||||
|
const auto owner = &session().data();
|
||||||
|
const auto add = [&](not_null<Dialogs::MainList*> list) {
|
||||||
|
for (const auto &row : list->indexed()->all()) {
|
||||||
|
if (const auto history = row->history()) {
|
||||||
|
if (const auto channel = history->peer->asBroadcast()) {
|
||||||
|
_channels.push_back(history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
add(owner->chatsList());
|
||||||
|
if (const auto folder = owner->folderLoaded(Data::Folder::kId)) {
|
||||||
|
add(owner->chatsList(folder));
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges::sort(_channels, ranges::greater(), &History::chatListTimeId);
|
||||||
|
_count = int(_channels.size());
|
||||||
|
|
||||||
|
_expanded.value() | rpl::start_with_next([=] {
|
||||||
|
fill();
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
|
auto loading = owner->chatsListChanges(
|
||||||
|
) | rpl::take_while([=](Data::Folder *folder) {
|
||||||
|
return !owner->chatsListLoaded(folder);
|
||||||
|
});
|
||||||
|
rpl::merge(
|
||||||
|
std::move(loading),
|
||||||
|
owner->chatsListLoadedEvents()
|
||||||
|
) | rpl::start_with_next([=](Data::Folder *folder) {
|
||||||
|
const auto list = owner->chatsList(folder);
|
||||||
|
for (const auto &row : list->indexed()->all()) {
|
||||||
|
if (const auto history = row->history()) {
|
||||||
|
if (const auto channel = history->peer->asBroadcast()) {
|
||||||
|
if (ranges::contains(_channels, not_null(history))) {
|
||||||
|
_channels.push_back(history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto was = _count.current();
|
||||||
|
const auto now = int(_channels.size());
|
||||||
|
if (was != now) {
|
||||||
|
_count = now;
|
||||||
|
fill();
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyChannelsController::fill() {
|
||||||
|
const auto count = _count.current();
|
||||||
|
const auto limit = _expanded.current()
|
||||||
|
? count
|
||||||
|
: std::min(count, kCollapsedChannelsCount);
|
||||||
|
const auto already = delegate()->peerListFullRowsCount();
|
||||||
|
const auto delta = limit - already;
|
||||||
|
if (!delta) {
|
||||||
|
return;
|
||||||
|
} else if (delta > 0) {
|
||||||
|
for (auto i = already; i != limit; ++i) {
|
||||||
|
appendRow(_channels[i]->peer->asBroadcast());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto i = already; i != limit;) {
|
||||||
|
delegate()->peerListRemoveRow(delegate()->peerListRowAt(--i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyChannelsController::appendRow(not_null<ChannelData*> channel) {
|
||||||
|
auto row = std::make_unique<PeerListRow>(channel);
|
||||||
|
if (channel->membersCountKnown()) {
|
||||||
|
row->setCustomStatus((channel->isBroadcast()
|
||||||
|
? tr::lng_chat_status_subscribers
|
||||||
|
: tr::lng_chat_status_members)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
channel->membersCount()));
|
||||||
|
}
|
||||||
|
delegate()->peerListAppendRow(std::move(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyChannelsController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
_chosen.fire(row->peer());
|
||||||
|
}
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> MyChannelsController::rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &MyChannelsController::session() const {
|
||||||
|
return _window->session();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyChannelsController::setupDivider() {
|
||||||
|
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
(QWidget*)nullptr,
|
||||||
|
st::searchedBarHeight);
|
||||||
|
const auto raw = result.data();
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
tr::lng_channels_your_title(),
|
||||||
|
st::searchedBarLabel);
|
||||||
|
_count.value(
|
||||||
|
) | rpl::map(
|
||||||
|
rpl::mappers::_1 > kCollapsedChannelsCount
|
||||||
|
) | rpl::distinct_until_changed() | rpl::start_with_next([=](bool more) {
|
||||||
|
_expanded = false;
|
||||||
|
if (!more) {
|
||||||
|
const auto toggle = _toggleExpanded.current();
|
||||||
|
_toggleExpanded = nullptr;
|
||||||
|
delete toggle;
|
||||||
|
return;
|
||||||
|
} else if (_toggleExpanded.current()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto toggle = Ui::CreateChild<Ui::LinkButton>(
|
||||||
|
raw,
|
||||||
|
tr::lng_channels_your_more(tr::now),
|
||||||
|
st::searchedBarLink);
|
||||||
|
toggle->show();
|
||||||
|
toggle->setClickedCallback([=] {
|
||||||
|
const auto expand = !_expanded.current();
|
||||||
|
toggle->setText(expand
|
||||||
|
? tr::lng_channels_your_less(tr::now)
|
||||||
|
: tr::lng_channels_your_more(tr::now));
|
||||||
|
_expanded = expand;
|
||||||
|
});
|
||||||
|
rpl::combine(
|
||||||
|
raw->sizeValue(),
|
||||||
|
toggle->widthValue()
|
||||||
|
) | rpl::start_with_next([=](QSize size, int width) {
|
||||||
|
const auto x = st::searchedBarPosition.x();
|
||||||
|
const auto y = st::searchedBarPosition.y();
|
||||||
|
toggle->moveToRight(0, 0, size.width());
|
||||||
|
label->resizeToWidth(size.width() - x - width);
|
||||||
|
label->moveToLeft(x, y, size.width());
|
||||||
|
}, toggle->lifetime());
|
||||||
|
_toggleExpanded = toggle;
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
raw->sizeValue(),
|
||||||
|
_toggleExpanded.value()
|
||||||
|
) | rpl::filter(
|
||||||
|
rpl::mappers::_2 == nullptr
|
||||||
|
) | rpl::start_with_next([=](QSize size, const auto) {
|
||||||
|
const auto x = st::searchedBarPosition.x();
|
||||||
|
const auto y = st::searchedBarPosition.y();
|
||||||
|
label->resizeToWidth(size.width() - x * 2);
|
||||||
|
label->moveToLeft(x, y, size.width());
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
QPainter(raw).fillRect(clip, st::searchedBarBg);
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
delegate()->peerListSetAboveWidget(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
RecommendationsController::RecommendationsController(
|
||||||
|
not_null<Window::SessionController*> window)
|
||||||
|
: _window(window) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecommendationsController::prepare() {
|
||||||
|
setupDivider();
|
||||||
|
|
||||||
|
const auto participants = &session().api().chatParticipants();
|
||||||
|
const auto fill = [=] {
|
||||||
|
const auto &list = participants->recommendations().list;
|
||||||
|
if (list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto &peer : list) {
|
||||||
|
if (const auto channel = peer->asBroadcast()) {
|
||||||
|
appendRow(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
_count = delegate()->peerListFullRowsCount();
|
||||||
|
};
|
||||||
|
|
||||||
|
fill();
|
||||||
|
if (!_count.current()) {
|
||||||
|
participants->loadRecommendations();
|
||||||
|
participants->recommendationsLoaded(
|
||||||
|
) | rpl::take(1) | rpl::start_with_next(fill, _lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecommendationsController::appendRow(not_null<ChannelData*> channel) {
|
||||||
|
auto row = std::make_unique<PeerListRow>(channel);
|
||||||
|
if (channel->membersCountKnown()) {
|
||||||
|
row->setCustomStatus((channel->isBroadcast()
|
||||||
|
? tr::lng_chat_status_subscribers
|
||||||
|
: tr::lng_chat_status_members)(
|
||||||
|
tr::now,
|
||||||
|
lt_count,
|
||||||
|
channel->membersCount()));
|
||||||
|
}
|
||||||
|
delegate()->peerListAppendRow(std::move(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecommendationsController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
_chosen.fire(row->peer());
|
||||||
|
}
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> RecommendationsController::rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &RecommendationsController::session() const {
|
||||||
|
return _window->session();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecommendationsController::setupDivider() {
|
||||||
|
auto result = object_ptr<Ui::FixedHeightWidget>(
|
||||||
|
(QWidget*)nullptr,
|
||||||
|
st::searchedBarHeight);
|
||||||
|
const auto raw = result.data();
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
raw,
|
||||||
|
tr::lng_channels_recommended(),
|
||||||
|
st::searchedBarLabel);
|
||||||
|
raw->sizeValue(
|
||||||
|
) | rpl::start_with_next([=](QSize size) {
|
||||||
|
const auto x = st::searchedBarPosition.x();
|
||||||
|
const auto y = st::searchedBarPosition.y();
|
||||||
|
label->resizeToWidth(size.width() - x * 2);
|
||||||
|
label->moveToLeft(x, y, size.width());
|
||||||
|
}, raw->lifetime());
|
||||||
|
raw->paintRequest() | rpl::start_with_next([=](QRect clip) {
|
||||||
|
QPainter(raw).fillRect(clip, st::searchedBarBg);
|
||||||
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
delegate()->peerListSetAboveWidget(std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Suggestions::Suggestions(
|
Suggestions::Suggestions(
|
||||||
|
@ -434,16 +782,54 @@ Suggestions::Suggestions(
|
||||||
rpl::producer<TopPeersList> topPeers,
|
rpl::producer<TopPeersList> topPeers,
|
||||||
RecentPeersList recentPeers)
|
RecentPeersList recentPeers)
|
||||||
: RpWidget(parent)
|
: RpWidget(parent)
|
||||||
, _scroll(std::make_unique<Ui::ElasticScroll>(this))
|
, _controller(controller)
|
||||||
, _content(_scroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
, _tabs(std::make_unique<Ui::SettingsSlider>(this, st::dialogsSearchTabs))
|
||||||
, _topPeersWrap(_content->add(object_ptr<Ui::SlideWrap<TopPeersStrip>>(
|
, _chatsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||||
this,
|
, _chatsContent(
|
||||||
object_ptr<TopPeersStrip>(this, std::move(topPeers)))))
|
_chatsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||||
|
, _topPeersWrap(
|
||||||
|
_chatsContent->add(object_ptr<Ui::SlideWrap<TopPeersStrip>>(
|
||||||
|
this,
|
||||||
|
object_ptr<TopPeersStrip>(this, std::move(topPeers)))))
|
||||||
, _topPeers(_topPeersWrap->entity())
|
, _topPeers(_topPeersWrap->entity())
|
||||||
, _recentPeers(
|
, _recentPeers(_chatsContent->add(setupRecentPeers(std::move(recentPeers))))
|
||||||
_content->add(
|
, _emptyRecent(_chatsContent->add(setupEmptyRecent()))
|
||||||
setupRecentPeers(controller, std::move(recentPeers))))
|
, _channelsScroll(std::make_unique<Ui::ElasticScroll>(this))
|
||||||
, _emptyRecent(_content->add(setupEmptyRecent(controller))) {
|
, _channelsContent(
|
||||||
|
_channelsScroll->setOwnedWidget(object_ptr<Ui::VerticalLayout>(this)))
|
||||||
|
, _myChannels(_channelsContent->add(setupMyChannels()))
|
||||||
|
, _recommendations(_channelsContent->add(setupRecommendations()))
|
||||||
|
, _emptyChannels(_channelsContent->add(setupEmptyChannels())) {
|
||||||
|
|
||||||
|
setupTabs();
|
||||||
|
setupChats();
|
||||||
|
setupChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
Suggestions::~Suggestions() = default;
|
||||||
|
|
||||||
|
void Suggestions::setupTabs() {
|
||||||
|
const auto shadow = Ui::CreateChild<Ui::PlainShadow>(this);
|
||||||
|
shadow->lower();
|
||||||
|
|
||||||
|
_tabs->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
|
const auto line = st::lineWidth;
|
||||||
|
shadow->setGeometry(0, size.height() - line, width(), line);
|
||||||
|
}, shadow->lifetime());
|
||||||
|
|
||||||
|
shadow->showOn(_tabs->shownValue());
|
||||||
|
|
||||||
|
_tabs->setSections({
|
||||||
|
tr::lng_recent_chats(tr::now),
|
||||||
|
tr::lng_recent_channels(tr::now),
|
||||||
|
});
|
||||||
|
_tabs->sectionActivated(
|
||||||
|
) | rpl::start_with_next([=](int section) {
|
||||||
|
switchTab(section ? Tab::Channels : Tab::Chats);
|
||||||
|
}, _tabs->lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::setupChats() {
|
||||||
_recentCount.value() | rpl::start_with_next([=](int count) {
|
_recentCount.value() | rpl::start_with_next([=](int count) {
|
||||||
_recentPeers->toggle(count > 0, anim::type::instant);
|
_recentPeers->toggle(count > 0, anim::type::instant);
|
||||||
_emptyRecent->toggle(count == 0, anim::type::instant);
|
_emptyRecent->toggle(count == 0, anim::type::instant);
|
||||||
|
@ -455,13 +841,13 @@ Suggestions::Suggestions(
|
||||||
|
|
||||||
_topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) {
|
_topPeers->clicks() | rpl::start_with_next([=](uint64 peerIdRaw) {
|
||||||
const auto peerId = PeerId(peerIdRaw);
|
const auto peerId = PeerId(peerIdRaw);
|
||||||
_topPeerChosen.fire(controller->session().data().peer(peerId));
|
_topPeerChosen.fire(_controller->session().data().peer(peerId));
|
||||||
}, _topPeers->lifetime());
|
}, _topPeers->lifetime());
|
||||||
|
|
||||||
_topPeers->showMenuRequests(
|
_topPeers->showMenuRequests(
|
||||||
) | rpl::start_with_next([=](const ShowTopPeerMenuRequest &request) {
|
) | rpl::start_with_next([=](const ShowTopPeerMenuRequest &request) {
|
||||||
const auto weak = Ui::MakeWeak(this);
|
const auto weak = Ui::MakeWeak(this);
|
||||||
const auto owner = &controller->session().data();
|
const auto owner = &_controller->session().data();
|
||||||
const auto peer = owner->peer(PeerId(request.id));
|
const auto peer = owner->peer(PeerId(request.id));
|
||||||
const auto removeOne = [=] {
|
const auto removeOne = [=] {
|
||||||
peer->session().topPeers().remove(peer);
|
peer->session().topPeers().remove(peer);
|
||||||
|
@ -469,7 +855,7 @@ Suggestions::Suggestions(
|
||||||
_topPeers->removeLocally(peer->id.value);
|
_topPeers->removeLocally(peer->id.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const auto session = &controller->session();
|
const auto session = &_controller->session();
|
||||||
const auto removeAll = crl::guard(session, [=] {
|
const auto removeAll = crl::guard(session, [=] {
|
||||||
session->topPeers().toggleDisabled(true);
|
session->topPeers().toggleDisabled(true);
|
||||||
if (weak) {
|
if (weak) {
|
||||||
|
@ -477,7 +863,7 @@ Suggestions::Suggestions(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
FillEntryMenu(request.callback, {
|
FillEntryMenu(request.callback, {
|
||||||
.controller = controller,
|
.controller = _controller,
|
||||||
.peer = peer,
|
.peer = peer,
|
||||||
.removeOneText = tr::lng_recent_remove(tr::now),
|
.removeOneText = tr::lng_recent_remove(tr::now),
|
||||||
.removeOne = removeOne,
|
.removeOne = removeOne,
|
||||||
|
@ -487,9 +873,28 @@ Suggestions::Suggestions(
|
||||||
.removeAll = removeAll,
|
.removeAll = removeAll,
|
||||||
});
|
});
|
||||||
}, _topPeers->lifetime());
|
}, _topPeers->lifetime());
|
||||||
|
|
||||||
|
_chatsScroll->setVisible(_tab == Tab::Chats);
|
||||||
}
|
}
|
||||||
|
|
||||||
Suggestions::~Suggestions() = default;
|
void Suggestions::setupChannels() {
|
||||||
|
_myChannelsCount.value() | rpl::start_with_next([=](int count) {
|
||||||
|
_myChannels->toggle(count > 0, anim::type::instant);
|
||||||
|
}, _myChannels->lifetime());
|
||||||
|
|
||||||
|
_recommendationsCount.value() | rpl::start_with_next([=](int count) {
|
||||||
|
_recommendations->toggle(count > 0, anim::type::instant);
|
||||||
|
}, _recommendations->lifetime());
|
||||||
|
|
||||||
|
_emptyChannels->toggleOn(
|
||||||
|
rpl::combine(
|
||||||
|
_myChannelsCount.value(),
|
||||||
|
_recommendationsCount.value(),
|
||||||
|
rpl::mappers::_1 + rpl::mappers::_2 == 0),
|
||||||
|
anim::type::instant);
|
||||||
|
|
||||||
|
_channelsScroll->setVisible(_tab == Tab::Channels);
|
||||||
|
}
|
||||||
|
|
||||||
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||||
const auto recentHasSelection = [=] {
|
const auto recentHasSelection = [=] {
|
||||||
|
@ -507,7 +912,7 @@ void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||||
}
|
}
|
||||||
if (_recentSelectJump(direction, pageSize) == JumpResult::AppliedAndOut) {
|
if (_recentSelectJump(direction, pageSize) == JumpResult::AppliedAndOut) {
|
||||||
if (direction == Qt::Key_Up) {
|
if (direction == Qt::Key_Up) {
|
||||||
_scroll->scrollTo(0);
|
_chatsScroll->scrollTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -515,7 +920,7 @@ void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||||
if (_recentSelectJump(direction, pageSize)
|
if (_recentSelectJump(direction, pageSize)
|
||||||
== JumpResult::AppliedAndOut) {
|
== JumpResult::AppliedAndOut) {
|
||||||
_topPeers->selectByKeyboard(Qt::Key());
|
_topPeers->selectByKeyboard(Qt::Key());
|
||||||
_scroll->scrollTo(0);
|
_chatsScroll->scrollTo(0);
|
||||||
} else {
|
} else {
|
||||||
_topPeers->deselectByKeyboard();
|
_topPeers->deselectByKeyboard();
|
||||||
}
|
}
|
||||||
|
@ -529,12 +934,12 @@ void Suggestions::selectJump(Qt::Key direction, int pageSize) {
|
||||||
_recentSelectJump(direction, pageSize);
|
_recentSelectJump(direction, pageSize);
|
||||||
} else {
|
} else {
|
||||||
_topPeers->selectByKeyboard(Qt::Key());
|
_topPeers->selectByKeyboard(Qt::Key());
|
||||||
_scroll->scrollTo(0);
|
_chatsScroll->scrollTo(0);
|
||||||
}
|
}
|
||||||
} else if (direction == Qt::Key_Left || direction == Qt::Key_Right) {
|
} else if (direction == Qt::Key_Left || direction == Qt::Key_Right) {
|
||||||
if (!recentHasSelection()) {
|
if (!recentHasSelection()) {
|
||||||
_topPeers->selectByKeyboard(direction);
|
_topPeers->selectByKeyboard(direction);
|
||||||
_scroll->scrollTo(0);
|
_chatsScroll->scrollTo(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -550,19 +955,9 @@ void Suggestions::show(anim::type animated, Fn<void()> finish) {
|
||||||
|
|
||||||
_hidden = false;
|
_hidden = false;
|
||||||
if (animated == anim::type::instant) {
|
if (animated == anim::type::instant) {
|
||||||
_shownAnimation.stop();
|
finishShow();
|
||||||
_scroll->show();
|
|
||||||
} else {
|
} else {
|
||||||
_shownAnimation.start([=] {
|
startShownAnimation(true, std::move(finish));
|
||||||
update();
|
|
||||||
if (!_shownAnimation.animating() && finish) {
|
|
||||||
finish();
|
|
||||||
_cache = QPixmap();
|
|
||||||
_scroll->show();
|
|
||||||
}
|
|
||||||
}, 0., 1., st::slideDuration, anim::easeOutQuint);
|
|
||||||
_cache = Ui::GrabWidget(_scroll.get());
|
|
||||||
_scroll->hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,17 +968,78 @@ void Suggestions::hide(anim::type animated, Fn<void()> finish) {
|
||||||
} else if (animated == anim::type::instant) {
|
} else if (animated == anim::type::instant) {
|
||||||
RpWidget::hide();
|
RpWidget::hide();
|
||||||
} else {
|
} else {
|
||||||
_shownAnimation.start([=] {
|
startShownAnimation(false, std::move(finish));
|
||||||
update();
|
|
||||||
if (!_shownAnimation.animating() && finish) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}, 1., 0., st::slideDuration, anim::easeOutQuint);
|
|
||||||
_cache = Ui::GrabWidget(_scroll.get());
|
|
||||||
_scroll->hide();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Suggestions::switchTab(Tab tab) {
|
||||||
|
if (_tab == tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tab = tab;
|
||||||
|
if (_tabs->isHidden()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startSlideAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::startSlideAnimation() {
|
||||||
|
if (!_slideAnimation.animating()) {
|
||||||
|
_slideLeft = Ui::GrabWidget(_chatsScroll.get());
|
||||||
|
_slideRight = Ui::GrabWidget(_channelsScroll.get());
|
||||||
|
_chatsScroll->hide();
|
||||||
|
_channelsScroll->hide();
|
||||||
|
}
|
||||||
|
const auto from = (_tab == Tab::Channels) ? 0. : 1.;
|
||||||
|
const auto to = (_tab == Tab::Channels) ? 1. : 0.;
|
||||||
|
_slideAnimation.start([=] {
|
||||||
|
update();
|
||||||
|
if (!_slideAnimation.animating() && !_shownAnimation.animating()) {
|
||||||
|
finishShow();
|
||||||
|
}
|
||||||
|
}, from, to, st::slideDuration, anim::sineInOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::startShownAnimation(bool shown, Fn<void()> finish) {
|
||||||
|
const auto from = shown ? 0. : 1.;
|
||||||
|
const auto to = shown ? 1. : 0.;
|
||||||
|
_shownAnimation.start([=] {
|
||||||
|
update();
|
||||||
|
if (!_shownAnimation.animating() && finish) {
|
||||||
|
finish();
|
||||||
|
if (shown) {
|
||||||
|
finishShow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, from, to, st::slideDuration, anim::easeOutQuint);
|
||||||
|
if (_cache.isNull()) {
|
||||||
|
const auto now = width();
|
||||||
|
if (now < st::columnMinimalWidthLeft) {
|
||||||
|
resize(st::columnMinimalWidthLeft, height());
|
||||||
|
}
|
||||||
|
_cache = Ui::GrabWidget(this);
|
||||||
|
if (now < st::columnMinimalWidthLeft) {
|
||||||
|
resize(now, height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_tabs->hide();
|
||||||
|
_chatsScroll->hide();
|
||||||
|
_channelsScroll->hide();
|
||||||
|
_slideAnimation.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Suggestions::finishShow() {
|
||||||
|
_slideAnimation.stop();
|
||||||
|
_slideLeft = _slideRight = QPixmap();
|
||||||
|
|
||||||
|
_shownAnimation.stop();
|
||||||
|
_cache = QPixmap();
|
||||||
|
|
||||||
|
_tabs->show();
|
||||||
|
_chatsScroll->setVisible(_tab == Tab::Chats);
|
||||||
|
_channelsScroll->setVisible(_tab == Tab::Channels);
|
||||||
|
}
|
||||||
|
|
||||||
float64 Suggestions::shownOpacity() const {
|
float64 Suggestions::shownOpacity() const {
|
||||||
return _shownAnimation.value(_hidden ? 0. : 1.);
|
return _shownAnimation.value(_hidden ? 0. : 1.);
|
||||||
}
|
}
|
||||||
|
@ -595,28 +1051,48 @@ void Suggestions::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
auto p = QPainter(this);
|
auto p = QPainter(this);
|
||||||
p.fillRect(e->rect(), color);
|
p.fillRect(e->rect(), color);
|
||||||
if (_scroll->isHidden()) {
|
if (!_cache.isNull()) {
|
||||||
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
||||||
p.setOpacity(opacity);
|
p.setOpacity(opacity);
|
||||||
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
|
p.drawPixmap(0, (opacity - 1.) * slide, _cache);
|
||||||
|
} else if (!_slideLeft.isNull()) {
|
||||||
|
const auto slide = st::topPeers.height + st::searchedBarHeight;
|
||||||
|
const auto right = (_tab == Tab::Channels);
|
||||||
|
const auto progress = _slideAnimation.value(right ? 1. : 0.);
|
||||||
|
const auto shift = st::topPeers.height + st::searchedBarHeight;
|
||||||
|
p.setOpacity(1. - progress);
|
||||||
|
p.drawPixmap(
|
||||||
|
anim::interpolate(0, -slide, progress),
|
||||||
|
_chatsScroll->y(),
|
||||||
|
_slideLeft);
|
||||||
|
p.setOpacity(progress);
|
||||||
|
p.drawPixmap(
|
||||||
|
anim::interpolate(slide, 0, progress),
|
||||||
|
_channelsScroll->y(),
|
||||||
|
_slideRight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Suggestions::resizeEvent(QResizeEvent *e) {
|
void Suggestions::resizeEvent(QResizeEvent *e) {
|
||||||
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
const auto w = std::max(width(), st::columnMinimalWidthLeft);
|
||||||
_scroll->setGeometry(0, 0, w, height());
|
_tabs->resizeToWidth(w);
|
||||||
_content->resizeToWidth(w);
|
const auto tabs = _tabs->height();
|
||||||
|
|
||||||
|
_chatsScroll->setGeometry(0, tabs, w, height() - tabs);
|
||||||
|
_chatsContent->resizeToWidth(w);
|
||||||
|
|
||||||
|
_channelsScroll->setGeometry(0, tabs, w, height() - tabs);
|
||||||
|
_channelsContent->resizeToWidth(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
||||||
not_null<Window::SessionController*> window,
|
|
||||||
RecentPeersList recentPeers) {
|
RecentPeersList recentPeers) {
|
||||||
auto &lifetime = _content->lifetime();
|
auto &lifetime = _chatsContent->lifetime();
|
||||||
const auto delegate = lifetime.make_state<
|
const auto delegate = lifetime.make_state<
|
||||||
PeerListContentDelegateSimple
|
PeerListContentDelegateSimple
|
||||||
>();
|
>();
|
||||||
const auto controller = lifetime.make_state<RecentsController>(
|
const auto controller = lifetime.make_state<RecentsController>(
|
||||||
window,
|
_controller,
|
||||||
std::move(recentPeers));
|
std::move(recentPeers));
|
||||||
controller->setStyleOverrides(&st::recentPeersList);
|
controller->setStyleOverrides(&st::recentPeersList);
|
||||||
|
|
||||||
|
@ -624,11 +1100,11 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
||||||
|
|
||||||
controller->chosen(
|
controller->chosen(
|
||||||
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||||
window->session().recentPeers().bump(peer);
|
_controller->session().recentPeers().bump(peer);
|
||||||
_recentPeerChosen.fire_copy(peer);
|
_recentPeerChosen.fire_copy(peer);
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
|
||||||
auto content = object_ptr<PeerListContent>(_content, controller);
|
auto content = object_ptr<PeerListContent>(_chatsContent, controller);
|
||||||
|
|
||||||
const auto raw = content.data();
|
const auto raw = content.data();
|
||||||
_recentPeersChoose = [=] {
|
_recentPeersChoose = [=] {
|
||||||
|
@ -658,7 +1134,7 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
||||||
raw->scrollToRequests(
|
raw->scrollToRequests(
|
||||||
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||||
const auto add = _topPeersWrap->toggled() ? _topPeers->height() : 0;
|
const auto add = _topPeersWrap->toggled() ? _topPeers->height() : 0;
|
||||||
_scroll->scrollToY(request.ymin + add, request.ymax + add);
|
_chatsScroll->scrollToY(request.ymin + add, request.ymax + add);
|
||||||
}, lifetime);
|
}, lifetime);
|
||||||
|
|
||||||
delegate->setContent(raw);
|
delegate->setContent(raw);
|
||||||
|
@ -667,26 +1143,39 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupRecentPeers(
|
||||||
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
|
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent(
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent() {
|
||||||
not_null<Window::SessionController*> window) {
|
return setupEmpty(_chatsContent, "search", tr::lng_recent_none());
|
||||||
auto content = object_ptr<Ui::RpWidget>(_content);
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyChannels() {
|
||||||
|
return setupEmpty(
|
||||||
|
_channelsContent,
|
||||||
|
"noresults",
|
||||||
|
tr::lng_channels_none_about());
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupEmpty(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
const QString &animation,
|
||||||
|
rpl::producer<QString> text) {
|
||||||
|
auto content = object_ptr<Ui::RpWidget>(parent);
|
||||||
const auto raw = content.data();
|
const auto raw = content.data();
|
||||||
|
|
||||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
raw,
|
raw,
|
||||||
tr::lng_recent_none(),
|
std::move(text),
|
||||||
st::defaultPeerListAbout);
|
st::defaultPeerListAbout);
|
||||||
const auto size = st::recentPeersEmptySize;
|
const auto size = st::recentPeersEmptySize;
|
||||||
const auto [widget, animate] = Settings::CreateLottieIcon(
|
const auto [widget, animate] = Settings::CreateLottieIcon(
|
||||||
raw,
|
raw,
|
||||||
{
|
{
|
||||||
.name = u"search"_q,
|
.name = animation,
|
||||||
.sizeOverride = { size, size },
|
.sizeOverride = { size, size },
|
||||||
},
|
},
|
||||||
st::recentPeersEmptyMargin);
|
st::recentPeersEmptyMargin);
|
||||||
const auto icon = widget.data();
|
const auto icon = widget.data();
|
||||||
|
|
||||||
_scroll->heightValue() | rpl::start_with_next([=](int height) {
|
_chatsScroll->heightValue() | rpl::start_with_next([=](int height) {
|
||||||
raw->resize(raw->width(), height - st::topPeers.height);
|
raw->resize(raw->width(), height - st::topPeers.height);
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
|
|
||||||
|
@ -699,11 +1188,13 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent(
|
||||||
y + icon->height() + st::recentPeersEmptySkip);
|
y + icon->height() + st::recentPeersEmptySkip);
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
|
|
||||||
auto result = object_ptr<Ui::SlideWrap<>>(_content, std::move(content));
|
auto result = object_ptr<Ui::SlideWrap<>>(
|
||||||
|
parent,
|
||||||
|
std::move(content));
|
||||||
result->toggle(false, anim::type::instant);
|
result->toggle(false, anim::type::instant);
|
||||||
|
|
||||||
result->toggledValue() | rpl::filter([=](bool shown) {
|
result->toggledValue() | rpl::filter([=](bool shown) {
|
||||||
return shown && window->session().data().chatsListLoaded();
|
return shown && _controller->session().data().chatsListLoaded();
|
||||||
}) | rpl::start_with_next([=] {
|
}) | rpl::start_with_next([=] {
|
||||||
animate(anim::repeat::once);
|
animate(anim::repeat::once);
|
||||||
}, raw->lifetime());
|
}, raw->lifetime());
|
||||||
|
@ -711,6 +1202,115 @@ object_ptr<Ui::SlideWrap<>> Suggestions::setupEmptyRecent(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupMyChannels() {
|
||||||
|
auto &lifetime = _channelsContent->lifetime();
|
||||||
|
const auto delegate = lifetime.make_state<
|
||||||
|
PeerListContentDelegateSimple
|
||||||
|
>();
|
||||||
|
const auto controller = lifetime.make_state<MyChannelsController>(
|
||||||
|
_controller);
|
||||||
|
controller->setStyleOverrides(&st::recentPeersList);
|
||||||
|
|
||||||
|
_myChannelsCount = controller->count();
|
||||||
|
|
||||||
|
controller->chosen(
|
||||||
|
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||||
|
_myChannelChosen.fire_copy(peer);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
auto content = object_ptr<PeerListContent>(_channelsContent, controller);
|
||||||
|
|
||||||
|
const auto raw = content.data();
|
||||||
|
_myChannelsChoose = [=] {
|
||||||
|
return raw->submitted();
|
||||||
|
};
|
||||||
|
_myChannelsSelectJump = [raw](Qt::Key direction, int pageSize) {
|
||||||
|
const auto had = raw->hasSelection();
|
||||||
|
if (direction == Qt::Key()) {
|
||||||
|
return had ? JumpResult::Applied : JumpResult::NotApplied;
|
||||||
|
} else if (direction == Qt::Key_Up && !had) {
|
||||||
|
return JumpResult::NotApplied;
|
||||||
|
} else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
|
||||||
|
const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
|
||||||
|
if (pageSize > 0) {
|
||||||
|
raw->selectSkipPage(pageSize, delta);
|
||||||
|
} else {
|
||||||
|
raw->selectSkip(delta);
|
||||||
|
}
|
||||||
|
return raw->hasSelection()
|
||||||
|
? JumpResult::Applied
|
||||||
|
: had
|
||||||
|
? JumpResult::AppliedAndOut
|
||||||
|
: JumpResult::NotApplied;
|
||||||
|
}
|
||||||
|
return JumpResult::NotApplied;
|
||||||
|
};
|
||||||
|
raw->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||||
|
_channelsScroll->scrollToY(request.ymin, request.ymax);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
delegate->setContent(raw);
|
||||||
|
controller->setDelegate(delegate);
|
||||||
|
|
||||||
|
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
object_ptr<Ui::SlideWrap<>> Suggestions::setupRecommendations() {
|
||||||
|
auto &lifetime = _channelsContent->lifetime();
|
||||||
|
const auto delegate = lifetime.make_state<
|
||||||
|
PeerListContentDelegateSimple
|
||||||
|
>();
|
||||||
|
const auto controller = lifetime.make_state<RecommendationsController>(
|
||||||
|
_controller);
|
||||||
|
controller->setStyleOverrides(&st::recentPeersList);
|
||||||
|
|
||||||
|
_recommendationsCount = controller->count();
|
||||||
|
|
||||||
|
controller->chosen(
|
||||||
|
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
|
||||||
|
_recommendationChosen.fire_copy(peer);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
auto content = object_ptr<PeerListContent>(_channelsContent, controller);
|
||||||
|
|
||||||
|
const auto raw = content.data();
|
||||||
|
_recommendationsChoose = [=] {
|
||||||
|
return raw->submitted();
|
||||||
|
};
|
||||||
|
_recommendationsSelectJump = [raw](Qt::Key direction, int pageSize) {
|
||||||
|
const auto had = raw->hasSelection();
|
||||||
|
if (direction == Qt::Key()) {
|
||||||
|
return had ? JumpResult::Applied : JumpResult::NotApplied;
|
||||||
|
} else if (direction == Qt::Key_Up && !had) {
|
||||||
|
return JumpResult::NotApplied;
|
||||||
|
} else if (direction == Qt::Key_Down || direction == Qt::Key_Up) {
|
||||||
|
const auto delta = (direction == Qt::Key_Down) ? 1 : -1;
|
||||||
|
if (pageSize > 0) {
|
||||||
|
raw->selectSkipPage(pageSize, delta);
|
||||||
|
} else {
|
||||||
|
raw->selectSkip(delta);
|
||||||
|
}
|
||||||
|
return raw->hasSelection()
|
||||||
|
? JumpResult::Applied
|
||||||
|
: had
|
||||||
|
? JumpResult::AppliedAndOut
|
||||||
|
: JumpResult::NotApplied;
|
||||||
|
}
|
||||||
|
return JumpResult::NotApplied;
|
||||||
|
};
|
||||||
|
raw->scrollToRequests(
|
||||||
|
) | rpl::start_with_next([this](Ui::ScrollToRequest request) {
|
||||||
|
const auto add = _myChannels->toggled() ? _myChannels->height() : 0;
|
||||||
|
_channelsScroll->scrollToY(request.ymin + add, request.ymax + add);
|
||||||
|
}, lifetime);
|
||||||
|
|
||||||
|
delegate->setContent(raw);
|
||||||
|
controller->setDelegate(delegate);
|
||||||
|
|
||||||
|
return object_ptr<Ui::SlideWrap<>>(this, std::move(content));
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<TopPeersList> TopPeersContent(
|
rpl::producer<TopPeersList> TopPeersContent(
|
||||||
not_null<Main::Session*> session) {
|
not_null<Main::Session*> session) {
|
||||||
return [=](auto consumer) {
|
return [=](auto consumer) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ class Session;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class ElasticScroll;
|
class ElasticScroll;
|
||||||
|
class SettingsSlider;
|
||||||
class VerticalLayout;
|
class VerticalLayout;
|
||||||
template <typename Widget>
|
template <typename Widget>
|
||||||
class SlideWrap;
|
class SlideWrap;
|
||||||
|
@ -52,12 +53,25 @@ public:
|
||||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> topPeerChosen() const {
|
[[nodiscard]] rpl::producer<not_null<PeerData*>> topPeerChosen() const {
|
||||||
return _topPeerChosen.events();
|
return _topPeerChosen.events();
|
||||||
}
|
}
|
||||||
[[nodiscard]] rpl::producer<not_null<PeerData*>> recentPeerChosen() const {
|
[[nodiscard]] auto recentPeerChosen() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
return _recentPeerChosen.events();
|
return _recentPeerChosen.events();
|
||||||
}
|
}
|
||||||
|
[[nodiscard]] auto myChannelChosen() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
|
return _myChannelChosen.events();
|
||||||
|
}
|
||||||
|
[[nodiscard]] auto recommendationChosen() const
|
||||||
|
-> rpl::producer<not_null<PeerData*>> {
|
||||||
|
return _recommendationChosen.events();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class JumpResult {
|
enum class Tab : uchar {
|
||||||
|
Chats,
|
||||||
|
Channels,
|
||||||
|
};
|
||||||
|
enum class JumpResult : uchar {
|
||||||
NotApplied,
|
NotApplied,
|
||||||
Applied,
|
Applied,
|
||||||
AppliedAndOut,
|
AppliedAndOut,
|
||||||
|
@ -66,14 +80,34 @@ private:
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
|
||||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers(
|
void setupTabs();
|
||||||
not_null<Window::SessionController*> window,
|
void setupChats();
|
||||||
RecentPeersList recentPeers);
|
void setupChannels();
|
||||||
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent(
|
|
||||||
not_null<Window::SessionController*> window);
|
|
||||||
|
|
||||||
const std::unique_ptr<Ui::ElasticScroll> _scroll;
|
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupRecentPeers(
|
||||||
const not_null<Ui::VerticalLayout*> _content;
|
RecentPeersList recentPeers);
|
||||||
|
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmptyRecent();
|
||||||
|
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupMyChannels();
|
||||||
|
[[nodiscard]] auto setupRecommendations()
|
||||||
|
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
|
||||||
|
[[nodiscard]] auto setupEmptyChannels()
|
||||||
|
-> object_ptr<Ui::SlideWrap<Ui::RpWidget>>;
|
||||||
|
[[nodiscard]] object_ptr<Ui::SlideWrap<Ui::RpWidget>> setupEmpty(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
const QString &animation,
|
||||||
|
rpl::producer<QString> text);
|
||||||
|
|
||||||
|
void switchTab(Tab tab);
|
||||||
|
void startShownAnimation(bool shown, Fn<void()> finish);
|
||||||
|
void startSlideAnimation();
|
||||||
|
void finishShow();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _controller;
|
||||||
|
|
||||||
|
const std::unique_ptr<Ui::SettingsSlider> _tabs;
|
||||||
|
|
||||||
|
const std::unique_ptr<Ui::ElasticScroll> _chatsScroll;
|
||||||
|
const not_null<Ui::VerticalLayout*> _chatsContent;
|
||||||
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
|
const not_null<Ui::SlideWrap<TopPeersStrip>*> _topPeersWrap;
|
||||||
const not_null<TopPeersStrip*> _topPeers;
|
const not_null<TopPeersStrip*> _topPeers;
|
||||||
|
|
||||||
|
@ -83,14 +117,36 @@ private:
|
||||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
|
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recentPeers;
|
||||||
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
|
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyRecent;
|
||||||
|
|
||||||
|
const std::unique_ptr<Ui::ElasticScroll> _channelsScroll;
|
||||||
|
const not_null<Ui::VerticalLayout*> _channelsContent;
|
||||||
|
|
||||||
|
rpl::variable<int> _myChannelsCount;
|
||||||
|
Fn<bool()> _myChannelsChoose;
|
||||||
|
Fn<JumpResult(Qt::Key, int)> _myChannelsSelectJump;
|
||||||
|
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _myChannels;
|
||||||
|
|
||||||
|
rpl::variable<int> _recommendationsCount;
|
||||||
|
Fn<bool()> _recommendationsChoose;
|
||||||
|
Fn<JumpResult(Qt::Key, int)> _recommendationsSelectJump;
|
||||||
|
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _recommendations;
|
||||||
|
|
||||||
|
const not_null<Ui::SlideWrap<Ui::RpWidget>*> _emptyChannels;
|
||||||
|
|
||||||
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
|
rpl::event_stream<not_null<PeerData*>> _topPeerChosen;
|
||||||
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen;
|
rpl::event_stream<not_null<PeerData*>> _recentPeerChosen;
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _myChannelChosen;
|
||||||
|
rpl::event_stream<not_null<PeerData*>> _recommendationChosen;
|
||||||
|
|
||||||
Ui::Animations::Simple _shownAnimation;
|
Ui::Animations::Simple _shownAnimation;
|
||||||
Fn<void()> _showFinished;
|
Fn<void()> _showFinished;
|
||||||
|
Tab _tab = Tab::Chats;
|
||||||
bool _hidden = false;
|
bool _hidden = false;
|
||||||
QPixmap _cache;
|
QPixmap _cache;
|
||||||
|
|
||||||
|
Ui::Animations::Simple _slideAnimation;
|
||||||
|
QPixmap _slideLeft;
|
||||||
|
QPixmap _slideRight;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
|
[[nodiscard]] rpl::producer<TopPeersList> TopPeersContent(
|
||||||
|
|
|
@ -467,6 +467,7 @@ void TopPeersStrip::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
const auto nameLeft = x + st.nameLeft;
|
const auto nameLeft = x + st.nameLeft;
|
||||||
const auto nameWidth = single - 2 * st.nameLeft;
|
const auto nameWidth = single - 2 * st.nameLeft;
|
||||||
|
p.setPen(st::dialogsNameFg);
|
||||||
entry.name.drawElided(
|
entry.name.drawElided(
|
||||||
p,
|
p,
|
||||||
nameLeft,
|
nameLeft,
|
||||||
|
|
|
@ -243,8 +243,10 @@ std::vector<float64> SettingsSlider::countSectionsWidths(
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
// If labelsWidth > sectionsWidth we're screwed anyway.
|
// If labelsWidth > sectionsWidth we're screwed anyway.
|
||||||
if (!commonWidth && labelsWidth <= sectionsWidth) {
|
if (_st.strictSkip || (!commonWidth && labelsWidth <= sectionsWidth)) {
|
||||||
auto padding = (sectionsWidth - labelsWidth) / (2. * count);
|
auto padding = _st.strictSkip
|
||||||
|
? (_st.strictSkip / 2.)
|
||||||
|
: (sectionsWidth - labelsWidth) / (2. * count);
|
||||||
auto currentWidth = result.begin();
|
auto currentWidth = result.begin();
|
||||||
enumerateSections([&](const Section §ion) {
|
enumerateSections([&](const Section §ion) {
|
||||||
Expects(currentWidth != result.end());
|
Expects(currentWidth != result.end());
|
||||||
|
@ -334,7 +336,7 @@ void SettingsSlider::paintEvent(QPaintEvent *e) {
|
||||||
+ (section.width - activeWidth) / 2;
|
+ (section.width - activeWidth) / 2;
|
||||||
auto active = 1.
|
auto active = 1.
|
||||||
- std::clamp(
|
- std::clamp(
|
||||||
qAbs(range.left - activeLeft) / float64(section.width),
|
qAbs(range.left - activeLeft) / float64(range.width),
|
||||||
0.,
|
0.,
|
||||||
1.);
|
1.);
|
||||||
if (section.ripple) {
|
if (section.ripple) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue