Extracted ElementOverlayHost to deduplicate overlay management.

This commit is contained in:
23rd
2026-03-23 22:16:02 +03:00
parent 77529fb698
commit bb6a008309
7 changed files with 293 additions and 210 deletions
+2
View File
@@ -951,6 +951,8 @@ PRIVATE
history/view/history_view_draw_to_reply.h
history/view/history_view_add_poll_option.cpp
history/view/history_view_add_poll_option.h
history/view/history_view_element_overlay.cpp
history/view/history_view_element_overlay.h
history/view/history_view_element.cpp
history/view/history_view_element.h
history/view/history_view_emoji_interactions.cpp
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_reaction_preview.h"
#include "history/view/history_view_quick_action.h"
#include "history/view/history_view_add_poll_option.h"
#include "history/view/history_view_element_overlay.h"
#include "history/view/history_view_emoji_interactions.h"
#include "history/view/history_view_top_peers_selector.h"
#include "history/history_item_components.h"
@@ -368,7 +369,10 @@ HistoryInner::HistoryInner(
[=] { mouseActionUpdate(QCursor::pos()); setCursor(_cursor); },
[=] { return window()->isActiveWindow(); })
, _scrollDateCheck([this] { scrollDateCheck(); })
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); }) {
, _scrollDateHideTimer([this] { scrollDateHideByTimer(); })
, _overlayHost(std::make_unique<HistoryView::ElementOverlayHost>(
this,
[=](not_null<const Element*> view) { return itemTop(view); })) {
_history->delegateMixin()->setCurrent(this);
if (_migrated) {
_migrated->delegateMixin()->setCurrent(this);
@@ -1885,15 +1889,7 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
e->accept();
return; // ignore mouse press, that was hiding context menu
}
if (_addPollOptionWidget && _addPollOptionView) {
const auto top = itemTop(_addPollOptionView);
const auto viewRect = (top >= 0)
? QRect(0, top, width(), _addPollOptionView->height())
: QRect();
if (!viewRect.contains(e->pos())) {
hideAddPollOption();
}
}
_overlayHost->handleClickOutside(e->pos());
if (_middleClickAutoscroll.active()) {
_middleClickAutoscroll.stop();
e->accept();
@@ -2204,11 +2200,7 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
}
void HistoryInner::viewRemoved(not_null<const Element*> view) {
if (_addPollOptionView == view) {
_addPollOptionWidget = nullptr;
_addPollOptionView = nullptr;
_addPollOptionContext = FullMsgId();
}
_overlayHost->viewGone(view);
const auto refresh = [&](auto &saved) {
if (saved == view) {
const auto now = viewByItem(view->data());
@@ -3974,7 +3966,7 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
_visibleAreaTop,
_visibleAreaBottom);
updateAddPollOptionPosition();
_overlayHost->updatePosition();
}
bool HistoryInner::displayScrollDate() const {
@@ -4132,7 +4124,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
}
HistoryInner::~HistoryInner() {
hideAddPollOption();
_overlayHost->hide();
_aboutView = nullptr;
for (const auto &item : _animatedStickersPlayed) {
if (const auto view = item->mainView()) {
@@ -4309,91 +4301,45 @@ void HistoryInner::elementShowAddPollOption(
not_null<PollData*> poll,
FullMsgId context,
QRect optionRect) {
showAddPollOption(view, poll, context);
}
void HistoryInner::elementSubmitAddPollOption(FullMsgId context) {
if (_addPollOptionWidget && _addPollOptionContext == context) {
_addPollOptionWidget->triggerSubmit();
}
}
void HistoryInner::showAddPollOption(
not_null<Element*> view,
not_null<PollData*> poll,
FullMsgId context) {
if (_addPollOptionWidget && _addPollOptionContext == context) {
hideAddPollOption();
return;
}
hideAddPollOption();
_addPollOptionView = view;
_addPollOptionContext = context;
if (const auto media = view->media()) {
media->setAddOptionActive(true);
}
_addPollOptionWidget = base::make_unique_q<
auto widget = base::make_unique_q<
HistoryView::AddPollOptionWidget>(
this,
poll,
context,
_controller);
_addPollOptionWidget->submitted(
) | rpl::on_next([=] {
hideAddPollOption();
}, _addPollOptionWidget->lifetime());
_addPollOptionWidget->cancelled(
) | rpl::on_next([=] {
hideAddPollOption();
}, _addPollOptionWidget->lifetime());
updateAddPollOptionPosition();
_addPollOptionWidget->show();
const auto raw = widget.get();
_overlayHost->show(
view,
context,
std::move(widget),
rpl::merge(raw->submitted(), raw->cancelled()),
[raw](not_null<Element*> v, int top) {
const auto media = v->media();
if (!media) {
return false;
}
const auto mediaPos = v->mediaTopLeft();
const auto innerWidth = v->innerGeometry().width()
- st::msgPadding.left()
- st::msgPadding.right();
const auto rect = media->addOptionRect(innerWidth);
raw->updatePosition(
QPoint(
mediaPos.x() + rect.x(),
top + mediaPos.y() + rect.y()),
rect.width());
return true;
},
[](not_null<Element*> v, bool active) {
if (const auto media = v->media()) {
media->setAddOptionActive(active);
}
},
[raw] { raw->triggerSubmit(); });
}
void HistoryInner::hideAddPollOption() {
if (!_addPollOptionWidget) {
return;
}
if (_addPollOptionView) {
if (const auto media = _addPollOptionView->media()) {
media->setAddOptionActive(false);
}
}
_addPollOptionWidget = nullptr;
_addPollOptionView = nullptr;
_addPollOptionContext = FullMsgId();
}
void HistoryInner::updateAddPollOptionPosition() {
if (!_addPollOptionWidget || !_addPollOptionView) {
return;
}
const auto view = _addPollOptionView;
const auto top = itemTop(view);
if (top < 0) {
return;
}
const auto media = view->media();
if (!media) {
hideAddPollOption();
return;
}
const auto mediaPos = view->mediaTopLeft();
const auto innerWidth = view->innerGeometry().width()
- st::msgPadding.left()
- st::msgPadding.right();
const auto rect = media->addOptionRect(innerWidth);
_addPollOptionWidget->updatePosition(
QPoint(
mediaPos.x() + rect.x(),
top + mediaPos.y() + rect.y()),
rect.width());
void HistoryInner::elementSubmitAddPollOption(FullMsgId context) {
_overlayHost->triggerSubmit(context);
}
void HistoryInner::elementOpenPhoto(
@@ -37,7 +37,7 @@ struct StateRequest;
enum class CursorState : char;
enum class PointState : char;
enum class ElementChatMode : char;
class AddPollOptionWidget;
class ElementOverlayHost;
class EmptyPainter;
class Element;
class TranslateTracker;
@@ -262,13 +262,6 @@ protected:
void contextMenuEvent(QContextMenuEvent *e) override;
private:
void showAddPollOption(
not_null<Element*> view,
not_null<PollData*> poll,
FullMsgId context);
void hideAddPollOption();
void updateAddPollOptionPosition();
void onTouchSelect();
void onTouchScrollTimer();
void markReadMetricsStale();
@@ -610,9 +603,7 @@ private:
ClickHandlerPtr _scrollDateLink;
ClickHandlerPtr _forumThreadBarLink;
base::unique_qptr<HistoryView::AddPollOptionWidget> _addPollOptionWidget;
Element *_addPollOptionView = nullptr;
FullMsgId _addPollOptionContext;
std::unique_ptr<HistoryView::ElementOverlayHost> _overlayHost;
};
@@ -0,0 +1,139 @@
/*
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 "history/view/history_view_element_overlay.h"
#include "history/view/history_view_element.h"
#include "ui/rp_widget.h"
namespace HistoryView {
ElementOverlayHost::ElementOverlayHost(
not_null<QWidget*> container,
ItemTopFn itemTopFn)
: _container(container)
, _itemTopFn(std::move(itemTopFn)) {
}
void ElementOverlayHost::show(
not_null<Element*> view,
FullMsgId context,
base::unique_qptr<Ui::RpWidget> widget,
rpl::producer<> closeRequests,
PositionFn positionFn,
MediaStateFn mediaStateFn,
Fn<void()> submitFn) {
if (_widget && _context == context) {
hide();
return;
}
hide();
_view = view;
_context = context;
_positionFn = std::move(positionFn);
_mediaStateFn = std::move(mediaStateFn);
_submitFn = std::move(submitFn);
if (_mediaStateFn) {
_mediaStateFn(view, true);
}
_widget = std::move(widget);
std::move(
closeRequests
) | rpl::on_next([=] {
hide();
}, _closeLifetime);
updatePosition();
_widget->show();
}
void ElementOverlayHost::hide() {
if (!_widget) {
return;
}
if (_view && _mediaStateFn) {
_mediaStateFn(_view, false);
}
_widget = nullptr;
_view = nullptr;
_context = FullMsgId();
_positionFn = nullptr;
_mediaStateFn = nullptr;
_submitFn = nullptr;
_closeLifetime.destroy();
if (const auto callback = _hiddenCallback) {
callback();
}
}
void ElementOverlayHost::viewGone(not_null<const Element*> view) {
if (_view != view.get()) {
return;
}
_widget = nullptr;
_view = nullptr;
_context = FullMsgId();
_positionFn = nullptr;
_mediaStateFn = nullptr;
_submitFn = nullptr;
_closeLifetime.destroy();
if (const auto callback = _hiddenCallback) {
callback();
}
}
void ElementOverlayHost::updatePosition() {
if (!_widget || !_view || !_positionFn) {
return;
}
const auto top = _itemTopFn(_view);
if (top < 0) {
return;
}
if (!_positionFn(_view, top)) {
hide();
}
}
bool ElementOverlayHost::handleClickOutside(QPoint clickPos) {
if (!_widget || !_view) {
return false;
}
const auto top = _itemTopFn(_view);
const auto viewRect = (top >= 0)
? QRect(0, top, _container->width(), _view->height())
: QRect();
if (!viewRect.contains(clickPos)) {
hide();
return true;
}
return false;
}
void ElementOverlayHost::triggerSubmit(FullMsgId context) {
if (_widget && _context == context && _submitFn) {
_submitFn();
}
}
void ElementOverlayHost::setHiddenCallback(Fn<void()> callback) {
_hiddenCallback = std::move(callback);
}
bool ElementOverlayHost::active() const {
return _widget != nullptr;
}
FullMsgId ElementOverlayHost::context() const {
return _context;
}
} // namespace HistoryView
@@ -0,0 +1,64 @@
/*
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/unique_qptr.h"
#include "data/data_messages.h"
namespace Ui {
class RpWidget;
} // namespace Ui
namespace HistoryView {
class Element;
class ElementOverlayHost final {
public:
using ItemTopFn = Fn<int(not_null<const Element*>)>;
using PositionFn = Fn<bool(not_null<Element*>, int itemTop)>;
using MediaStateFn = Fn<void(not_null<Element*>, bool active)>;
explicit ElementOverlayHost(
not_null<QWidget*> container,
ItemTopFn itemTopFn);
void show(
not_null<Element*> view,
FullMsgId context,
base::unique_qptr<Ui::RpWidget> widget,
rpl::producer<> closeRequests,
PositionFn positionFn,
MediaStateFn mediaStateFn = nullptr,
Fn<void()> submitFn = nullptr);
void hide();
void viewGone(not_null<const Element*> view);
void updatePosition();
bool handleClickOutside(QPoint clickPos);
void triggerSubmit(FullMsgId context);
void setHiddenCallback(Fn<void()> callback);
[[nodiscard]] bool active() const;
[[nodiscard]] FullMsgId context() const;
private:
const not_null<QWidget*> _container;
const ItemTopFn _itemTopFn;
base::unique_qptr<Ui::RpWidget> _widget;
Element *_view = nullptr;
FullMsgId _context;
PositionFn _positionFn;
MediaStateFn _mediaStateFn;
Fn<void()> _submitFn;
Fn<void()> _hiddenCallback;
rpl::lifetime _closeLifetime;
};
} // namespace HistoryView
@@ -30,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_translate_tracker.h"
#include "history/view/history_view_read_metrics_tracker.h"
#include "history/view/history_view_add_poll_option.h"
#include "history/view/history_view_element_overlay.h"
#include "data/data_poll.h"
#include "history/view/history_view_top_peers_selector.h"
#include "history/view/history_view_quick_action.h"
@@ -439,9 +440,15 @@ ListWidget::ListWidget(
[=](int d) { _delegate->listScrollTo(_visibleTop + d, false); },
[=](const QCursor &cursor) { setCursor(cursor); },
[=] { mouseActionUpdate(QCursor::pos()); setCursor(_cursor); },
[=] { return window()->isActiveWindow(); }) {
[=] { return window()->isActiveWindow(); })
, _overlayHost(std::make_unique<ElementOverlayHost>(
this,
[=](not_null<const Element*> view) { return itemTop(view); })) {
setAttribute(Qt::WA_AcceptTouchEvents);
setMouseTracking(true);
_overlayHost->setHiddenCallback([=] {
_delegate->listWindowSetInnerFocus();
});
if (_readMetricsTracker) {
Core::App().inAppKeyPressed(
) | rpl::on_next([=] {
@@ -1176,7 +1183,7 @@ void ListWidget::visibleTopBottomUpdated(
_applyUpdatedScrollState.call();
_emojiInteractions->visibleAreaUpdated(_visibleTop, _visibleBottom);
updateAddPollOptionPosition();
_overlayHost->updatePosition();
}
void ListWidget::applyUpdatedScrollState() {
@@ -1897,91 +1904,44 @@ void ListWidget::elementShowAddPollOption(
not_null<PollData*> poll,
FullMsgId context,
QRect optionRect) {
showAddPollOption(view, poll, context);
}
void ListWidget::elementSubmitAddPollOption(FullMsgId context) {
if (_addPollOptionWidget && _addPollOptionContext == context) {
_addPollOptionWidget->triggerSubmit();
}
}
void ListWidget::showAddPollOption(
not_null<Element*> view,
not_null<PollData*> poll,
FullMsgId context) {
if (_addPollOptionWidget && _addPollOptionContext == context) {
hideAddPollOption();
return;
}
hideAddPollOption();
_addPollOptionView = view;
_addPollOptionContext = context;
if (const auto media = view->media()) {
media->setAddOptionActive(true);
}
_addPollOptionWidget = base::make_unique_q<AddPollOptionWidget>(
auto widget = base::make_unique_q<AddPollOptionWidget>(
this,
poll,
context,
controller());
_addPollOptionWidget->submitted(
) | rpl::on_next([=] {
hideAddPollOption();
}, _addPollOptionWidget->lifetime());
_addPollOptionWidget->cancelled(
) | rpl::on_next([=] {
hideAddPollOption();
}, _addPollOptionWidget->lifetime());
updateAddPollOptionPosition();
_addPollOptionWidget->show();
const auto raw = widget.get();
_overlayHost->show(
view,
context,
std::move(widget),
rpl::merge(raw->submitted(), raw->cancelled()),
[raw](not_null<Element*> v, int top) {
const auto media = v->media();
if (!media) {
return false;
}
const auto mediaPos = v->mediaTopLeft();
const auto innerWidth = v->innerGeometry().width()
- st::msgPadding.left()
- st::msgPadding.right();
const auto rect = media->addOptionRect(innerWidth);
raw->updatePosition(
QPoint(
mediaPos.x() + rect.x(),
top + mediaPos.y() + rect.y()),
rect.width());
return true;
},
[](not_null<Element*> v, bool active) {
if (const auto media = v->media()) {
media->setAddOptionActive(active);
}
},
[raw] { raw->triggerSubmit(); });
}
void ListWidget::hideAddPollOption() {
if (!_addPollOptionWidget) {
return;
}
if (_addPollOptionView) {
if (const auto media = _addPollOptionView->media()) {
media->setAddOptionActive(false);
}
}
_addPollOptionWidget = nullptr;
_addPollOptionView = nullptr;
_addPollOptionContext = FullMsgId();
_delegate->listWindowSetInnerFocus();
}
void ListWidget::updateAddPollOptionPosition() {
if (!_addPollOptionWidget || !_addPollOptionView) {
return;
}
const auto view = _addPollOptionView;
const auto top = itemTop(view);
if (top < 0) {
return;
}
const auto media = view->media();
if (!media) {
hideAddPollOption();
return;
}
const auto mediaPos = view->mediaTopLeft();
const auto innerWidth = view->innerGeometry().width()
- st::msgPadding.left()
- st::msgPadding.right();
const auto rect = media->addOptionRect(innerWidth);
_addPollOptionWidget->updatePosition(
QPoint(
mediaPos.x() + rect.x(),
top + mediaPos.y() + rect.y()),
rect.width());
void ListWidget::elementSubmitAddPollOption(FullMsgId context) {
_overlayHost->triggerSubmit(context);
}
void ListWidget::elementOpenPhoto(
@@ -3164,15 +3124,7 @@ void ListWidget::mousePressEvent(QMouseEvent *e) {
e->accept();
return; // ignore mouse press, that was hiding context menu
}
if (_addPollOptionWidget && _addPollOptionView) {
const auto top = itemTop(_addPollOptionView);
const auto viewRect = (top >= 0)
? QRect(0, top, width(), _addPollOptionView->height())
: QRect();
if (!viewRect.contains(e->pos())) {
hideAddPollOption();
}
}
_overlayHost->handleClickOutside(e->pos());
if (_middleClickAutoscroll.active()) {
_middleClickAutoscroll.stop();
e->accept();
@@ -4461,9 +4413,7 @@ void ListWidget::showItemHighlight(not_null<HistoryItem*> item) {
}
void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
if (_addPollOptionView == was) {
hideAddPollOption();
}
_overlayHost->viewGone(was);
if (_visibleTopItem == was) _visibleTopItem = now;
if (_scrollDateLastItem == was) _scrollDateLastItem = now;
if (_overElement == was) _overElement = now;
@@ -66,7 +66,7 @@ namespace HistoryView {
struct TextState;
struct StateRequest;
class AddPollOptionWidget;
class ElementOverlayHost;
class EmojiInteractions;
class TranslateTracker;
class ReadMetricsTracker;
@@ -547,13 +547,6 @@ private:
Deselecting,
};
void showAddPollOption(
not_null<Element*> view,
not_null<PollData*> poll,
FullMsgId context);
void hideAddPollOption();
void updateAddPollOptionPosition();
void onTouchSelect();
void onTouchScrollTimer();
void markReadMetricsStale();
@@ -907,9 +900,7 @@ private:
rpl::event_stream<FullMsgId> _requestedToShowMessage;
rpl::event_stream<not_null<QKeyEvent*>> _scrollKeyEvents;
base::unique_qptr<AddPollOptionWidget> _addPollOptionWidget;
Element *_addPollOptionView = nullptr;
FullMsgId _addPollOptionContext;
std::unique_ptr<ElementOverlayHost> _overlayHost;
rpl::lifetime _viewerLifetime;