270 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| 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 "platform/platform_overlay_widget.h"
 | |
| 
 | |
| #include "ui/effects/animations.h"
 | |
| #include "ui/platform/ui_platform_window_title.h"
 | |
| #include "ui/platform/ui_platform_utility.h"
 | |
| #include "ui/widgets/rp_window.h"
 | |
| #include "ui/abstract_button.h"
 | |
| #include "styles/style_media_view.h"
 | |
| 
 | |
| namespace Media::View {
 | |
| 
 | |
| QColor OverBackgroundColor() {
 | |
| 	auto c1 = st::mediaviewBg->c;
 | |
| 	auto c2 = QColor(255, 255, 255);
 | |
| 	const auto mix = [&](int a, int b) {
 | |
| 		constexpr auto k1 = 0.15 * 0.85 / (1. - 0.85 * 0.85);
 | |
| 		constexpr auto k2 = 0.15 / (1. - 0.85 * 0.85);
 | |
| 		return int(a * k1 + b * k2);
 | |
| 	};
 | |
| 	return QColor(
 | |
| 		mix(c1.red(), c2.red()),
 | |
| 		mix(c1.green(), c2.green()),
 | |
| 		mix(c1.blue(), c2.blue()));
 | |
| }
 | |
| 
 | |
| } // namespace Media::View
 | |
| 
 | |
| namespace Platform {
 | |
| namespace {
 | |
| 
 | |
| using namespace Media::View;
 | |
| 
 | |
| } // namespace
 | |
| 
 | |
| class DefaultOverlayWidgetHelper::Buttons final
 | |
| 	: public Ui::Platform::AbstractTitleButtons {
 | |
| public:
 | |
| 	using Control = Ui::Platform::TitleControl;
 | |
| 
 | |
| 	object_ptr<Ui::AbstractButton> create(
 | |
| 		not_null<QWidget*> parent,
 | |
| 		Control control,
 | |
| 		const style::WindowTitle &st) override;
 | |
| 	void updateState(
 | |
| 		bool active,
 | |
| 		bool maximized,
 | |
| 		const style::WindowTitle &st) override;
 | |
| 	void notifySynteticOver(Control control, bool over) override;
 | |
| 
 | |
| 	void setMasterOpacity(float64 opacity);
 | |
| 	[[nodiscard]] rpl::producer<> activations() const;
 | |
| 
 | |
| 	void clearState();
 | |
| 
 | |
| private:
 | |
| 	rpl::event_stream<> _activations;
 | |
| 	rpl::variable<float64> _masterOpacity = 1.;
 | |
| 	rpl::variable<bool> _maximized = false;
 | |
| 	rpl::event_stream<> _clearStateRequests;
 | |
| 
 | |
| };
 | |
| 
 | |
| object_ptr<Ui::AbstractButton> DefaultOverlayWidgetHelper::Buttons::create(
 | |
| 		not_null<QWidget*> parent,
 | |
| 		Control control,
 | |
| 		const style::WindowTitle &st) {
 | |
| 	auto result = object_ptr<Ui::AbstractButton>(parent);
 | |
| 	const auto raw = result.data();
 | |
| 
 | |
| 	struct State {
 | |
| 		Ui::Animations::Simple animation;
 | |
| 		float64 progress = -1.;
 | |
| 		QImage frame;
 | |
| 		bool maximized = false;
 | |
| 		bool over = false;
 | |
| 	};
 | |
| 	const auto state = raw->lifetime().make_state<State>();
 | |
| 
 | |
| 	rpl::merge(
 | |
| 		_masterOpacity.changes() | rpl::to_empty,
 | |
| 		_maximized.changes() | rpl::to_empty
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		raw->update();
 | |
| 	}, raw->lifetime());
 | |
| 
 | |
| 	_clearStateRequests.events(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		raw->clearState();
 | |
| 		raw->update();
 | |
| 		state->over = raw->isOver();
 | |
| 		state->animation.stop();
 | |
| 	}, raw->lifetime());
 | |
| 
 | |
| 	const auto icon = [&] {
 | |
| 		switch (control) {
 | |
| 		case Control::Minimize: return &st::mediaviewTitleMinimize;
 | |
| 		case Control::Maximize: return &st::mediaviewTitleMaximize;
 | |
| 		case Control::Close: return &st::mediaviewTitleClose;
 | |
| 		}
 | |
| 		Unexpected("Value in DefaultOverlayWidgetHelper::Buttons::create.");
 | |
| 	}();
 | |
| 
 | |
| 	raw->resize(icon->size());
 | |
| 	state->frame = QImage(
 | |
| 		icon->size() * style::DevicePixelRatio(),
 | |
| 		QImage::Format_ARGB32_Premultiplied);
 | |
| 	state->frame.setDevicePixelRatio(style::DevicePixelRatio());
 | |
| 
 | |
| 	const auto updateOver = [=] {
 | |
| 		const auto over = raw->isOver();
 | |
| 		if (state->over == over) {
 | |
| 			return;
 | |
| 		}
 | |
| 		state->over = over;
 | |
| 		state->animation.start(
 | |
| 			[=] { raw->update(); },
 | |
| 			state->over ? 0. : 1.,
 | |
| 			state->over ? 1. : 0.,
 | |
| 			st::mediaviewFadeDuration);
 | |
| 	};
 | |
| 
 | |
| 	const auto prepareFrame = [=] {
 | |
| 		const auto progress = state->animation.value(state->over ? 1. : 0.);
 | |
| 		const auto maximized = _maximized.current();
 | |
| 		if (state->progress == progress && state->maximized == maximized) {
 | |
| 			return;
 | |
| 		}
 | |
| 		state->progress = progress;
 | |
| 		state->maximized = maximized;
 | |
| 		auto current = icon;
 | |
| 		if (control == Control::Maximize) {
 | |
| 			current = maximized ? &st::mediaviewTitleRestore : icon;
 | |
| 		}
 | |
| 		const auto alpha = progress * kOverBackgroundOpacity;
 | |
| 		auto color = OverBackgroundColor();
 | |
| 		color.setAlpha(anim::interpolate(0, 255, alpha));
 | |
| 		state->frame.fill(color);
 | |
| 
 | |
| 		auto q = QPainter(&state->frame);
 | |
| 		const auto normal = maximized
 | |
| 			? kMaximizedIconOpacity
 | |
| 			: kNormalIconOpacity;
 | |
| 		q.setOpacity(progress + (1 - progress) * normal);
 | |
| 		current->paint(q, 0, 0, raw->width());
 | |
| 		q.end();
 | |
| 	};
 | |
| 
 | |
| 	raw->paintRequest(
 | |
| 	) | rpl::start_with_next([=] {
 | |
| 		updateOver();
 | |
| 		prepareFrame();
 | |
| 
 | |
| 		auto p = QPainter(raw);
 | |
| 		p.setOpacity(_masterOpacity.current());
 | |
| 		p.drawImage(0, 0, state->frame);
 | |
| 	}, raw->lifetime());
 | |
| 
 | |
| 	return result;
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::Buttons::updateState(
 | |
| 		bool active,
 | |
| 		bool maximized,
 | |
| 		const style::WindowTitle &st) {
 | |
| 	_maximized = maximized;
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::Buttons::notifySynteticOver(
 | |
| 		Ui::Platform::TitleControl control,
 | |
| 		bool over) {
 | |
| 	if (over) {
 | |
| 		_activations.fire({});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::Buttons::clearState() {
 | |
| 	_clearStateRequests.fire({});
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::Buttons::setMasterOpacity(float64 opacity) {
 | |
| 	_masterOpacity = opacity;
 | |
| }
 | |
| 
 | |
| rpl::producer<> DefaultOverlayWidgetHelper::Buttons::activations() const {
 | |
| 	return _activations.events();
 | |
| }
 | |
| 
 | |
| void OverlayWidgetHelper::minimize(not_null<Ui::RpWindow*> window) {
 | |
| 	window->setWindowState(window->windowState() | Qt::WindowMinimized);
 | |
| }
 | |
| 
 | |
| DefaultOverlayWidgetHelper::DefaultOverlayWidgetHelper(
 | |
| 	not_null<Ui::RpWindow*> window,
 | |
| 	Fn<void(bool)> maximize)
 | |
| : _buttons(new DefaultOverlayWidgetHelper::Buttons())
 | |
| , _controls(Ui::Platform::SetupSeparateTitleControls(
 | |
| 	window,
 | |
| 	std::make_unique<Ui::Platform::SeparateTitleControls>(
 | |
| 		window->body(),
 | |
| 		st::mediaviewTitle,
 | |
| 		std::unique_ptr<DefaultOverlayWidgetHelper::Buttons>(_buttons.get()),
 | |
| 		std::move(maximize)))) {
 | |
| }
 | |
| 
 | |
| DefaultOverlayWidgetHelper::~DefaultOverlayWidgetHelper() = default;
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::orderWidgets() {
 | |
| 	_controls->wrap.raise();
 | |
| }
 | |
| 
 | |
| bool DefaultOverlayWidgetHelper::skipTitleHitTest(QPoint position) {
 | |
| 	return _controls->controls.geometry().contains(position);
 | |
| }
 | |
| 
 | |
| rpl::producer<> DefaultOverlayWidgetHelper::controlsActivations() {
 | |
| 	return _buttons->activations();
 | |
| }
 | |
| 
 | |
| rpl::producer<bool> DefaultOverlayWidgetHelper::controlsSideRightValue() {
 | |
| 	using namespace Ui::Platform;
 | |
| 
 | |
| 	return TitleControlsLayoutValue(
 | |
| 	) | rpl::map([=](const TitleControls::Layout &layout) {
 | |
| 		// See TitleControls::updateControlsPosition.
 | |
| 		if (ranges::contains(layout.left, TitleControl::Close)) {
 | |
| 			return false;
 | |
| 		} else if (ranges::contains(layout.right, TitleControl::Close)) {
 | |
| 			return true;
 | |
| 		} else if (layout.left.size() > layout.right.size()) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			return true;
 | |
| 		}
 | |
| 	}) | rpl::distinct_until_changed();
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::beforeShow(bool fullscreen) {
 | |
| 	_buttons->clearState();
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::clearState() {
 | |
| 	_buttons->clearState();
 | |
| }
 | |
| 
 | |
| void DefaultOverlayWidgetHelper::setControlsOpacity(float64 opacity) {
 | |
| 	_buttons->setMasterOpacity(opacity);
 | |
| }
 | |
| 
 | |
| auto DefaultOverlayWidgetHelper::mouseEvents() const
 | |
| -> rpl::producer<not_null<QMouseEvent*>> {
 | |
| 	return _controls->wrap.events(
 | |
| 	) | rpl::filter([](not_null<QEvent*> e) {
 | |
| 		const auto type = e->type();
 | |
| 		return (type == QEvent::MouseButtonPress)
 | |
| 			|| (type == QEvent::MouseButtonRelease)
 | |
| 			|| (type == QEvent::MouseMove)
 | |
| 			|| (type == QEvent::MouseButtonDblClick);
 | |
| 	}) | rpl::map([](not_null<QEvent*> e) {
 | |
| 		return not_null{ static_cast<QMouseEvent*>(e.get()) };
 | |
| 	});
 | |
| }
 | |
| 
 | |
| } // namespace Platform
 |