Improve reactions position and colors.

This commit is contained in:
John Preston 2021-12-28 16:46:43 +03:00
parent 38c296b69f
commit 4228557722
17 changed files with 195 additions and 55 deletions

View file

@ -746,7 +746,7 @@ bool HistoryItem::suggestDeleteAllReport() const {
}
bool HistoryItem::canReact() const {
return isRegular();
return isRegular() && !isService();
}
void HistoryItem::addReaction(const QString &reaction) {

View file

@ -359,15 +359,23 @@ QSize Message::performCountOptimalSize() {
}
minHeight = hasVisibleText() ? item->_text.minHeight() : 0;
if (reactionsInBubble) {
accumulate_max(maxWidth, std::min(
st::msgMaxWidth,
(st::msgPadding.left()
+ _reactions->maxWidth()
+ st::msgPadding.right())));
const auto reactionsMaxWidth = st::msgPadding.left()
+ _reactions->maxWidth()
+ st::msgPadding.right();
accumulate_max(
maxWidth,
std::min(st::msgMaxWidth, reactionsMaxWidth));
if (!mediaDisplayed) {
minHeight += st::mediaInBubbleSkip;
}
minHeight += _reactions->minHeight();
if (maxWidth >= reactionsMaxWidth) {
minHeight += _reactions->minHeight();
} else {
const auto widthForReactions = maxWidth
- st::msgPadding.left()
- st::msgPadding.right();
minHeight += _reactions->resizeGetHeight(widthForReactions);
}
}
if (!mediaOnBottom) {
minHeight += st::msgPadding.bottom();
@ -457,13 +465,6 @@ QSize Message::performCountOptimalSize() {
maxWidth = st::msgMinWidth;
minHeight = 0;
}
if (_reactions && !reactionsInBubble) {
// if we have a text bubble we can resize it to fit the keyboard
// but if we have only media we don't do that
if (hasVisibleText()) {
accumulate_max(maxWidth, _reactions->maxWidth());
}
}
if (const auto markup = item->inlineReplyMarkup()) {
if (!markup->inlineKeyboard) {
markup->inlineKeyboard = std::make_unique<ReplyKeyboard>(
@ -601,8 +602,11 @@ void Message::draw(Painter &p, const PaintContext &context) const {
if (_reactions && !reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().x()
: 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
p.translate(reactionsPosition);
_reactions->paint(p, context, g.width(), context.clip.translated(-reactionsPosition));
p.translate(-reactionsPosition);
@ -1250,7 +1254,9 @@ TextState Message::textState(
return result;
}
const auto bubble = drawBubble();
const auto reactionsInBubble = _reactions && embedReactionsInBubble();
const auto mediaDisplayed = media && media->isDisplayed();
auto keyboard = item->inlineReplyKeyboard();
auto keyboardHeight = 0;
if (keyboard) {
@ -1260,17 +1266,19 @@ TextState Message::textState(
if (_reactions && !reactionsInBubble) {
const auto reactionsHeight = st::mediaInBubbleSkip + _reactions->height();
const auto reactionsLeft = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().x()
: 0;
g.setHeight(g.height() - reactionsHeight);
const auto reactionsPosition = QPoint(g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
const auto reactionsPosition = QPoint(reactionsLeft + g.left(), g.top() + g.height() + st::mediaInBubbleSkip);
if (_reactions->getState(point - reactionsPosition, &result)) {
return result;
}
}
if (drawBubble()) {
if (bubble) {
const auto inBubble = g.contains(point);
auto entry = logEntryOriginal();
auto mediaDisplayed = media && media->isDisplayed();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
@ -1807,17 +1815,39 @@ TextSelection Message::adjustSelection(
Reactions::ButtonParameters Message::reactionButtonParameters(
QPoint position,
const TextState &reactionState) const {
auto result = Reactions::ButtonParameters{ .context = data()->fullId() };
const auto outbg = result.outbg = hasOutLayout();
using namespace Reactions;
auto result = ButtonParameters{ .context = data()->fullId() };
const auto outbg = hasOutLayout();
result.style = (!_comments && !embedReactionsInBubble())
? ButtonStyle::Service
: outbg
? ButtonStyle::Outgoing
: ButtonStyle::Incoming;
const auto geometry = countGeometry();
result.pointer = position;
const auto onTheLeft = (outbg && !delegate()->elementIsChatWide());
const auto leftAdd = onTheLeft ? 0 : geometry.width();
result.center = geometry.topLeft() + (onTheLeft
? (QPoint(0, geometry.height()) + QPoint(
const auto keyboard = data()->inlineReplyKeyboard();
const auto keyboardHeight = keyboard
? (st::msgBotKbButton.margin + keyboard->naturalHeight())
: 0;
const auto reactionsHeight = (_reactions && !embedReactionsInBubble())
? (st::mediaInBubbleSkip + _reactions->height())
: 0;
const auto innerHeight = geometry.height()
- keyboardHeight
- reactionsHeight;
const auto contentRect = (result.style == ButtonStyle::Service
&& !drawBubble())
? media()->contentRectForReactionButton().translated(
geometry.topLeft())
: geometry;
result.center = contentRect.topLeft() + (onTheLeft
? (QPoint(0, innerHeight) + QPoint(
-st::reactionCornerCenter.x(),
st::reactionCornerCenter.y()))
: (QPoint(geometry.width(), geometry.height())
: (QPoint(contentRect.width(), innerHeight)
+ st::reactionCornerCenter));
if (reactionState.itemId != result.context) {
const auto top = marginTop();
@ -2739,7 +2769,11 @@ int Message::resizeContentGetHeight(int newWidth) {
newHeight = 0;
}
if (_reactions && !reactionsInBubble) {
newHeight += st::mediaInBubbleSkip + _reactions->resizeGetHeight(contentWidth);
const auto reactionsWidth = (!bubble && mediaDisplayed)
? media->contentRectForReactionButton().width()
: contentWidth;
newHeight += st::mediaInBubbleSkip
+ _reactions->resizeGetHeight(reactionsWidth);
}
if (const auto keyboard = item->inlineReplyKeyboard()) {

View file

@ -25,6 +25,7 @@ constexpr auto kActivateDuration = crl::time(150);
constexpr auto kExpandDuration = crl::time(150);
constexpr auto kInCacheIndex = 0;
constexpr auto kOutCacheIndex = 1;
constexpr auto kServiceCacheIndex = 2;
constexpr auto kShadowCacheIndex = 0;
constexpr auto kEmojiCacheIndex = 1;
constexpr auto kMaskCacheIndex = 2;
@ -86,8 +87,8 @@ Button::Button(
Button::~Button() = default;
bool Button::outbg() const {
return _outbg;
ButtonStyle Button::style() const {
return _style;
}
bool Button::isHidden() const {
@ -150,8 +151,8 @@ void Button::applyParameters(
? State::Active
: State::Shown;
applyState(state, update);
if (_outbg != parameters.outbg) {
_outbg = parameters.outbg;
if (_style != parameters.style) {
_style = parameters.style;
if (update) {
update(_geometry);
}
@ -264,12 +265,12 @@ Manager::Manager(
QRect({}, _outer).center() - _innerActive.center());
const auto ratio = style::DevicePixelRatio();
_cacheInOut = QImage(
_outer.width() * 2 * ratio,
_cacheInOutService = QImage(
_outer.width() * 3 * ratio,
_outer.height() * kFramesCount * ratio,
QImage::Format_ARGB32_Premultiplied);
_cacheInOut.setDevicePixelRatio(ratio);
_cacheInOut.fill(Qt::transparent);
_cacheInOutService.setDevicePixelRatio(ratio);
_cacheInOutService.fill(Qt::transparent);
_cacheParts = QImage(
_outer.width() * kCacheColumsCount * ratio,
_outer.height() * kFramesCount * ratio,
@ -529,8 +530,8 @@ void Manager::paintButton(
const auto geometry = button->geometry();
const auto position = geometry.topLeft();
const auto size = geometry.size();
const auto outbg = button->outbg();
const auto patterned = outbg
const auto style = button->style();
const auto patterned = (style == ButtonStyle::Outgoing)
&& context.bubblesPattern
&& !context.viewport.isEmpty()
&& !context.bubblesPattern->pixmap.size().isEmpty();
@ -547,15 +548,19 @@ void Manager::paintButton(
_cacheForPattern,
QRect(QPoint(), geometry.size() * style::DevicePixelRatio()));
} else {
const auto &stm = context.st->messageStyle(outbg, false);
const auto background = stm.msgBg->c;
const auto background = (style == ButtonStyle::Service)
? context.st->msgServiceFg()->c
: context.st->messageStyle(
(style == ButtonStyle::Outgoing),
false
).msgBg->c;
const auto source = validateFrame(
outbg,
style,
frameIndex,
scale,
stm.msgBg->c,
background,
shadow);
paintLongImage(p, geometry, _cacheInOut, source);
paintLongImage(p, geometry, _cacheInOutService, source);
}
const auto mainEmojiPosition = position + (button->expandUp()
@ -746,20 +751,32 @@ QRect Manager::validateEmoji(int frameIndex, float64 scale) {
}
QRect Manager::validateFrame(
bool outbg,
ButtonStyle style,
int frameIndex,
float64 scale,
const QColor &background,
const QColor &shadow) {
applyPatternedShadow(shadow);
auto &valid = outbg ? _validOut : _validIn;
auto &color = outbg ? _backgroundOut : _backgroundIn;
auto &valid = (style == ButtonStyle::Service)
? _validService
: (style == ButtonStyle::Outgoing)
? _validOut
: _validIn;
auto &color = (style == ButtonStyle::Service)
? _backgroundService
: (style == ButtonStyle::Outgoing)
? _backgroundOut
: _backgroundIn;
if (color != background) {
color = background;
ranges::fill(valid, false);
}
const auto columnIndex = outbg ? kOutCacheIndex : kInCacheIndex;
const auto columnIndex = (style == ButtonStyle::Service)
? kServiceCacheIndex
: (style == ButtonStyle::Outgoing)
? kOutCacheIndex
: kInCacheIndex;
const auto result = cacheRect(frameIndex, columnIndex);
if (valid[frameIndex]) {
return result;
@ -767,7 +784,7 @@ QRect Manager::validateFrame(
const auto shadowSource = validateShadow(frameIndex, scale, shadow);
const auto position = result.topLeft() / style::DevicePixelRatio();
auto p = QPainter(&_cacheInOut);
auto p = QPainter(&_cacheInOutService);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawImage(position, _cacheParts, shadowSource);
p.setCompositionMode(QPainter::CompositionMode_SourceOver);

View file

@ -27,7 +27,9 @@ struct TextState;
namespace HistoryView::Reactions {
enum class ButtonStyle {
Bubble,
Incoming,
Outgoing,
Service,
};
enum class ExpandDirection {
@ -46,11 +48,10 @@ struct ButtonParameters {
FullMsgId context;
QPoint center;
QPoint pointer;
ButtonStyle style = ButtonStyle::Bubble;
ButtonStyle style = ButtonStyle::Incoming;
int reactionsCount = 1;
int visibleTop = 0;
int visibleBottom = 0;
bool outbg = false;
};
enum class ButtonState {
@ -70,7 +71,7 @@ public:
using State = ButtonState;
void applyState(State state);
[[nodiscard]] bool outbg() const;
[[nodiscard]] ButtonStyle style() const;
[[nodiscard]] bool expandUp() const;
[[nodiscard]] bool isHidden() const;
[[nodiscard]] QRect geometry() const;
@ -101,7 +102,7 @@ private:
int _finalHeight = 0;
int _scroll = 0;
ExpandDirection _expandDirection = ExpandDirection::Up;
bool _outbg = false;
ButtonStyle _style = ButtonStyle::Incoming;
};
@ -171,7 +172,7 @@ private:
const QColor &shadow);
QRect validateEmoji(int frameIndex, float64 scale);
QRect validateFrame(
bool outbg,
ButtonStyle style,
int frameIndex,
float64 scale,
const QColor &background,
@ -198,17 +199,19 @@ private:
QSize _outer;
QRectF _inner;
QRect _innerActive;
QImage _cacheInOut;
QImage _cacheInOutService;
QImage _cacheParts;
QImage _cacheForPattern;
QImage _shadowBuffer;
std::array<bool, kFramesCount> _validIn = { { false } };
std::array<bool, kFramesCount> _validOut = { { false } };
std::array<bool, kFramesCount> _validService = { { false } };
std::array<bool, kFramesCount> _validShadow = { { false } };
std::array<bool, kFramesCount> _validEmoji = { { false } };
std::array<bool, kFramesCount> _validMask = { { false } };
QColor _backgroundIn;
QColor _backgroundOut;
QColor _backgroundService;
QColor _shadow;
std::shared_ptr<Data::DocumentMedia> _mainReactionMedia;

View file

@ -22,6 +22,11 @@ namespace {
constexpr auto kInNonChosenOpacity = 0.12;
constexpr auto kOutNonChosenOpacity = 0.18;
[[nodiscard]] QColor AdaptChosenServiceFg(QColor serviceBg) {
serviceBg.setAlpha(std::max(serviceBg.alpha(), 192));
return serviceBg;
}
} // namespace
InlineList::InlineList(
@ -146,6 +151,14 @@ QSize InlineList::countCurrentSize(int newWidth) {
return { newWidth, height + add };
}
int InlineList::placeAndResizeGetHeight(QRect available) {
const auto result = resizeGetHeight(available.width());
for (auto &button : _buttons) {
button.geometry.translate(available.x(), 0);
}
return result;
}
void InlineList::paint(
Painter &p,
const PaintContext &context,
@ -173,9 +186,7 @@ void InlineList::paint(
}
p.setBrush(stm->msgFileBg);
} else {
p.setBrush(chosen
? st->msgServiceBgSelected()
: st->msgServiceBg());
p.setBrush(chosen ? st->msgServiceFg() : st->msgServiceBg());
}
const auto radius = geometry.height() / 2.;
p.drawRoundedRect(geometry, radius, radius);
@ -192,7 +203,9 @@ void InlineList::paint(
p.drawImage(inner.topLeft(), button.image);
}
p.setPen(!inbubble
? st->msgServiceFg()
? (chosen
? QPen(AdaptChosenServiceFg(st->msgServiceBg()->c))
: st->msgServiceFg())
: !chosen
? stm->msgServiceFg
: context.outbg

View file

@ -48,6 +48,7 @@ public:
void update(Data &&data, int availableWidth);
QSize countCurrentSize(int newWidth) override;
[[nodiscard]] int placeAndResizeGetHeight(QRect available);
void updateSkipBlock(int width, int height);
void removeSkipBlock();

View file

@ -1170,6 +1170,27 @@ bool Gif::needsBubble() const {
return false;
}
QRect Gif::contentRectForReactionButton() const {
if (isSeparateRoundVideo()) {
return QRect(0, 0, width(), height());
}
auto paintx = 0, painty = 0, paintw = width(), painth = height();
auto usex = 0, usew = paintw;
const auto outbg = _parent->hasOutLayout();
const auto item = _parent->data();
const auto via = item->Get<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto forwarded = item->Get<HistoryMessageForwarded>();
if (via || reply || forwarded) {
usew = maxWidth() - additionalWidth(via, reply, forwarded);
if (outbg) {
usex = width() - usew;
}
}
if (rtl()) usex = width() - usex - usew;
return style::rtlrect(usex + paintx, painty, usew, painth, width());
}
int Gif::additionalWidth() const {
const auto item = _parent->data();
return additionalWidth(

View file

@ -96,6 +96,7 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty();
}
QRect contentRectForReactionButton() const override;
QString additionalInfoString() const override;
bool skipBubbleTail() const override {

View file

@ -345,6 +345,10 @@ bool Location::needsBubble() const {
|| _parent->displayFromName();
}
QRect Location::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
int Location::fullWidth() const {
return st::locationSize.width();
}

View file

@ -53,6 +53,7 @@ public:
bool customInfoLayout() const override {
return true;
}
QRect contentRectForReactionButton() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom();

View file

@ -205,6 +205,9 @@ public:
}
[[nodiscard]] virtual bool needsBubble() const = 0;
[[nodiscard]] virtual bool customInfoLayout() const = 0;
[[nodiscard]] virtual QRect contentRectForReactionButton() const {
Unexpected("Media::contentRectForReactionButton");
}
[[nodiscard]] virtual QMargins bubbleMargins() const {
return QMargins();
}

View file

@ -730,6 +730,10 @@ bool GroupedMedia::needsBubble() const {
return _needBubble;
}
QRect GroupedMedia::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
bool GroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty() || _mode == Mode::Column) {
return true;

View file

@ -84,6 +84,7 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty() && (_mode != Mode::Column);
}
QRect contentRectForReactionButton() const override;
bool allowsFastShare() const override {
return true;
}

View file

@ -401,6 +401,37 @@ TextState UnwrappedMedia::textState(QPoint point, StateRequest request) const {
return result;
}
QRect UnwrappedMedia::contentRectForReactionButton() const {
const auto inWebPage = (_parent->media() != this);
if (inWebPage) {
return QRect(0, 0, width(), height());
}
const auto rightAligned = _parent->hasOutLayout()
&& !_parent->delegate()->elementIsChatWide();
const auto item = _parent->data();
const auto via = item->Get<HistoryMessageVia>();
const auto reply = _parent->displayedReply();
const auto forwarded = getDisplayedForwardedInfo();
auto usex = 0;
auto usew = maxWidth();
if (!inWebPage) {
usew -= additionalWidth(via, reply, forwarded);
if (rightAligned) {
usex = width() - usew;
}
}
if (rtl()) {
usex = width() - usex - usew;
}
const auto usey = rightAligned ? 0 : (height() - _contentSize.height());
const auto useh = rightAligned
? std::max(
_contentSize.height(),
height() - st::msgDateImgPadding.y() * 2 - st::msgDateFont->height)
: _contentSize.height();
return QRect(usex, usey, usew, useh);
}
std::unique_ptr<Lottie::SinglePlayer> UnwrappedMedia::stickerTakeLottie(
not_null<DocumentData*> data,
const Lottie::ColorReplacements *replacements) {

View file

@ -81,6 +81,7 @@ public:
bool customInfoLayout() const override {
return true;
}
QRect contentRectForReactionButton() const override;
void stickerClearLoopPlayed() override {
_content->stickerClearLoopPlayed();
}

View file

@ -831,6 +831,10 @@ bool Photo::needsBubble() const {
|| _parent->displayFromName());
}
QRect Photo::contentRectForReactionButton() const {
return QRect(0, 0, width(), height());
}
bool Photo::isReadyForOpen() const {
ensureDataMediaCreated();
return _dataMedia->loaded();

View file

@ -82,6 +82,7 @@ public:
bool customInfoLayout() const override {
return _caption.isEmpty();
}
QRect contentRectForReactionButton() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom() && _caption.isEmpty();
}