2024-01-09 11:49:35 +00:00
|
|
|
/*
|
|
|
|
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 "chat_helpers/ttl_media_layer_widget.h"
|
|
|
|
|
|
|
|
#include "base/event_filter.h"
|
2024-01-14 02:42:21 +00:00
|
|
|
#include "data/data_document.h"
|
2024-01-09 11:49:35 +00:00
|
|
|
#include "data/data_session.h"
|
|
|
|
#include "editor/editor_layer_widget.h"
|
|
|
|
#include "history/history.h"
|
|
|
|
#include "history/history_item.h"
|
|
|
|
#include "history/view/history_view_element.h"
|
|
|
|
#include "history/view/media/history_view_document.h"
|
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "main/main_session.h"
|
|
|
|
#include "mainwidget.h"
|
|
|
|
#include "media/audio/media_audio.h"
|
|
|
|
#include "media/player/media_player_instance.h"
|
|
|
|
#include "ui/chat/chat_style.h"
|
|
|
|
#include "ui/chat/chat_theme.h"
|
|
|
|
#include "ui/effects/path_shift_gradient.h"
|
|
|
|
#include "ui/painter.h"
|
2024-01-09 14:06:37 +00:00
|
|
|
#include "ui/rect.h"
|
2024-01-09 11:49:35 +00:00
|
|
|
#include "ui/text/text_utilities.h"
|
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/widgets/labels.h"
|
|
|
|
#include "ui/widgets/tooltip.h"
|
2024-01-14 01:00:59 +00:00
|
|
|
#include "window/section_widget.h" // Window::ChatThemeValueFromPeer.
|
2024-01-09 11:49:35 +00:00
|
|
|
#include "window/themes/window_theme.h"
|
2024-01-16 06:34:49 +00:00
|
|
|
#include "window/window_controller.h"
|
2024-01-09 11:49:35 +00:00
|
|
|
#include "window/window_session_controller.h"
|
|
|
|
#include "styles/style_chat.h"
|
|
|
|
#include "styles/style_chat_helpers.h"
|
|
|
|
#include "styles/style_dialogs.h"
|
|
|
|
|
|
|
|
namespace ChatHelpers {
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
class PreviewDelegate final : public HistoryView::DefaultElementDelegate {
|
|
|
|
public:
|
|
|
|
PreviewDelegate(
|
|
|
|
not_null<QWidget*> parent,
|
|
|
|
not_null<Ui::ChatStyle*> st,
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::producer<bool> chatWideValue,
|
2024-01-09 11:49:35 +00:00
|
|
|
Fn<void()> update);
|
|
|
|
|
|
|
|
bool elementAnimationsPaused() override;
|
|
|
|
not_null<Ui::PathShiftGradient*> elementPathShiftGradient() override;
|
|
|
|
HistoryView::Context elementContext() override;
|
|
|
|
bool elementIsChatWide() override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
const not_null<QWidget*> _parent;
|
|
|
|
const std::unique_ptr<Ui::PathShiftGradient> _pathGradient;
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::variable<bool> _chatWide;
|
2024-01-09 11:49:35 +00:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
PreviewDelegate::PreviewDelegate(
|
|
|
|
not_null<QWidget*> parent,
|
|
|
|
not_null<Ui::ChatStyle*> st,
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::producer<bool> chatWideValue,
|
2024-01-09 11:49:35 +00:00
|
|
|
Fn<void()> update)
|
|
|
|
: _parent(parent)
|
2024-01-16 06:34:49 +00:00
|
|
|
, _pathGradient(HistoryView::MakePathShiftGradient(st, update))
|
|
|
|
, _chatWide(std::move(chatWideValue)) {
|
2024-01-09 11:49:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool PreviewDelegate::elementAnimationsPaused() {
|
|
|
|
return _parent->window()->isActiveWindow();
|
|
|
|
}
|
|
|
|
|
|
|
|
not_null<Ui::PathShiftGradient*> PreviewDelegate::elementPathShiftGradient() {
|
|
|
|
return _pathGradient.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
HistoryView::Context PreviewDelegate::elementContext() {
|
|
|
|
return HistoryView::Context::TTLViewer;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PreviewDelegate::elementIsChatWide() {
|
2024-01-16 06:34:49 +00:00
|
|
|
return _chatWide.current();
|
2024-01-09 11:49:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class PreviewWrap final : public Ui::RpWidget {
|
|
|
|
public:
|
2024-01-14 01:00:59 +00:00
|
|
|
PreviewWrap(
|
|
|
|
not_null<Ui::RpWidget*> parent,
|
|
|
|
not_null<HistoryItem*> item,
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::producer<QRect> viewportValue,
|
|
|
|
rpl::producer<bool> chatWideValue,
|
2024-01-14 01:00:59 +00:00
|
|
|
rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme);
|
2024-01-09 11:49:35 +00:00
|
|
|
~PreviewWrap();
|
|
|
|
|
|
|
|
[[nodiscard]] rpl::producer<> closeRequests() const;
|
|
|
|
|
|
|
|
private:
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
2024-01-16 17:43:19 +00:00
|
|
|
void createView();
|
|
|
|
[[nodiscard]] bool goodItem() const;
|
|
|
|
void clear();
|
2024-01-09 11:49:35 +00:00
|
|
|
|
|
|
|
const not_null<HistoryItem*> _item;
|
|
|
|
const std::unique_ptr<Ui::ChatStyle> _style;
|
|
|
|
const std::unique_ptr<PreviewDelegate> _delegate;
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::variable<QRect> _globalViewport;
|
|
|
|
rpl::variable<bool> _chatWide;
|
2024-01-14 01:00:59 +00:00
|
|
|
std::shared_ptr<Ui::ChatTheme> _theme;
|
2024-01-09 11:49:35 +00:00
|
|
|
std::unique_ptr<HistoryView::Element> _element;
|
2024-01-16 06:34:49 +00:00
|
|
|
QRect _viewport;
|
|
|
|
QRect _elementGeometry;
|
|
|
|
rpl::variable<QRect> _elementInner;
|
2024-01-09 11:49:35 +00:00
|
|
|
rpl::lifetime _elementLifetime;
|
|
|
|
|
2024-01-16 07:56:28 +00:00
|
|
|
QImage _lastFrameCache;
|
2024-01-09 14:06:37 +00:00
|
|
|
|
2024-01-09 11:49:35 +00:00
|
|
|
rpl::event_stream<> _closeRequests;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
PreviewWrap::PreviewWrap(
|
|
|
|
not_null<Ui::RpWidget*> parent,
|
2024-01-14 01:00:59 +00:00
|
|
|
not_null<HistoryItem*> item,
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::producer<QRect> viewportValue,
|
|
|
|
rpl::producer<bool> chatWideValue,
|
2024-01-14 01:00:59 +00:00
|
|
|
rpl::producer<std::shared_ptr<Ui::ChatTheme>> theme)
|
2024-01-09 11:49:35 +00:00
|
|
|
: RpWidget(parent)
|
|
|
|
, _item(item)
|
|
|
|
, _style(std::make_unique<Ui::ChatStyle>(
|
|
|
|
item->history()->session().colorIndicesValue()))
|
|
|
|
, _delegate(std::make_unique<PreviewDelegate>(
|
|
|
|
parent,
|
|
|
|
_style.get(),
|
2024-01-16 06:34:49 +00:00
|
|
|
std::move(chatWideValue),
|
|
|
|
[=] { update(_elementGeometry); }))
|
|
|
|
, _globalViewport(std::move(viewportValue)) {
|
2024-01-16 07:56:28 +00:00
|
|
|
const auto closeCallback = [=] { _closeRequests.fire({}); };
|
|
|
|
HistoryView::TTLVoiceStops(
|
|
|
|
item->fullId()
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
_lastFrameCache = Ui::GrabWidgetToImage(this, _elementGeometry);
|
|
|
|
closeCallback();
|
|
|
|
}, lifetime());
|
|
|
|
|
2024-01-14 02:42:21 +00:00
|
|
|
const auto isRound = _item
|
|
|
|
&& _item->media()
|
|
|
|
&& _item->media()->document()
|
|
|
|
&& _item->media()->document()->isVideoMessage();
|
|
|
|
|
2024-01-14 01:00:59 +00:00
|
|
|
std::move(
|
|
|
|
theme
|
|
|
|
) | rpl::start_with_next([=](std::shared_ptr<Ui::ChatTheme> theme) {
|
|
|
|
_theme = std::move(theme);
|
|
|
|
_style->apply(_theme.get());
|
|
|
|
}, lifetime());
|
2024-01-09 11:49:35 +00:00
|
|
|
|
|
|
|
const auto session = &_item->history()->session();
|
|
|
|
session->data().viewRepaintRequest(
|
|
|
|
) | rpl::start_with_next([=](not_null<const HistoryView::Element*> view) {
|
|
|
|
if (view == _element.get()) {
|
2024-01-16 06:34:49 +00:00
|
|
|
update(_elementGeometry);
|
2024-01-09 11:49:35 +00:00
|
|
|
}
|
|
|
|
}, lifetime());
|
2024-01-16 17:43:19 +00:00
|
|
|
session->data().itemViewRefreshRequest(
|
|
|
|
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
|
|
|
if (item == _item) {
|
|
|
|
if (goodItem()) {
|
|
|
|
createView();
|
|
|
|
update();
|
|
|
|
} else {
|
|
|
|
clear();
|
|
|
|
_closeRequests.fire({});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, lifetime());
|
|
|
|
session->data().itemDataChanges(
|
|
|
|
) | rpl::start_with_next([=](not_null<HistoryItem*> item) {
|
|
|
|
if (item == _item) {
|
|
|
|
_element->itemDataChanged();
|
|
|
|
}
|
|
|
|
}, lifetime());
|
|
|
|
session->data().itemRemoved(
|
|
|
|
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
|
|
|
if (item == _item) {
|
|
|
|
_closeRequests.fire({});
|
|
|
|
}
|
|
|
|
}, lifetime());
|
2024-01-09 11:49:35 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
const auto close = Ui::CreateChild<Ui::RoundButton>(
|
|
|
|
this,
|
|
|
|
item->out()
|
|
|
|
? tr::lng_close()
|
|
|
|
: tr::lng_ttl_voice_close_in(),
|
|
|
|
st::ttlMediaButton);
|
|
|
|
close->setFullRadius(true);
|
|
|
|
close->setClickedCallback(closeCallback);
|
|
|
|
close->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
|
|
|
|
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::combine(
|
|
|
|
sizeValue(),
|
|
|
|
_elementInner.value()
|
|
|
|
) | rpl::start_with_next([=](QSize size, QRect inner) {
|
2024-01-09 11:49:35 +00:00
|
|
|
close->moveToLeft(
|
2024-01-16 06:34:49 +00:00
|
|
|
inner.x() + (inner.width() - close->width()) / 2,
|
|
|
|
(size.height()
|
|
|
|
- close->height()
|
|
|
|
- st::ttlMediaButtonBottomSkip));
|
2024-01-09 11:49:35 +00:00
|
|
|
}, close->lifetime());
|
|
|
|
}
|
|
|
|
|
|
|
|
QWidget::setAttribute(Qt::WA_OpaquePaintEvent, false);
|
2024-01-16 17:43:19 +00:00
|
|
|
createView();
|
2024-01-09 11:49:35 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
auto text = item->out()
|
2024-01-14 02:42:21 +00:00
|
|
|
? (isRound
|
|
|
|
? tr::lng_ttl_round_tooltip_out
|
|
|
|
: tr::lng_ttl_voice_tooltip_out)(
|
|
|
|
lt_user,
|
|
|
|
rpl::single(
|
|
|
|
item->history()->peer->shortName()
|
|
|
|
) | rpl::map(Ui::Text::RichLangValue),
|
|
|
|
Ui::Text::RichLangValue)
|
|
|
|
: (isRound
|
|
|
|
? tr::lng_ttl_round_tooltip_in
|
|
|
|
: tr::lng_ttl_voice_tooltip_in)(Ui::Text::RichLangValue);
|
2024-01-09 11:49:35 +00:00
|
|
|
const auto tooltip = Ui::CreateChild<Ui::ImportantTooltip>(
|
|
|
|
this,
|
|
|
|
object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
|
|
|
this,
|
|
|
|
Ui::MakeNiceTooltipLabel(
|
|
|
|
parent,
|
|
|
|
std::move(text),
|
|
|
|
st::dialogsStoriesTooltipMaxWidth,
|
|
|
|
st::ttlMediaImportantTooltipLabel),
|
|
|
|
st::defaultImportantTooltip.padding),
|
|
|
|
st::dialogsStoriesTooltip);
|
|
|
|
tooltip->toggleFast(true);
|
2024-01-16 06:34:49 +00:00
|
|
|
_elementInner.value(
|
|
|
|
) | rpl::filter([](const QRect &inner) {
|
|
|
|
return !inner.isEmpty();
|
|
|
|
}) | rpl::start_with_next([=](const QRect &inner) {
|
|
|
|
tooltip->pointAt(inner, RectPart::Top, [=](QSize size) {
|
2024-01-09 11:49:35 +00:00
|
|
|
return QPoint{
|
2024-01-16 06:34:49 +00:00
|
|
|
inner.x() + (inner.width() - size.width()) / 2,
|
|
|
|
(inner.y()
|
|
|
|
- st::normalFont->height
|
|
|
|
- size.height()
|
|
|
|
- st::defaultImportantTooltip.padding.top()),
|
2024-01-09 11:49:35 +00:00
|
|
|
};
|
|
|
|
});
|
|
|
|
}, tooltip->lifetime());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<> PreviewWrap::closeRequests() const {
|
|
|
|
return _closeRequests.events();
|
|
|
|
}
|
|
|
|
|
2024-01-16 17:43:19 +00:00
|
|
|
bool PreviewWrap::goodItem() const {
|
|
|
|
const auto media = _item->media();
|
|
|
|
if (!media || !media->ttlSeconds()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const auto document = media->document();
|
|
|
|
return document
|
|
|
|
&& (document->isVoiceMessage() || document->isVideoMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
void PreviewWrap::createView() {
|
|
|
|
clear();
|
|
|
|
_element = _item->createView(_delegate.get());
|
|
|
|
_element->initDimensions();
|
|
|
|
rpl::combine(
|
|
|
|
sizeValue(),
|
|
|
|
_globalViewport.value()
|
|
|
|
) | rpl::start_with_next([=](QSize outer, QRect globalViewport) {
|
|
|
|
_viewport = globalViewport.isEmpty()
|
|
|
|
? rect()
|
|
|
|
: mapFromGlobal(globalViewport);
|
|
|
|
if (_viewport.width() < st::msgMinWidth) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
_element->resizeGetHeight(_viewport.width());
|
|
|
|
_elementGeometry = QRect(
|
|
|
|
(_viewport.width() - _element->width()) / 2,
|
|
|
|
(_viewport.height() - _element->height()) / 2,
|
|
|
|
_element->width(),
|
|
|
|
_element->height()
|
|
|
|
).translated(_viewport.topLeft());
|
|
|
|
_elementInner = _element->innerGeometry().translated(
|
|
|
|
_elementGeometry.topLeft());
|
|
|
|
update();
|
|
|
|
}, _elementLifetime);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PreviewWrap::clear() {
|
2024-01-09 11:49:35 +00:00
|
|
|
_elementLifetime.destroy();
|
|
|
|
_element = nullptr;
|
|
|
|
}
|
|
|
|
|
2024-01-16 17:43:19 +00:00
|
|
|
PreviewWrap::~PreviewWrap() {
|
|
|
|
clear();
|
|
|
|
}
|
|
|
|
|
2024-01-09 11:49:35 +00:00
|
|
|
void PreviewWrap::paintEvent(QPaintEvent *e) {
|
2024-01-16 06:34:49 +00:00
|
|
|
if (!_element || _elementGeometry.isEmpty()) {
|
2024-01-09 11:49:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-01-16 07:56:28 +00:00
|
|
|
auto p = Painter(this);
|
2024-01-16 06:34:49 +00:00
|
|
|
p.translate(_elementGeometry.topLeft());
|
2024-01-16 07:56:28 +00:00
|
|
|
if (!_lastFrameCache.isNull()) {
|
|
|
|
p.drawImage(0, 0, _lastFrameCache);
|
|
|
|
} else {
|
|
|
|
auto context = _theme->preparePaintContext(
|
|
|
|
_style.get(),
|
|
|
|
Rect(_element->currentSize()),
|
|
|
|
Rect(_element->currentSize()),
|
|
|
|
!window()->isActiveWindow());
|
|
|
|
context.outbg = _element->hasOutLayout();
|
|
|
|
_element->draw(p, context);
|
|
|
|
}
|
2024-01-09 11:49:35 +00:00
|
|
|
}
|
|
|
|
|
2024-01-16 06:34:49 +00:00
|
|
|
rpl::producer<QRect> GlobalViewportForWindow(
|
|
|
|
not_null<Window::SessionController*> controller) {
|
|
|
|
const auto delegate = controller->window().floatPlayerDelegate();
|
|
|
|
return rpl::single(rpl::empty) | rpl::then(
|
|
|
|
delegate->floatPlayerAreaUpdates()
|
|
|
|
) | rpl::map([=] {
|
|
|
|
auto section = (Media::Player::FloatSectionDelegate*)nullptr;
|
|
|
|
delegate->floatPlayerEnumerateSections([&](
|
|
|
|
not_null<Media::Player::FloatSectionDelegate*> check,
|
|
|
|
Window::Column column) {
|
|
|
|
if ((column == Window::Column::First && !section)
|
|
|
|
|| column == Window::Column::Second) {
|
|
|
|
section = check;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (section) {
|
|
|
|
const auto rect = section->floatPlayerAvailableRect();
|
|
|
|
if (rect.width() >= st::msgMinWidth) {
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return QRect();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-01-09 11:49:35 +00:00
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void ShowTTLMediaLayerWidget(
|
|
|
|
not_null<Window::SessionController*> controller,
|
|
|
|
not_null<HistoryItem*> item) {
|
|
|
|
const auto parent = controller->content();
|
|
|
|
const auto show = controller->uiShow();
|
2024-01-14 01:00:59 +00:00
|
|
|
auto preview = base::make_unique_q<PreviewWrap>(
|
|
|
|
parent,
|
|
|
|
item,
|
2024-01-16 06:34:49 +00:00
|
|
|
GlobalViewportForWindow(controller),
|
|
|
|
controller->adaptive().chatWideValue(),
|
2024-01-14 01:00:59 +00:00
|
|
|
Window::ChatThemeValueFromPeer(
|
|
|
|
controller,
|
|
|
|
item->history()->peer));
|
2024-01-09 11:49:35 +00:00
|
|
|
preview->closeRequests(
|
|
|
|
) | rpl::start_with_next([=] {
|
|
|
|
show->hideLayer();
|
|
|
|
}, preview->lifetime());
|
|
|
|
auto layer = std::make_unique<Editor::LayerWidget>(
|
|
|
|
parent,
|
|
|
|
std::move(preview));
|
|
|
|
layer->lifetime().add([] { ::Media::Player::instance()->stop(); });
|
|
|
|
base::install_event_filter(layer.get(), [=](not_null<QEvent*> e) {
|
|
|
|
if (e->type() == QEvent::KeyPress) {
|
|
|
|
const auto k = static_cast<QKeyEvent*>(e.get());
|
|
|
|
if (k->key() == Qt::Key_Escape) {
|
|
|
|
show->hideLayer();
|
|
|
|
}
|
|
|
|
return base::EventFilterResult::Cancel;
|
|
|
|
}
|
|
|
|
return base::EventFilterResult::Continue;
|
|
|
|
});
|
|
|
|
controller->showLayer(std::move(layer), Ui::LayerOption::KeepOther);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace ChatHelpers
|