/* 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 create( not_null 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 _masterOpacity = 1.; rpl::variable _maximized = false; rpl::event_stream<> _clearStateRequests; }; object_ptr DefaultOverlayWidgetHelper::Buttons::create( not_null parent, Control control, const style::WindowTitle &st) { auto result = object_ptr(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(); 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 window) { window->setWindowState(window->windowState() | Qt::WindowMinimized); } DefaultOverlayWidgetHelper::DefaultOverlayWidgetHelper( not_null window, Fn maximize) : _buttons(new DefaultOverlayWidgetHelper::Buttons()) , _controls(Ui::Platform::SetupSeparateTitleControls( window, std::make_unique( window->body(), st::mediaviewTitle, std::unique_ptr(_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 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> { return _controls->wrap.events( ) | rpl::filter([](not_null e) { const auto type = e->type(); return (type == QEvent::MouseButtonPress) || (type == QEvent::MouseButtonRelease) || (type == QEvent::MouseMove) || (type == QEvent::MouseButtonDblClick); }) | rpl::map([](not_null e) { return not_null{ static_cast(e.get()) }; }); } } // namespace Platform