Allow selecting custom filter icons.
This commit is contained in:
		
							parent
							
								
									ce7621fbd9
								
							
						
					
					
						commit
						c4a0bc1fd5
					
				
					 11 changed files with 766 additions and 81 deletions
				
			
		|  | @ -924,6 +924,8 @@ PRIVATE | |||
|     ui/empty_userpic.h | ||||
|     ui/filter_icons.cpp | ||||
|     ui/filter_icons.h | ||||
|     ui/filter_icon_panel.cpp | ||||
|     ui/filter_icon_panel.h | ||||
|     ui/grouped_layout.cpp | ||||
|     ui/grouped_layout.h | ||||
|     ui/resize_area.h | ||||
|  |  | |||
|  | @ -2277,6 +2277,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | |||
| "lng_filters_type_no_archived" = "Archived"; | ||||
| "lng_filters_type_no_muted" = "Muted"; | ||||
| "lng_filters_type_no_read" = "Read"; | ||||
| "lng_filters_icon_header" = "Choose icon"; | ||||
| 
 | ||||
| // Wnd specific | ||||
| 
 | ||||
|  |  | |||
|  | @ -12,10 +12,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | |||
| #include "ui/text/text_utilities.h" | ||||
| #include "ui/widgets/buttons.h" | ||||
| #include "ui/widgets/input_fields.h" | ||||
| #include "ui/effects/panel_animation.h" | ||||
| #include "ui/filter_icons.h" | ||||
| #include "ui/filter_icon_panel.h" | ||||
| #include "data/data_chat_filters.h" | ||||
| #include "data/data_peer.h" | ||||
| #include "data/data_session.h" | ||||
| #include "settings/settings_common.h" | ||||
| #include "base/event_filter.h" | ||||
| #include "lang/lang_keys.h" | ||||
| #include "history/history.h" | ||||
| #include "main/main_session.h" | ||||
|  | @ -91,39 +95,42 @@ private: | |||
| 
 | ||||
| not_null<FilterChatsPreview*> SetupChatsPreview( | ||||
| 		not_null<Ui::VerticalLayout*> content, | ||||
| 		not_null<Data::ChatFilter*> data, | ||||
| 		not_null<rpl::variable<Data::ChatFilter>*> data, | ||||
| 		Flags flags, | ||||
| 		ExceptionPeersGetter peers) { | ||||
| 	const auto rules = data->current(); | ||||
| 	const auto preview = content->add(object_ptr<FilterChatsPreview>( | ||||
| 		content, | ||||
| 		data->flags() & flags, | ||||
| 		(data->*peers)())); | ||||
| 		rules.flags() & flags, | ||||
| 		(rules.*peers)())); | ||||
| 
 | ||||
| 	preview->flagRemoved( | ||||
| 	) | rpl::start_with_next([=](Flag flag) { | ||||
| 		const auto rules = data->current(); | ||||
| 		*data = Data::ChatFilter( | ||||
| 			data->id(), | ||||
| 			data->title(), | ||||
| 			data->iconEmoji(), | ||||
| 			(data->flags() & ~flag), | ||||
| 			data->always(), | ||||
| 			data->pinned(), | ||||
| 			data->never()); | ||||
| 			rules.id(), | ||||
| 			rules.title(), | ||||
| 			rules.iconEmoji(), | ||||
| 			(rules.flags() & ~flag), | ||||
| 			rules.always(), | ||||
| 			rules.pinned(), | ||||
| 			rules.never()); | ||||
| 	}, preview->lifetime()); | ||||
| 
 | ||||
| 	preview->peerRemoved( | ||||
| 	) | rpl::start_with_next([=](not_null<History*> history) { | ||||
| 		auto always = data->always(); | ||||
| 		auto pinned = data->pinned(); | ||||
| 		auto never = data->never(); | ||||
| 		const auto rules = data->current(); | ||||
| 		auto always = rules.always(); | ||||
| 		auto pinned = rules.pinned(); | ||||
| 		auto never = rules.never(); | ||||
| 		always.remove(history); | ||||
| 		pinned.erase(ranges::remove(pinned, history), end(pinned)); | ||||
| 		never.remove(history); | ||||
| 		*data = Data::ChatFilter( | ||||
| 			data->id(), | ||||
| 			data->title(), | ||||
| 			data->iconEmoji(), | ||||
| 			data->flags(), | ||||
| 			rules.id(), | ||||
| 			rules.title(), | ||||
| 			rules.iconEmoji(), | ||||
| 			rules.flags(), | ||||
| 			std::move(always), | ||||
| 			std::move(pinned), | ||||
| 			std::move(never)); | ||||
|  | @ -261,21 +268,23 @@ void EditExceptions( | |||
| 		not_null<Window::SessionController*> window, | ||||
| 		not_null<QObject*> context, | ||||
| 		Flags options, | ||||
| 		not_null<Data::ChatFilter*> data, | ||||
| 		not_null<rpl::variable<Data::ChatFilter>*> data, | ||||
| 		Fn<void()> refresh) { | ||||
| 	const auto include = (options & Flag::Contacts) != Flags(0); | ||||
| 	const auto rules = data->current(); | ||||
| 	auto controller = std::make_unique<EditFilterChatsListController>( | ||||
| 		window, | ||||
| 		(include | ||||
| 			? tr::lng_filters_include_title() | ||||
| 			: tr::lng_filters_exclude_title()), | ||||
| 		options, | ||||
| 		data->flags() & options, | ||||
| 		include ? data->always() : data->never()); | ||||
| 		rules.flags() & options, | ||||
| 		include ? rules.always() : rules.never()); | ||||
| 	const auto rawController = controller.get(); | ||||
| 	auto initBox = [=](not_null<PeerListBox*> box) { | ||||
| 		box->addButton(tr::lng_settings_save(), crl::guard(context, [=] { | ||||
| 			const auto peers = box->peerListCollectSelectedRows(); | ||||
| 			const auto rules = data->current(); | ||||
| 			auto &&histories = ranges::view::all( | ||||
| 				peers | ||||
| 			) | ranges::view::transform([=](not_null<PeerData*> peer) { | ||||
|  | @ -285,20 +294,22 @@ void EditExceptions( | |||
| 				histories.begin(), | ||||
| 				histories.end() | ||||
| 			}; | ||||
| 			auto removeFrom = include ? data->never() : data->always(); | ||||
| 			auto removeFrom = include ? rules.never() : rules.always(); | ||||
| 			for (const auto &history : changed) { | ||||
| 				removeFrom.remove(history); | ||||
| 			} | ||||
| 			auto pinned = data->pinned(); | ||||
| 			pinned.erase(ranges::remove_if(pinned, [&](not_null<History*> history) { | ||||
| 			auto pinned = rules.pinned(); | ||||
| 			pinned.erase(ranges::remove_if(pinned, [&]( | ||||
| 					not_null<History*> history) { | ||||
| 				const auto contains = changed.contains(history); | ||||
| 				return include ? !contains : contains; | ||||
| 			}), end(pinned)); | ||||
| 			*data = Data::ChatFilter( | ||||
| 				data->id(), | ||||
| 				data->title(), | ||||
| 				data->iconEmoji(), | ||||
| 				(data->flags() & ~options) | rawController->chosenOptions(), | ||||
| 				rules.id(), | ||||
| 				rules.title(), | ||||
| 				rules.iconEmoji(), | ||||
| 				((rules.flags() & ~options) | ||||
| 					| rawController->chosenOptions()), | ||||
| 				include ? std::move(changed) : std::move(removeFrom), | ||||
| 				std::move(pinned), | ||||
| 				include ? std::move(removeFrom) : std::move(changed)); | ||||
|  | @ -314,6 +325,94 @@ void EditExceptions( | |||
| 		Ui::LayerOption::KeepOther); | ||||
| } | ||||
| 
 | ||||
| [[nodiscard]] void CreateIconSelector( | ||||
| 		not_null<QWidget*> outer, | ||||
| 		not_null<QWidget*> box, | ||||
| 		not_null<QWidget*> parent, | ||||
| 		not_null<Ui::InputField*> input, | ||||
| 		not_null<rpl::variable<Data::ChatFilter>*> data) { | ||||
| 	const auto rules = data->current(); | ||||
| 	const auto toggle = Ui::CreateChild<Ui::AbstractButton>(parent.get()); | ||||
| 	toggle->resize(st::windowFilterIconToggleSize); | ||||
| 
 | ||||
| 	const auto type = toggle->lifetime().make_state<Ui::FilterIcon>(); | ||||
| 	data->value( | ||||
| 	) | rpl::map([=](const Data::ChatFilter &filter) { | ||||
| 		return Ui::ComputeFilterIcon(filter); | ||||
| 	}) | rpl::start_with_next([=](Ui::FilterIcon icon) { | ||||
| 		*type = icon; | ||||
| 		toggle->update(); | ||||
| 	}, toggle->lifetime()); | ||||
| 
 | ||||
| 	input->geometryValue( | ||||
| 	) | rpl::start_with_next([=](QRect geometry) { | ||||
| 		const auto left = geometry.x() + geometry.width() - toggle->width(); | ||||
| 		const auto position = st::windowFilterIconTogglePosition; | ||||
| 		toggle->move( | ||||
| 			left - position.x(), | ||||
| 			geometry.y() + position.y()); | ||||
| 	}, toggle->lifetime()); | ||||
| 
 | ||||
| 	toggle->paintRequest( | ||||
| 	) | rpl::start_with_next([=] { | ||||
| 		auto p = QPainter(toggle); | ||||
| 		const auto icons = Ui::LookupFilterIcon(*type); | ||||
| 		icons.normal->paintInCenter(p, toggle->rect(), st::emojiIconFg->c); | ||||
| 	}, toggle->lifetime()); | ||||
| 
 | ||||
| 	const auto panel = toggle->lifetime().make_state<Ui::FilterIconPanel>( | ||||
| 		outer); | ||||
| 	toggle->installEventFilter(panel); | ||||
| 	toggle->addClickHandler([=] { | ||||
| 		panel->toggleAnimated(); | ||||
| 	}); | ||||
| 	panel->chosen( | ||||
| 	) | rpl::filter([=](Ui::FilterIcon icon) { | ||||
| 		return icon != Ui::ComputeFilterIcon(data->current()); | ||||
| 	}) | rpl::start_with_next([=](Ui::FilterIcon icon) { | ||||
| 		panel->hideAnimated(); | ||||
| 		const auto rules = data->current(); | ||||
| 		*data = Data::ChatFilter( | ||||
| 			rules.id(), | ||||
| 			rules.title(), | ||||
| 			Ui::LookupFilterIcon(icon).emoji, | ||||
| 			rules.flags(), | ||||
| 			rules.always(), | ||||
| 			rules.pinned(), | ||||
| 			rules.never()); | ||||
| 	}, panel->lifetime()); | ||||
| 
 | ||||
| 	const auto updatePanelGeometry = [=] { | ||||
| 		const auto global = toggle->mapToGlobal({ | ||||
| 			toggle->width(), | ||||
| 			toggle->height() | ||||
| 		}); | ||||
| 		const auto local = outer->mapFromGlobal(global); | ||||
| 		const auto position = st::windwoFilterIconPanelPosition; | ||||
| 		const auto padding = panel->innerPadding(); | ||||
| 		panel->move( | ||||
| 			local.x() - panel->width() + position.x() + padding.right(), | ||||
| 			local.y() + position.y() - padding.top()); | ||||
| 	}; | ||||
| 
 | ||||
| 	const auto filterForGeometry = [=](not_null<QEvent*> event) { | ||||
| 		const auto type = event->type(); | ||||
| 		if (type == QEvent::Move || type == QEvent::Resize) { | ||||
| 			// updatePanelGeometry uses not only container geometry, but
 | ||||
| 			// also container children geometries that will be updated later.
 | ||||
| 			crl::on_main(panel, [=] { updatePanelGeometry(); }); | ||||
| 		} | ||||
| 		return base::EventFilterResult::Continue; | ||||
| 	}; | ||||
| 
 | ||||
| 	const auto installFilterForGeometry = [&](not_null<QWidget*> target) { | ||||
| 		panel->lifetime().make_state<base::unique_qptr<QObject>>( | ||||
| 			base::install_event_filter(target, filterForGeometry)); | ||||
| 	}; | ||||
| 	installFilterForGeometry(outer); | ||||
| 	installFilterForGeometry(box); | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| void EditFilterBox( | ||||
|  | @ -323,18 +422,28 @@ void EditFilterBox( | |||
| 		Fn<void(const Data::ChatFilter &)> doneCallback) { | ||||
| 	const auto creating = filter.title().isEmpty(); | ||||
| 	box->setTitle(creating ? tr::lng_filters_new() : tr::lng_filters_edit()); | ||||
| 	box->setCloseByOutsideClick(false); | ||||
| 
 | ||||
| 	using State = rpl::variable<Data::ChatFilter>; | ||||
| 	const auto data = box->lifetime().make_state<State>(filter); | ||||
| 
 | ||||
| 	const auto content = box->verticalLayout(); | ||||
| 	const auto name = content->add( | ||||
| 		object_ptr<Ui::InputField>( | ||||
| 			box, | ||||
| 			st::defaultInputField, | ||||
| 			st::windowFilterNameInput, | ||||
| 			tr::lng_filters_new_name(), | ||||
| 			filter.title()), | ||||
| 			data->current().title()), | ||||
| 		st::markdownLinkFieldPadding); | ||||
| 	name->setMaxLength(kMaxFilterTitleLength); | ||||
| 
 | ||||
| 	const auto data = box->lifetime().make_state<Data::ChatFilter>(filter); | ||||
| 	const auto outer = box->getDelegate()->outerContainer(); | ||||
| 	CreateIconSelector( | ||||
| 		outer, | ||||
| 		box, | ||||
| 		content, | ||||
| 		name, | ||||
| 		data); | ||||
| 
 | ||||
| 	constexpr auto kTypes = Flag::Contacts | ||||
| 		| Flag::NonContacts | ||||
|  | @ -391,8 +500,12 @@ void EditFilterBox( | |||
| 		st::settingsDividerLabelPadding); | ||||
| 
 | ||||
| 	const auto refreshPreviews = [=] { | ||||
| 		include->updateData(data->flags() & kTypes, data->always()); | ||||
| 		exclude->updateData(data->flags() & kExcludeTypes, data->never()); | ||||
| 		include->updateData( | ||||
| 			data->current().flags() & kTypes, | ||||
| 			data->current().always()); | ||||
| 		exclude->updateData( | ||||
| 			data->current().flags() & kExcludeTypes, | ||||
| 			data->current().never()); | ||||
| 	}; | ||||
| 	includeAdd->setClickedCallback([=] { | ||||
| 		EditExceptions(window, box, kTypes, data, refreshPreviews); | ||||
|  | @ -403,26 +516,27 @@ void EditFilterBox( | |||
| 
 | ||||
| 	const auto save = [=] { | ||||
| 		const auto title = name->getLastText().trimmed(); | ||||
| 		const auto rules = data->current(); | ||||
| 		const auto result = Data::ChatFilter( | ||||
| 			rules.id(), | ||||
| 			title, | ||||
| 			rules.iconEmoji(), | ||||
| 			rules.flags(), | ||||
| 			rules.always(), | ||||
| 			rules.pinned(), | ||||
| 			rules.never()); | ||||
| 		if (title.isEmpty()) { | ||||
| 			name->showError(); | ||||
| 			return; | ||||
| 		} else if (!(data->flags() & kTypes) && data->always().empty()) { | ||||
| 		} else if (!(rules.flags() & kTypes) && rules.always().empty()) { | ||||
| 			window->window().showToast(tr::lng_filters_empty(tr::now)); | ||||
| 			return; | ||||
| 		} else if ((data->flags() == (kTypes | Flag::NoArchived)) | ||||
| 			&& data->always().empty() | ||||
| 			&& data->never().empty()) { | ||||
| 		} else if ((rules.flags() == (kTypes | Flag::NoArchived)) | ||||
| 			&& rules.always().empty() | ||||
| 			&& rules.never().empty()) { | ||||
| 			window->window().showToast(tr::lng_filters_default(tr::now)); | ||||
| 			return; | ||||
| 		} | ||||
| 		const auto result = Data::ChatFilter( | ||||
| 			data->id(), | ||||
| 			title, | ||||
| 			data->iconEmoji(), | ||||
| 			data->flags(), | ||||
| 			data->always(), | ||||
| 			data->pinned(), | ||||
| 			data->never()); | ||||
| 		box->closeBox(); | ||||
| 
 | ||||
| 		doneCallback(result); | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ MTPDialogFilter ChatFilter::tl(FilterId replaceId) const { | |||
| 		never.push_back(history->peer->input); | ||||
| 	} | ||||
| 	return MTP_dialogFilter( | ||||
| 		MTP_flags(flags), | ||||
| 		MTP_flags(flags | TLFlag::f_emoticon), | ||||
| 		MTP_int(replaceId ? replaceId : _id), | ||||
| 		MTP_string(_title), | ||||
| 		MTP_string(_iconEmoji), | ||||
|  | @ -344,7 +344,8 @@ bool ChatFilters::applyChange(ChatFilter &filter, ChatFilter &&updated) { | |||
| 	const auto pinnedChanged = (filter.pinned() != updated.pinned()); | ||||
| 	if (!rulesChanged | ||||
| 		&& !pinnedChanged | ||||
| 		&& filter.title() == updated.title()) { | ||||
| 		&& filter.title() == updated.title() | ||||
| 		&& filter.iconEmoji() == updated.iconEmoji()) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (rulesChanged) { | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ private: | |||
| 
 | ||||
| inline bool operator==(const ChatFilter &a, const ChatFilter &b) { | ||||
| 	return (a.title() == b.title()) | ||||
| 		&& (a.iconEmoji() == b.iconEmoji()) | ||||
| 		&& (a.flags() == b.flags()) | ||||
| 		&& (a.always() == b.always()) | ||||
| 		&& (a.never() == b.never()); | ||||
|  |  | |||
							
								
								
									
										455
									
								
								Telegram/SourceFiles/ui/filter_icon_panel.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										455
									
								
								Telegram/SourceFiles/ui/filter_icon_panel.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,455 @@ | |||
| /*
 | ||||
| This file is part of Telegram Desktop, | ||||
| the official desktop application for the Telegram messaging service. | ||||
| 
 | ||||
| For license and copyright information please follow this link: | ||||
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | ||||
| */ | ||||
| #include "ui/filter_icon_panel.h" | ||||
| 
 | ||||
| #include "ui/widgets/shadow.h" | ||||
| #include "ui/image/image_prepare.h" | ||||
| #include "ui/effects/panel_animation.h" | ||||
| #include "ui/ui_utility.h" | ||||
| #include "ui/filter_icons.h" | ||||
| #include "lang/lang_keys.h" | ||||
| #include "core/application.h" | ||||
| #include "app.h" | ||||
| #include "styles/style_chat_helpers.h" | ||||
| #include "styles/style_window.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| namespace { | ||||
| 
 | ||||
| constexpr auto kHideTimeoutMs = crl::time(300); | ||||
| constexpr auto kDelayedHideTimeoutMs = 3 * crl::time(1000); | ||||
| constexpr auto kIconsPerRow = 6; | ||||
| 
 | ||||
| constexpr auto kIcons = std::array{ | ||||
| 	FilterIcon::Cat, | ||||
| 	FilterIcon::Crown, | ||||
| 	FilterIcon::Favorite, | ||||
| 	FilterIcon::Flower, | ||||
| 	FilterIcon::Game, | ||||
| 	FilterIcon::Home, | ||||
| 	FilterIcon::Love, | ||||
| 	FilterIcon::Mask, | ||||
| 	FilterIcon::Party, | ||||
| 	FilterIcon::Sport, | ||||
| 	FilterIcon::Study, | ||||
| 	FilterIcon::Trade, | ||||
| 	FilterIcon::Travel, | ||||
| 	FilterIcon::Work, | ||||
| 
 | ||||
| 	FilterIcon::All, | ||||
| 	FilterIcon::Unread, | ||||
| 	FilterIcon::Unmuted, | ||||
| 	FilterIcon::Bots, | ||||
| 	FilterIcon::Channels, | ||||
| 	FilterIcon::Groups, | ||||
| 	FilterIcon::Private, | ||||
| 	FilterIcon::Custom, | ||||
| 	FilterIcon::Setup, | ||||
| }; | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| FilterIconPanel::FilterIconPanel(QWidget *parent) | ||||
| : RpWidget(parent) | ||||
| , _inner(Ui::CreateChild<Ui::RpWidget>(this)) { | ||||
| 	setup(); | ||||
| } | ||||
| 
 | ||||
| FilterIconPanel::~FilterIconPanel() { | ||||
| 	hideFast(); | ||||
| } | ||||
| 
 | ||||
| rpl::producer<FilterIcon> FilterIconPanel::chosen() const { | ||||
| 	return _chosen.events(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::setup() { | ||||
| 	setupInner(); | ||||
| 	resize(_inner->rect().marginsAdded(innerPadding()).size()); | ||||
| 	_inner->move(innerRect().topLeft()); | ||||
| 
 | ||||
| 	_hideTimer.setCallback([=] { hideByTimerOrLeave(); }); | ||||
| 
 | ||||
| 	macWindowDeactivateEvents( | ||||
| 	) | rpl::filter([=] { | ||||
| 		return !isHidden(); | ||||
| 	}) | rpl::start_with_next([=] { | ||||
| 		hideAnimated(); | ||||
| 	}, lifetime()); | ||||
| 
 | ||||
| 	setAttribute(Qt::WA_OpaquePaintEvent, false); | ||||
| 
 | ||||
| 	hideChildren(); | ||||
| 	hide(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::setupInner() { | ||||
| 	const auto count = kIcons.size(); | ||||
| 	const auto rows = (count / kIconsPerRow) | ||||
| 		+ ((count % kIconsPerRow) ? 1 : 0); | ||||
| 	const auto single = st::windowFilterIconSingle; | ||||
| 	const auto size = QSize( | ||||
| 		single.width() * kIconsPerRow, | ||||
| 		single.height() * rows); | ||||
| 	const auto full = QRect(QPoint(), size).marginsAdded( | ||||
| 		st::windowFilterIconPadding).size(); | ||||
| 	_inner->resize(full); | ||||
| 
 | ||||
| 	_inner->paintRequest( | ||||
| 		) | rpl::start_with_next([=](QRect clip) { | ||||
| 		auto p = Painter(_inner); | ||||
| 		App::roundRect( | ||||
| 			p, | ||||
| 			_inner->rect(), | ||||
| 			st::emojiPanBg, | ||||
| 			ImageRoundRadius::Small); | ||||
| 		p.setFont(st::emojiPanHeaderFont); | ||||
| 		p.setPen(st::emojiPanHeaderFg); | ||||
| 		p.drawTextLeft( | ||||
| 			st::windowFilterIconHeaderPosition.x(), | ||||
| 			st::windowFilterIconHeaderPosition.y(), | ||||
| 			_inner->width(), | ||||
| 			tr::lng_filters_icon_header(tr::now)); | ||||
| 
 | ||||
| 		const auto selected = (_pressed >= 0) ? _pressed : _selected; | ||||
| 		for (auto i = 0; i != kIcons.size(); ++i) { | ||||
| 			const auto rect = countRect(i); | ||||
| 			if (!rect.intersects(clip)) { | ||||
| 				continue; | ||||
| 			} | ||||
| 			if (i == selected) { | ||||
| 				App::roundRect( | ||||
| 					p, | ||||
| 					rect, | ||||
| 					st::emojiPanHover, | ||||
| 					StickerHoverCorners); | ||||
| 			} | ||||
| 			const auto icon = LookupFilterIcon(kIcons[i]).normal; | ||||
| 			icon->paintInCenter(p, rect, st::emojiIconFg->c); | ||||
| 		} | ||||
| 	}, _inner->lifetime()); | ||||
| 
 | ||||
| 	_inner->setMouseTracking(true); | ||||
| 	_inner->events( | ||||
| 	) | rpl::start_with_next([=](not_null<QEvent*> e) { | ||||
| 		switch (e->type()) { | ||||
| 		case QEvent::Leave: setSelected(-1); break; | ||||
| 		case QEvent::MouseMove: | ||||
| 			mouseMove(static_cast<QMouseEvent*>(e.get())->pos()); | ||||
| 			break; | ||||
| 		case QEvent::MouseButtonPress: | ||||
| 			mousePress(static_cast<QMouseEvent*>(e.get())->button()); | ||||
| 			break; | ||||
| 		case QEvent::MouseButtonRelease: | ||||
| 			mouseRelease(static_cast<QMouseEvent*>(e.get())->button()); | ||||
| 			break; | ||||
| 		} | ||||
| 	}, _inner->lifetime()); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::setSelected(int selected) { | ||||
| 	if (_selected == selected) { | ||||
| 		return; | ||||
| 	} | ||||
| 	const auto was = (_selected >= 0); | ||||
| 	updateRect(_selected); | ||||
| 	_selected = selected; | ||||
| 	updateRect(_selected); | ||||
| 	const auto now = (_selected >= 0); | ||||
| 	if (was != now) { | ||||
| 		_inner->setCursor(now ? style::cur_pointer : style::cur_default); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::setPressed(int pressed) { | ||||
| 	if (_pressed == pressed) { | ||||
| 		return; | ||||
| 	} | ||||
| 	updateRect(_pressed); | ||||
| 	_pressed = pressed; | ||||
| 	updateRect(_pressed); | ||||
| } | ||||
| 
 | ||||
| QRect FilterIconPanel::countRect(int index) const { | ||||
| 	Expects(index >= 0); | ||||
| 
 | ||||
| 	const auto row = index / kIconsPerRow; | ||||
| 	const auto column = index % kIconsPerRow; | ||||
| 	const auto single = st::windowFilterIconSingle; | ||||
| 	const auto rect = QRect( | ||||
| 		QPoint(column * single.width(), row * single.height()), | ||||
| 		single); | ||||
| 	const auto padding = st::windowFilterIconPadding; | ||||
| 	return rect.translated(padding.left(), padding.top()); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::updateRect(int index) { | ||||
| 	if (index < 0) { | ||||
| 		return; | ||||
| 	} | ||||
| 	_inner->update(countRect(index)); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::mouseMove(QPoint position) { | ||||
| 	const auto padding = st::windowFilterIconPadding; | ||||
| 	if (!_inner->rect().marginsRemoved(padding).contains(position)) { | ||||
| 		setSelected(-1); | ||||
| 	} else { | ||||
| 		const auto point = position - QPoint(padding.left(), padding.top()); | ||||
| 		const auto column = point.x() / st::windowFilterIconSingle.width(); | ||||
| 		const auto row = point.y() / st::windowFilterIconSingle.height(); | ||||
| 		const auto index = row * kIconsPerRow + column; | ||||
| 		setSelected(index < kIcons.size() ? index : -1); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::mousePress(Qt::MouseButton button) { | ||||
| 	if (button != Qt::LeftButton) { | ||||
| 		return; | ||||
| 	} | ||||
| 	setPressed(_selected); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::mouseRelease(Qt::MouseButton button) { | ||||
| 	if (button != Qt::LeftButton) { | ||||
| 		return; | ||||
| 	} | ||||
| 	const auto pressed = _pressed; | ||||
| 	setPressed(-1); | ||||
| 	if (pressed == _selected && pressed >= 0) { | ||||
| 		Assert(pressed < kIcons.size()); | ||||
| 		_chosen.fire_copy(kIcons[pressed]); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::paintEvent(QPaintEvent *e) { | ||||
| 	Painter p(this); | ||||
| 
 | ||||
| 	// This call can finish _a_show animation and destroy _showAnimation.
 | ||||
| 	const auto opacityAnimating = _a_opacity.animating(); | ||||
| 
 | ||||
| 	const auto showAnimating = _a_show.animating(); | ||||
| 	if (_showAnimation && !showAnimating) { | ||||
| 		_showAnimation.reset(); | ||||
| 		if (!opacityAnimating) { | ||||
| 			showChildren(); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if (showAnimating) { | ||||
| 		Assert(_showAnimation != nullptr); | ||||
| 		if (auto opacity = _a_opacity.value(_hiding ? 0. : 1.)) { | ||||
| 			_showAnimation->paintFrame( | ||||
| 				p, | ||||
| 				0, | ||||
| 				0, | ||||
| 				width(), | ||||
| 				_a_show.value(1.), | ||||
| 				opacity); | ||||
| 		} | ||||
| 	} else if (opacityAnimating) { | ||||
| 		p.setOpacity(_a_opacity.value(_hiding ? 0. : 1.)); | ||||
| 		p.drawPixmap(0, 0, _cache); | ||||
| 	} else if (_hiding || isHidden()) { | ||||
| 		hideFinished(); | ||||
| 	} else { | ||||
| 		if (!_cache.isNull()) _cache = QPixmap(); | ||||
| 		Ui::Shadow::paint( | ||||
| 			p, | ||||
| 			innerRect(), | ||||
| 			width(), | ||||
| 			st::emojiPanAnimation.shadow); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::enterEventHook(QEvent *e) { | ||||
| 	Core::App().registerLeaveSubscription(this); | ||||
| 	showAnimated(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::leaveEventHook(QEvent *e) { | ||||
| 	Core::App().unregisterLeaveSubscription(this); | ||||
| 	if (_a_show.animating() || _a_opacity.animating()) { | ||||
| 		hideAnimated(); | ||||
| 	} else { | ||||
| 		_hideTimer.callOnce(kHideTimeoutMs); | ||||
| 	} | ||||
| 	return TWidget::leaveEventHook(e); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::otherEnter() { | ||||
| 	showAnimated(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::otherLeave() { | ||||
| 	if (_a_opacity.animating()) { | ||||
| 		hideByTimerOrLeave(); | ||||
| 	} else { | ||||
| 		_hideTimer.callOnce(0); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::hideFast() { | ||||
| 	if (isHidden()) return; | ||||
| 
 | ||||
| 	_hideTimer.cancel(); | ||||
| 	_hiding = false; | ||||
| 	_a_opacity.stop(); | ||||
| 	hideFinished(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::opacityAnimationCallback() { | ||||
| 	update(); | ||||
| 	if (!_a_opacity.animating()) { | ||||
| 		if (_hiding) { | ||||
| 			_hiding = false; | ||||
| 			hideFinished(); | ||||
| 		} else if (!_a_show.animating()) { | ||||
| 			showChildren(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::hideByTimerOrLeave() { | ||||
| 	if (isHidden()) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	hideAnimated(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::prepareCacheFor(bool hiding) { | ||||
| 	if (_a_opacity.animating()) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	auto showAnimation = base::take(_a_show); | ||||
| 	auto showAnimationData = base::take(_showAnimation); | ||||
| 	_hiding = false; | ||||
| 	showChildren(); | ||||
| 
 | ||||
| 	_cache = Ui::GrabWidget(this); | ||||
| 
 | ||||
| 	_a_show = base::take(showAnimation); | ||||
| 	_showAnimation = base::take(showAnimationData); | ||||
| 	_hiding = hiding; | ||||
| 	if (_a_show.animating()) { | ||||
| 		hideChildren(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::startOpacityAnimation(bool hiding) { | ||||
| 	prepareCacheFor(hiding); | ||||
| 	hideChildren(); | ||||
| 	_a_opacity.start( | ||||
| 		[=] { opacityAnimationCallback(); }, | ||||
| 		_hiding ? 1. : 0., | ||||
| 		_hiding ? 0. : 1., | ||||
| 		st::emojiPanDuration); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::startShowAnimation() { | ||||
| 	if (!_a_show.animating()) { | ||||
| 		auto image = grabForAnimation(); | ||||
| 
 | ||||
| 		_showAnimation = std::make_unique<Ui::PanelAnimation>(st::emojiPanAnimation, Ui::PanelAnimation::Origin::TopRight); | ||||
| 		auto inner = rect().marginsRemoved(st::emojiPanMargins); | ||||
| 		_showAnimation->setFinalImage(std::move(image), QRect(inner.topLeft() * cIntRetinaFactor(), inner.size() * cIntRetinaFactor())); | ||||
| 		_showAnimation->setCornerMasks(Images::CornersMask(ImageRoundRadius::Small)); | ||||
| 		_showAnimation->start(); | ||||
| 	} | ||||
| 	hideChildren(); | ||||
| 	_a_show.start([this] { update(); }, 0., 1., st::emojiPanShowDuration); | ||||
| } | ||||
| 
 | ||||
| QImage FilterIconPanel::grabForAnimation() { | ||||
| 	auto cache = base::take(_cache); | ||||
| 	auto opacityAnimation = base::take(_a_opacity); | ||||
| 	auto showAnimationData = base::take(_showAnimation); | ||||
| 	auto showAnimation = base::take(_a_show); | ||||
| 
 | ||||
| 	showChildren(); | ||||
| 	Ui::SendPendingMoveResizeEvents(this); | ||||
| 
 | ||||
| 	auto result = QImage( | ||||
| 		size() * cIntRetinaFactor(), | ||||
| 		QImage::Format_ARGB32_Premultiplied); | ||||
| 	result.setDevicePixelRatio(cRetinaFactor()); | ||||
| 	result.fill(Qt::transparent); | ||||
| 	if (_inner) { | ||||
| 		QPainter p(&result); | ||||
| 		Ui::RenderWidget(p, _inner, _inner->pos()); | ||||
| 	} | ||||
| 
 | ||||
| 	_a_show = base::take(showAnimation); | ||||
| 	_showAnimation = base::take(showAnimationData); | ||||
| 	_a_opacity = base::take(opacityAnimation); | ||||
| 	_cache = base::take(_cache); | ||||
| 
 | ||||
| 	return result; | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::hideAnimated() { | ||||
| 	if (isHidden() || _hiding) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	_hideTimer.cancel(); | ||||
| 	startOpacityAnimation(true); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::toggleAnimated() { | ||||
| 	if (isHidden() || _hiding || _hideAfterSlide) { | ||||
| 		showAnimated(); | ||||
| 	} else { | ||||
| 		hideAnimated(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::hideFinished() { | ||||
| 	hide(); | ||||
| 	_a_show.stop(); | ||||
| 	_showAnimation.reset(); | ||||
| 	_cache = QPixmap(); | ||||
| 	_hiding = false; | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::showAnimated() { | ||||
| 	_hideTimer.cancel(); | ||||
| 	_hideAfterSlide = false; | ||||
| 	showStarted(); | ||||
| } | ||||
| 
 | ||||
| void FilterIconPanel::showStarted() { | ||||
| 	if (isHidden()) { | ||||
| 		raise(); | ||||
| 		show(); | ||||
| 		startShowAnimation(); | ||||
| 	} else if (_hiding) { | ||||
| 		startOpacityAnimation(false); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool FilterIconPanel::eventFilter(QObject *obj, QEvent *e) { | ||||
| 	if (e->type() == QEvent::Enter) { | ||||
| 		otherEnter(); | ||||
| 	} else if (e->type() == QEvent::Leave) { | ||||
| 		otherLeave(); | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| style::margins FilterIconPanel::innerPadding() const { | ||||
| 	return st::emojiPanMargins; | ||||
| } | ||||
| 
 | ||||
| QRect FilterIconPanel::innerRect() const { | ||||
| 	return rect().marginsRemoved(innerPadding()); | ||||
| } | ||||
| 
 | ||||
| } // namespace Ui
 | ||||
							
								
								
									
										87
									
								
								Telegram/SourceFiles/ui/filter_icon_panel.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								Telegram/SourceFiles/ui/filter_icon_panel.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | |||
| /*
 | ||||
| This file is part of Telegram Desktop, | ||||
| the official desktop application for the Telegram messaging service. | ||||
| 
 | ||||
| For license and copyright information please follow this link: | ||||
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | ||||
| */ | ||||
| #pragma once | ||||
| 
 | ||||
| #include "base/timer.h" | ||||
| #include "ui/rp_widget.h" | ||||
| #include "ui/effects/animations.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
| 
 | ||||
| enum class FilterIcon : uchar; | ||||
| class PanelAnimation; | ||||
| 
 | ||||
| class FilterIconPanel final : public Ui::RpWidget { | ||||
| public: | ||||
| 	FilterIconPanel(QWidget *parent); | ||||
| 	~FilterIconPanel(); | ||||
| 
 | ||||
| 	void hideFast(); | ||||
| 	[[nodiscard]] bool hiding() const { | ||||
| 		return _hiding || _hideTimer.isActive(); | ||||
| 	} | ||||
| 
 | ||||
| 	[[nodiscard]] style::margins innerPadding() const; | ||||
| 
 | ||||
| 	void showAnimated(); | ||||
| 	void hideAnimated(); | ||||
| 	void toggleAnimated(); | ||||
| 
 | ||||
| 	[[nodiscard]] rpl::producer<FilterIcon> chosen() const; | ||||
| 
 | ||||
| private: | ||||
| 	void enterEventHook(QEvent *e) override; | ||||
| 	void leaveEventHook(QEvent *e) override; | ||||
| 	void otherEnter(); | ||||
| 	void otherLeave(); | ||||
| 
 | ||||
| 	void paintEvent(QPaintEvent *e) override; | ||||
| 	bool eventFilter(QObject *obj, QEvent *e) override; | ||||
| 
 | ||||
| 	void setup(); | ||||
| 	void setupInner(); | ||||
| 	void hideByTimerOrLeave(); | ||||
| 
 | ||||
| 	// Rounded rect which has shadow around it.
 | ||||
| 	[[nodiscard]] QRect innerRect() const; | ||||
| 
 | ||||
| 	[[nodiscard]] QImage grabForAnimation(); | ||||
| 	void startShowAnimation(); | ||||
| 	void startOpacityAnimation(bool hiding); | ||||
| 	void prepareCacheFor(bool hiding); | ||||
| 
 | ||||
| 	void opacityAnimationCallback(); | ||||
| 
 | ||||
| 	void hideFinished(); | ||||
| 	void showStarted(); | ||||
| 	void setSelected(int selected); | ||||
| 	void setPressed(int pressed); | ||||
| 	[[nodiscard]] QRect countRect(int index) const; | ||||
| 	void updateRect(int index); | ||||
| 	void mouseMove(QPoint position); | ||||
| 	void mousePress(Qt::MouseButton button); | ||||
| 	void mouseRelease(Qt::MouseButton button); | ||||
| 
 | ||||
| 	const not_null<Ui::RpWidget*> _inner; | ||||
| 	rpl::event_stream<FilterIcon> _chosen; | ||||
| 
 | ||||
| 	int _selected = -1; | ||||
| 	int _pressed = -1; | ||||
| 
 | ||||
| 	std::unique_ptr<Ui::PanelAnimation> _showAnimation; | ||||
| 	Ui::Animations::Simple _a_show; | ||||
| 
 | ||||
| 	bool _hiding = false; | ||||
| 	bool _hideAfterSlide = false; | ||||
| 	QPixmap _cache; | ||||
| 	Ui::Animations::Simple _a_opacity; | ||||
| 	base::Timer _hideTimer; | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| } // namespace Ui
 | ||||
|  | @ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL | |||
| #include "ui/filter_icons.h" | ||||
| 
 | ||||
| #include "ui/emoji_config.h" | ||||
| #include "data/data_chat_filters.h" | ||||
| #include "styles/style_filter_icons.h" | ||||
| 
 | ||||
| namespace Ui { | ||||
|  | @ -62,7 +63,7 @@ const auto kIcons = std::vector<FilterIcons>{ | |||
| 	{ | ||||
| 		&st::foldersCat, | ||||
| 		&st::foldersCatActive, | ||||
| 		"\xF0\x9F\x90\x88"_cs.utf16() | ||||
| 		"\xF0\x9F\x90\xB1"_cs.utf16() | ||||
| 	}, | ||||
| 	{ | ||||
| 		&st::foldersCrown, | ||||
|  | @ -155,4 +156,43 @@ std::optional<FilterIcon> LookupFilterIconByEmoji(const QString &emoji) { | |||
| 	return (i != end(kMap)) ? std::make_optional(i->second) : std::nullopt; | ||||
| } | ||||
| 
 | ||||
| FilterIcon ComputeDefaultFilterIcon(const Data::ChatFilter &filter) { | ||||
| 	using Icon = FilterIcon; | ||||
| 	using Flag = Data::ChatFilter::Flag; | ||||
| 
 | ||||
| 	const auto all = Flag::Contacts | ||||
| 		| Flag::NonContacts | ||||
| 		| Flag::Groups | ||||
| 		| Flag::Channels | ||||
| 		| Flag::Bots; | ||||
| 	const auto removed = Flag::NoRead | Flag::NoMuted; | ||||
| 	const auto people = Flag::Contacts | Flag::NonContacts; | ||||
| 	const auto allNoArchive = all | Flag::NoArchived; | ||||
| 	if (!filter.always().empty() | ||||
| 		|| !filter.never().empty() | ||||
| 		|| !(filter.flags() & all)) { | ||||
| 		return Icon::Custom; | ||||
| 	} else if ((filter.flags() & all) == Flag::Contacts | ||||
| 		|| (filter.flags() & all) == Flag::NonContacts | ||||
| 		|| (filter.flags() & all) == people) { | ||||
| 		return Icon::Private; | ||||
| 	} else if ((filter.flags() & all) == Flag::Groups) { | ||||
| 		return Icon::Groups; | ||||
| 	} else if ((filter.flags() & all) == Flag::Channels) { | ||||
| 		return Icon::Channels; | ||||
| 	} else if ((filter.flags() & all) == Flag::Bots) { | ||||
| 		return Icon::Bots; | ||||
| 	} else if ((filter.flags() & removed) == Flag::NoRead) { | ||||
| 		return Icon::Unread; | ||||
| 	} else if ((filter.flags() & removed) == Flag::NoMuted) { | ||||
| 		return Icon::Unmuted; | ||||
| 	} | ||||
| 	return Icon::Custom; | ||||
| } | ||||
| 
 | ||||
| FilterIcon ComputeFilterIcon(const Data::ChatFilter &filter) { | ||||
| 	return LookupFilterIconByEmoji(filter.iconEmoji()).value_or( | ||||
| 		ComputeDefaultFilterIcon(filter)); | ||||
| } | ||||
| 
 | ||||
| } // namespace Ui
 | ||||
|  |  | |||
|  | @ -13,6 +13,10 @@ class Icon; | |||
| } // namespace internal
 | ||||
| } // namespace style
 | ||||
| 
 | ||||
| namespace Data { | ||||
| class ChatFilter; | ||||
| } // namespace Data
 | ||||
| 
 | ||||
| namespace Ui { | ||||
| 
 | ||||
| enum class FilterIcon : uchar { | ||||
|  | @ -52,4 +56,8 @@ struct FilterIcons { | |||
| [[nodiscard]] std::optional<FilterIcon> LookupFilterIconByEmoji( | ||||
| 	const QString &emoji); | ||||
| 
 | ||||
| [[nodiscard]] FilterIcon ComputeDefaultFilterIcon( | ||||
| 	const Data::ChatFilter &filter); | ||||
| [[nodiscard]] FilterIcon ComputeFilterIcon(const Data::ChatFilter &filter); | ||||
| 
 | ||||
| } // namespace Ui
 | ||||
|  |  | |||
|  | @ -284,6 +284,15 @@ windowFilterSmallList: PeerList(defaultPeerList) { | |||
| windowFilterSmallRemove: IconButton(notifyClose) { | ||||
| } | ||||
| windowFilterSmallRemoveRight: 10px; | ||||
| windowFilterNameInput: InputField(defaultInputField) { | ||||
| 	textMargins: margins(0px, 26px, 36px, 4px); | ||||
| } | ||||
| windowFilterIconToggleSize: size(36px, 36px); | ||||
| windowFilterIconTogglePosition: point(-4px, 12px); | ||||
| windwoFilterIconPanelPosition: point(-2px, -1px); | ||||
| windowFilterIconSingle: size(44px, 42px); | ||||
| windowFilterIconPadding: margins(10px, 36px, 10px, 8px); | ||||
| windowFilterIconHeaderPosition: point(18px, 14px); | ||||
| windowFilterTypeContacts: icon {{ "filters_type_contacts", historyPeerUserpicFg }}; | ||||
| windowFilterTypeNonContacts: icon {{ "filters_type_noncontacts", historyPeerUserpicFg }}; | ||||
| windowFilterTypeGroups: icon {{ "filters_type_groups", historyPeerUserpicFg }}; | ||||
|  |  | |||
|  | @ -24,39 +24,6 @@ namespace { | |||
| 
 | ||||
| using Icon = Ui::FilterIcon; | ||||
| 
 | ||||
| [[nodiscard]] Icon ComputeIcon(const Data::ChatFilter &filter) { | ||||
| 	using Flag = Data::ChatFilter::Flag; | ||||
| 
 | ||||
| 	const auto all = Flag::Contacts | ||||
| 		| Flag::NonContacts | ||||
| 		| Flag::Groups | ||||
| 		| Flag::Channels | ||||
| 		| Flag::Bots; | ||||
| 	const auto removed = Flag::NoRead | Flag::NoMuted; | ||||
| 	const auto people = Flag::Contacts | Flag::NonContacts; | ||||
| 	const auto allNoArchive = all | Flag::NoArchived; | ||||
| 	if (!filter.always().empty() | ||||
| 		|| !filter.never().empty() | ||||
| 		|| !(filter.flags() & all)) { | ||||
| 		return Icon::Custom; | ||||
| 	} else if ((filter.flags() & all) == Flag::Contacts | ||||
| 		|| (filter.flags() & all) == Flag::NonContacts | ||||
| 		|| (filter.flags() & all) == people) { | ||||
| 		return Icon::Private; | ||||
| 	} else if ((filter.flags() & all) == Flag::Groups) { | ||||
| 		return Icon::Groups; | ||||
| 	} else if ((filter.flags() & all) == Flag::Channels) { | ||||
| 		return Icon::Channels; | ||||
| 	} else if ((filter.flags() & all) == Flag::Bots) { | ||||
| 		return Icon::Bots; | ||||
| 	} else if ((filter.flags() & removed) == Flag::NoRead) { | ||||
| 		return Icon::Unread; | ||||
| 	} else if ((filter.flags() & removed) == Flag::NoMuted) { | ||||
| 		return Icon::Unmuted; | ||||
| 	} | ||||
| 	return Icon::Custom; | ||||
| } | ||||
| 
 | ||||
| } // namespace
 | ||||
| 
 | ||||
| FiltersMenu::FiltersMenu( | ||||
|  | @ -187,7 +154,7 @@ void FiltersMenu::refresh() { | |||
| 		prepare( | ||||
| 			filter.id(), | ||||
| 			filter.title(), | ||||
| 			ComputeIcon(filter), | ||||
| 			Ui::ComputeFilterIcon(filter), | ||||
| 			QString()); | ||||
| 	} | ||||
| 	prepare(-1, tr::lng_filters_setup(tr::now), Icon::Setup, {}); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 John Preston
						John Preston