Added simple sending animation for text messages to history widget.

This commit is contained in:
23rd
2025-10-05 15:51:55 +03:00
parent b6000bc1a8
commit 443053b927
6 changed files with 72 additions and 22 deletions
@@ -576,6 +576,7 @@ void InitMessageFieldGeometry(not_null<Ui::InputField*> field) {
st::historySendSize.height() - 2 * st::historySendPadding);
field->setMaxHeight(st::historyComposeFieldMaxHeight);
// st::messageSendingAnimationTextFromOffset.
field->setDocumentMargin(4.);
field->setAdditionalMargin(style::ConvertScale(4) - 4);
}
@@ -4752,10 +4752,20 @@ void HistoryWidget::send(Api::SendOptions options) {
return;
}
const auto nextLocalMessageId = session().data().nextLocalMessageId();
if (HasSendText(_field) && message.webPage.url.isEmpty()) {
controller()->sendingAnimation().appendSending({
.type = Ui::MessageSendingAnimationFrom::Type::Text,
.localId = nextLocalMessageId,
.globalStartGeometry = _field->mapToGlobal(Rect(_field->size())),
});
}
// Just a flag not to drop reply info if we're not sending anything.
_justMarkingAsRead = !HasSendText(_field)
&& message.webPage.url.isEmpty();
session().api().sendMessage(std::move(message));
session().api().sendMessage(std::move(message), nextLocalMessageId);
_justMarkingAsRead = false;
clearFieldText();
@@ -7141,7 +7151,6 @@ void HistoryWidget::startMessageSendingAnimation(
return;
}
Assert(item->mainView() != nullptr);
Assert(item->mainView()->media() != nullptr);
auto globalEndTopLeft = rpl::merge(
_scroll->innerResizes() | rpl::to_empty,
+2
View File
@@ -1396,6 +1396,8 @@ chatTabsOutlineHorizontal: ChatTabsOutline {
chatTabsOutlineVertical: ChatTabsOutline(chatTabsOutlineHorizontal) {
}
messageSendingAnimationTextFromOffset: point(-6px, 9px);
suggestPriceBox: Box(defaultBox) {
buttonPadding: margins(22px, 22px, 22px, 22px);
buttonHeight: 42px;
+1 -1
View File
@@ -243,7 +243,7 @@ struct ChatPaintContext {
};
// This is supported only in unwrapped media for now.
// This is supported only in unwrapped media and text messages for now.
enum class SkipDrawingParts {
None,
Content,
@@ -15,6 +15,7 @@ struct MessageSendingAnimationFrom {
Sticker,
Gif,
Emoji,
Text,
};
Type type = Type::None;
std::optional<MsgId> localId;
@@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/effects/animation_value_f.h"
#include "ui/effects/animations.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/rp_widget.h"
#include "window/window_session_controller.h"
#include "styles/style_chat.h"
@@ -58,6 +59,7 @@ private:
const not_null<Window::SessionController*> _controller;
const bool _crop;
const bool _isText;
MessageSendingAnimationController::SendingInfoTo _toInfo;
QRect _from;
QPoint _to;
@@ -84,10 +86,18 @@ Content::Content(
: RpWidget(parent)
, _controller(controller)
, _crop(fromInfo.crop)
, _isText(fromInfo.type == MessageSendingAnimationFrom::Type::Text)
, _toInfo(std::move(to))
, _from(parent->mapFromGlobal(fromInfo.globalStartGeometry))
, _innerContentRect(maybeView()->media()->contentRectForReactions())
, _minScale(float64(_from.height()) / _innerContentRect.height()) {
, _innerContentRect(_isText
? [&] {
const auto g = maybeView()->innerGeometry();
return Rect(g.size()) + QMargins(0, g.top(), 0, 0);
}()
: maybeView()->media()->contentRectForReactions())
, _minScale(!_isText
? float64(_from.height()) / _innerContentRect.height()
: 1.) {
Expects(_toInfo.view != nullptr);
Expects(_toInfo.paintContext != nullptr);
@@ -117,24 +127,41 @@ Content::Content(
auto animationCallback = [=](float64 value) {
auto resultFrom = rect();
resultFrom.moveCenter(_from.center());
if (_isText) {
resultFrom.moveTo(
style::RightToLeft()
? (rect::right(_from) - resultFrom.width())
: _from.left(),
_from.y());
// Calculating relative values for all possible cases
// is too complex, so we manually set the position
// for the simplest case.
resultFrom.translate(st::messageSendingAnimationTextFromOffset);
} else {
resultFrom.moveCenter(_from.center());
}
const auto resultTo = _to
+ innerGeometry.topLeft()
+ _innerContentRect.topLeft();
const auto resultTo = _isText
? _to + innerGeometry.topLeft()
: _to + innerGeometry.topLeft() + _innerContentRect.topLeft();
const auto x = anim::interpolate(resultFrom.x(), resultTo.x(), value);
const auto y = anim::interpolate(resultFrom.y(), resultTo.y(), value);
moveToLeft(x, y);
update();
if (!_isText) {
// Text-only messages are drawing only in _bubble.widget.
moveToLeft(x, y);
update();
} else {
setUpdatesEnabled(false);
}
if ((value > kSurroundingProgress)
if ((value > kSurroundingProgress || _isText)
&& !_surrounding
&& !_bubble.widget) {
const auto currentView = maybeView();
if (!checkView(currentView)) {
return;
}
if (currentView->hasBubble()) {
if (_isText || currentView->hasBubble()) {
createBubble();
} else {
createSurrounding();
@@ -280,12 +307,12 @@ void Content::createSurrounding() {
const auto alpha = (divider - revProgress) / divider;
p.setOpacity(alpha);
const auto scale = anim::interpolateF(_minScale, 1., progress);
const auto scale = anim::interpolateF(_minScale, 1., progress);
p.translate(
revProgress * OffsetMid(size.width() + offset.x(), _minScale),
revProgress * OffsetMid(size.height() + offset.y(), _minScale));
p.scale(scale, scale);
p.translate(
revProgress * OffsetMid(size.width() + offset.x(), _minScale),
revProgress * OffsetMid(size.height() + offset.y(), _minScale));
p.scale(scale, scale);
const auto currentView = maybeView();
if (!checkView(currentView)) {
@@ -312,7 +339,11 @@ void Content::createBubble() {
const auto tailWidth = st::historyBubbleTailOutLeft.width();
_bubble.offsetFromContent = QPoint(
currentView->hasOutLayout() ? 0 : tailWidth,
(currentView->hasOutLayout()
&& (currentView->delegate()->elementChatMode()
!= HistoryView::ElementChatMode::Wide))
? 0
: tailWidth,
innerGeometry.y());
const auto scaleOffset = QPoint(0, innerGeometry.y());
@@ -323,8 +354,11 @@ void Content::createBubble() {
|| currentView->data()->externalReply();
_bubble.widget->resize(innerGeometry.size()
+ QSize(
currentView->hasOutLayout() ? tailWidth : 0,
hasCommentsButton ? innerGeometry.y() : 0));
(currentView->hasOutLayout() ? tailWidth : 0)
+ (_isText && currentView->data()->isPost()
? rect::m::sum::h(st::msgPadding)
: 0),
(hasCommentsButton || _isText) ? innerGeometry.y() : 0));
_bubble.widget->show();
_bubble.widget->stackUnder(this);
@@ -338,7 +372,7 @@ void Content::createBubble() {
const auto progress = _animation.value(0.);
const auto revProgress = 1. - progress;
const auto divider = 1. - kSurroundingProgress;
const auto divider = 1. - (_isText ? 0. : kSurroundingProgress);
const auto alpha = (divider - revProgress) / divider;
p.setOpacity(alpha);
@@ -443,6 +477,7 @@ bool MessageSendingAnimationController::checkExpectedType(
const auto type = it->second.type;
const auto isSticker = type == MessageSendingAnimationFrom::Type::Sticker;
const auto isGif = type == MessageSendingAnimationFrom::Type::Gif;
const auto isText = type == MessageSendingAnimationFrom::Type::Text;
if (isSticker || isGif) {
if (item->emptyText()) {
if (const auto media = item->media()) {
@@ -454,6 +489,8 @@ bool MessageSendingAnimationController::checkExpectedType(
}
}
}
} else if (isText && !item->media()) {
return true;
}
_itemSendPending.erase(it);
return false;