Added ability to swipe-to-back to history widget.

This commit is contained in:
23rd 2025-02-17 15:13:56 +03:00
parent 17a10cf6bb
commit 540fa0e669
8 changed files with 184 additions and 11 deletions

View file

@ -440,7 +440,7 @@ HistoryInner::HistoryInner(
}, _scroll->lifetime());
setupSharingDisallowed();
setupSwipeReply();
setupSwipeReplyAndBack();
}
void HistoryInner::reactionChosen(const ChosenReaction &reaction) {
@ -523,12 +523,30 @@ void HistoryInner::setupSharingDisallowed() {
}, lifetime());
}
void HistoryInner::setupSwipeReply() {
if (_peer && _peer->isChannel() && !_peer->isMegagroup()) {
void HistoryInner::setupSwipeReplyAndBack() {
if (!_peer) {
return;
}
const auto peer = _peer;
HistoryView::SetupSwipeHandler(this, _scroll, [=, history = _history](
HistoryView::ChatPaintGestureHorizontalData data) {
if (data.translation > 0) {
if (!_swipeBackData.callback) {
_swipeBackData = HistoryView::SetupSwipeBack(
_widget,
[=]() -> std::pair<QColor, QColor> {
auto context = preparePaintContext({});
return {
context.st->msgServiceBg()->c,
context.st->msgServiceFg()->c,
};
});
}
_swipeBackData.callback(data);
return;
} else if (_swipeBackData.lifetime) {
_swipeBackData = {};
}
const auto changed = (_gestureHorizontal.msgBareId != data.msgBareId)
|| (_gestureHorizontal.translation != data.translation)
|| (_gestureHorizontal.reachRatio != data.reachRatio);
@ -541,9 +559,18 @@ void HistoryInner::setupSwipeReply() {
repaintItem(item);
}
}
}, [=, show = _controller->uiShow()](int cursorTop) {
}, [=, show = _controller->uiShow()](
int cursorTop,
Qt::LayoutDirection direction) {
if (direction == Qt::RightToLeft) {
return HistoryView::SwipeHandlerFinishData{
.callback = [=] { _controller->showBackFromStack(); },
.msgBareId = HistoryView::kMsgBareIdSwipeBack,
};
}
auto result = HistoryView::SwipeHandlerFinishData();
if (inSelectionMode().inSelectionMode) {
if (inSelectionMode().inSelectionMode
|| (peer->isChannel() && !peer->isMegagroup())) {
return result;
}
enumerateItems<EnumItemsDirection::BottomToTop>([&](

View file

@ -428,7 +428,7 @@ private:
void reactionChosen(const ChosenReaction &reaction);
void setupSharingDisallowed();
void setupSwipeReply();
void setupSwipeReplyAndBack();
[[nodiscard]] bool hasCopyRestriction(HistoryItem *item = nullptr) const;
[[nodiscard]] bool hasCopyMediaRestriction(
not_null<HistoryItem*> item) const;
@ -544,6 +544,7 @@ private:
base::Timer _touchScrollTimer;
HistoryView::ChatPaintGestureHorizontalData _gestureHorizontal;
HistoryView::SwipeBackResult _swipeBackData;
// _menu must be destroyed before _whoReactedMenuLifetime.
rpl::lifetime _whoReactedMenuLifetime;

View file

@ -13,9 +13,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/event_filter.h"
#include "history/history_view_swipe_data.h"
#include "ui/chat/chat_style.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/ui_utility.h"
#include "ui/widgets/elastic_scroll.h"
#include "ui/widgets/scroll_area.h"
#include "styles/style_chat.h"
#include <QtWidgets/QApplication>
@ -30,7 +33,7 @@ void SetupSwipeHandler(
not_null<Ui::RpWidget*> widget,
not_null<Ui::ScrollArea*> scroll,
Fn<void(ChatPaintGestureHorizontalData)> update,
Fn<SwipeHandlerFinishData(int)> generateFinishByTop,
Fn<SwipeHandlerFinishData(int, Qt::LayoutDirection)> generateFinish,
rpl::producer<bool> dontStart) {
constexpr auto kThresholdWidth = 50;
constexpr auto kMaxRatio = 1.5;
@ -145,8 +148,9 @@ void SetupSwipeHandler(
state->startAt = args.position;
state->delta = QPointF();
state->cursorTop = widget->mapFromGlobal(args.globalCursor).y();
state->finishByTopData = generateFinishByTop(
state->cursorTop);
state->finishByTopData = generateFinish(
state->cursorTop,
state->direction.value_or(Qt::RightToLeft));
if (!state->finishByTopData.callback) {
setOrientation(Qt::Vertical);
}
@ -279,4 +283,127 @@ void SetupSwipeHandler(
base::install_event_filter(widget, filter));
}
SwipeBackResult SetupSwipeBack(
not_null<Ui::RpWidget*> widget,
Fn<std::pair<QColor, QColor>()> colors) {
struct State {
base::unique_qptr<Ui::RpWidget> back;
ChatPaintGestureHorizontalData data;
};
constexpr auto kMaxRightOffset = 0.5;
constexpr auto kMaxLeftOffset = 0.8;
constexpr auto kIdealSize = 100;
const auto maxOffset = st::swipeBackSize * kMaxRightOffset;
const auto sizeRatio = st::swipeBackSize
/ style::ConvertFloatScale(kIdealSize);
auto lifetime = rpl::lifetime();
const auto state = lifetime.make_state<State>();
const auto paintCallback = [=] {
const auto [bg, fg] = colors();
const auto arrowPen = QPen(
fg,
st::lineWidth * 3 * sizeRatio,
Qt::SolidLine,
Qt::RoundCap);
return [=] {
auto p = QPainter(state->back);
constexpr auto kBouncePart = 0.25;
constexpr auto kStrokeWidth = 2.;
constexpr auto kWaveWidth = 10.;
const auto ratio = std::min(state->data.ratio, 1.);
const auto reachRatio = state->data.reachRatio;
const auto rect = state->back->rect()
- Margins(state->back->width() / 4);
const auto center = rect::center(rect);
const auto strokeWidth = style::ConvertFloatScale(kStrokeWidth)
* sizeRatio;
const auto reachScale = std::clamp(
(reachRatio > kBouncePart)
? (kBouncePart * 2 - reachRatio)
: reachRatio,
0.,
1.);
auto pen = QPen(bg);
pen.setWidthF(strokeWidth - (1. * (reachScale / kBouncePart)));
const auto arcRect = rect - Margins(strokeWidth);
auto hq = PainterHighQualityEnabler(p);
p.setOpacity(ratio);
if (reachScale) {
const auto scale = (1. + 1. * reachScale);
p.translate(center);
p.scale(scale, scale);
p.translate(-center);
}
{
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(rect);
p.drawEllipse(rect);
p.setPen(arrowPen);
p.setBrush(Qt::NoBrush);
const auto halfSize = rect.width() / 2;
const auto arrowSize = halfSize / 2;
const auto arrowHalf = arrowSize / 2;
const auto arrowX = st::swipeBackSize / 8
+ rect.x()
+ halfSize
- arrowHalf;
const auto arrowY = rect.y() + halfSize;
auto arrowPath = QPainterPath();
arrowPath.moveTo(arrowX + arrowSize, arrowY);
arrowPath.lineTo(arrowX, arrowY);
arrowPath.lineTo(arrowX + arrowHalf, arrowY - arrowHalf);
arrowPath.moveTo(arrowX, arrowY);
arrowPath.lineTo(arrowX + arrowHalf, arrowY + arrowHalf);
p.drawPath(arrowPath);
}
if (reachRatio) {
p.setPen(pen);
p.setBrush(Qt::NoBrush);
const auto w = style::ConvertFloatScale(kWaveWidth)
* sizeRatio;
p.setOpacity(ratio - reachRatio);
p.drawArc(
arcRect + Margins(reachRatio * reachRatio * w),
arc::kQuarterLength,
arc::kFullLength);
}
};
};
const auto callback = ([=](ChatPaintGestureHorizontalData data) {
const auto ratio = std::min(1.0, data.ratio);
state->data = std::move(data);
if (ratio > 0) {
if (!state->back) {
state->back = base::make_unique_q<Ui::RpWidget>(widget);
const auto raw = state->back.get();
raw->paintRequest(
) | rpl::start_with_next(paintCallback(), raw->lifetime());
raw->setAttribute(Qt::WA_TransparentForMouseEvents);
raw->resize(Size(st::swipeBackSize));
raw->show();
raw->raise();
}
state->back->moveToLeft(
anim::interpolate(
-st::swipeBackSize * kMaxLeftOffset,
maxOffset - st::swipeBackSize,
ratio),
(widget->height() - state->back->height()) / 2);
state->back->update();
} else if (state->back) {
state->back = nullptr;
}
});
return { std::move(lifetime), std::move(callback) };
}
} // namespace HistoryView

View file

@ -15,6 +15,9 @@ class ScrollArea;
namespace HistoryView {
struct ChatPaintGestureHorizontalData;
struct SwipeBackResult;
constexpr auto kMsgBareIdSwipeBack = std::numeric_limits<int64>::max() - 77;
struct SwipeHandlerFinishData {
Fn<void(void)> callback;
@ -25,7 +28,11 @@ void SetupSwipeHandler(
not_null<Ui::RpWidget*> widget,
not_null<Ui::ScrollArea*> scroll,
Fn<void(ChatPaintGestureHorizontalData)> update,
Fn<SwipeHandlerFinishData(int)> generateFinishByTop,
Fn<SwipeHandlerFinishData(int, Qt::LayoutDirection)> generateFinishByTop,
rpl::producer<bool> dontStart = nullptr);
SwipeBackResult SetupSwipeBack(
not_null<Ui::RpWidget*> widget,
Fn<std::pair<QColor, QColor>()> colors);
} // namespace HistoryView

View file

@ -17,4 +17,9 @@ struct ChatPaintGestureHorizontalData {
int cursorTop = 0;
};
struct SwipeBackResult final {
rpl::lifetime lifetime;
Fn<void(ChatPaintGestureHorizontalData)> callback;
};
} // namespace HistoryView

View file

@ -58,6 +58,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/qt_signal_producer.h"
#include "base/qt/qt_key_modifiers.h"
#include "base/unixtime.h"
#include "history/history_view_swipe.h"
#include "history/history_view_swipe_data.h"
#include "base/call_delayed.h"
#include "data/business/data_shortcut_messages.h"
#include "data/components/credits.h"

View file

@ -904,7 +904,9 @@ void RepliesWidget::setupSwipeReply() {
_history->owner().requestItemRepaint(item);
}
}
}, [=, show = controller()->uiShow()](int cursorTop) {
}, [=, show = controller()->uiShow()](
int cursorTop,
Qt::LayoutDirection direction) {
auto result = HistoryView::SwipeHandlerFinishData();
if (_inner->elementInSelectionMode(nullptr).inSelectionMode) {
return result;

View file

@ -1245,3 +1245,5 @@ newPeerUserpics: GroupCallUserpics {
}
newPeerUserpicsPadding: margins(0px, 3px, 0px, 0px);
newPeerWidth: 320px;
swipeBackSize: 150px;