Implement multi-song albums display.

This commit is contained in:
John Preston 2020-10-06 21:27:26 +03:00
parent cc28ba4284
commit 399b03beb2
7 changed files with 357 additions and 61 deletions

View File

@ -465,7 +465,7 @@ Storage::SharedMediaTypesMask MediaFile::sharedMediaTypes() const {
}
bool MediaFile::canBeGrouped() const {
return _document->isVideoFile();
return _document->isVideoFile() || _document->isSong();
}
bool MediaFile::hasReplyPreview() const {

View File

@ -138,27 +138,101 @@ QString FastReplyText() {
return tr::lng_fast_reply(tr::now);
}
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide) {
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, bool outbg, RectPart tailSide, RectParts skip) {
auto &bg = selected ? (outbg ? st::msgOutBgSelected : st::msgInBgSelected) : (outbg ? st::msgOutBg : st::msgInBg);
auto &sh = selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow);
auto sh = &(selected ? (outbg ? st::msgOutShadowSelected : st::msgInShadowSelected) : (outbg ? st::msgOutShadow : st::msgInShadow));
auto cors = selected ? (outbg ? MessageOutSelectedCorners : MessageInSelectedCorners) : (outbg ? MessageOutCorners : MessageInCorners);
auto parts = RectPart::FullTop | RectPart::NoTopBottom | RectPart::Bottom;
auto parts = RectPart::None | RectPart::NoTopBottom;
if (skip & RectPart::Top) {
if (skip & RectPart::Bottom) {
p.fillRect(rect, bg);
return;
}
rect.setTop(rect.y() - st::historyMessageRadius);
} else {
parts |= RectPart::FullTop;
}
if (skip & RectPart::Bottom) {
rect.setHeight(rect.height() + st::historyMessageRadius);
sh = nullptr;
tailSide = RectPart::None;
} else {
parts |= RectPart::Bottom;
}
if (tailSide == RectPart::Right) {
parts |= RectPart::BottomLeft;
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight;
tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth);
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh);
} else if (tailSide == RectPart::Left) {
parts |= RectPart::BottomRight;
p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
auto &tail = selected ? (outbg ? st::historyBubbleTailOutLeftSelected : st::historyBubbleTailInLeftSelected) : (outbg ? st::historyBubbleTailOutLeft : st::historyBubbleTailInLeft);
tail.paint(p, rect.x() - tail.width(), rect.y() + rect.height() - tail.height(), outerWidth);
p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, sh);
} else {
p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh);
} else if (!(skip & RectPart::Bottom)) {
parts |= RectPart::FullBottom;
}
App::roundRect(p, rect, bg, cors, &sh, parts);
App::roundRect(p, rect, bg, cors, sh, parts);
}
void PaintBubble(Painter &p, QRect rect, int outerWidth, bool selected, const std::vector<BubbleSelectionInterval> &selection, bool outbg, RectPart tailSide) {
if (selection.empty()) {
PaintBubble(
p,
rect,
outerWidth,
selected,
outbg,
tailSide,
RectPart::None);
return;
}
const auto left = rect.x();
const auto width = rect.width();
const auto top = rect.y();
const auto bottom = top + rect.height();
auto from = top;
for (const auto &selected : selection) {
if (selected.top > from) {
const auto skip = RectPart::Bottom
| (from > top ? RectPart::Top : RectPart::None);
PaintBubble(
p,
QRect(left, from, width, selected.top - from),
outerWidth,
false,
outbg,
tailSide,
skip);
}
const auto skip = ((selected.top > top)
? RectPart::Top
: RectPart::None)
| ((selected.top + selected.height < bottom)
? RectPart::Bottom
: RectPart::None);
PaintBubble(
p,
QRect(left, selected.top, width, selected.height),
outerWidth,
true,
outbg,
tailSide,
skip);
from = selected.top + selected.height;
}
if (from < bottom) {
PaintBubble(
p,
QRect(left, from, width, bottom - from),
outerWidth,
false,
outbg,
tailSide,
RectPart::Top);
}
}
style::color FromNameFg(PeerId peerId, bool selected) {
@ -535,20 +609,52 @@ void Message::draw(
auto entry = logEntryOriginal();
auto mediaDisplayed = media && media->isDisplayed();
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto mediaSelectionIntervals = (!selected && mediaDisplayed)
? media->getBubbleSelectionIntervals(selection)
: std::vector<BubbleSelectionInterval>();
if (!mediaSelectionIntervals.empty()) {
auto localMediaBottom = g.top() + g.height();
if (data()->repliesAreComments() || data()->externalReply()) {
localMediaBottom -= st::historyCommentsButtonHeight;
}
if (!mediaOnBottom) {
localMediaBottom -= st::msgPadding.bottom();
}
if (entry) {
localMediaBottom -= entry->height();
}
const auto localMediaTop = localMediaBottom - media->height();
for (auto &[top, height] : mediaSelectionIntervals) {
top += localMediaTop;
}
}
auto skipTail = isAttachedToNext()
|| (media && media->skipBubbleTail())
|| (keyboard != nullptr)
|| (context() == Context::Replies && data()->isDiscussionPost());
auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left;
PaintBubble(p, g, width(), selected, outbg, displayTail);
auto displayTail = skipTail
? RectPart::None
: (outbg && !Core::App().settings().chatWide())
? RectPart::Right
: RectPart::Left;
PaintBubble(
p,
g,
width(),
selected,
mediaSelectionIntervals,
outbg,
displayTail);
auto inner = g;
paintCommentsButton(p, inner, selected);
// Entry page is always a bubble bottom.
auto mediaOnBottom = (mediaDisplayed && media->isBubbleBottom()) || (entry/* && entry->isBubbleBottom()*/);
auto mediaOnTop = (mediaDisplayed && media->isBubbleTop()) || (entry && entry->isBubbleTop());
auto trect = inner.marginsRemoved(st::msgPadding);
if (mediaOnBottom) {
trect.setHeight(trect.height() + st::msgPadding.bottom());
@ -591,11 +697,16 @@ void Message::draw(
? !media->customInfoLayout()
: true);
if (needDrawInfo) {
drawInfo(p, inner.left() + inner.width(), inner.top() + inner.height(), 2 * inner.left() + inner.width(), selected, InfoDisplayType::Default);
const auto bottomSelected = selected
|| (!mediaSelectionIntervals.empty()
&& (mediaSelectionIntervals.back().top
+ mediaSelectionIntervals.back().height
>= inner.y() + inner.height()));
drawInfo(p, inner.left() + inner.width(), inner.top() + inner.height(), 2 * inner.left() + inner.width(), bottomSelected, InfoDisplayType::Default);
if (g != inner) {
const auto o = p.opacity();
p.setOpacity(0.3);
const auto color = selected
const auto color = bottomSelected
? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected)
: (outbg ? st::msgOutDateFg : st::msgInDateFg);
p.fillRect(inner.left(), inner.top() + inner.height() - st::lineWidth, inner.width(), st::lineWidth, color);

View File

@ -247,8 +247,21 @@ QSize Document::countCurrentSize(int newWidth) {
return { newWidth, newHeight };
}
void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
void Document::draw(
Painter &p,
const QRect &r,
TextSelection selection,
crl::time ms) const {
draw(p, width(), selection, ms, LayoutMode::Full);
}
void Document::draw(
Painter &p,
int width,
TextSelection selection,
crl::time ms,
LayoutMode mode) const {
if (width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
ensureDataMediaCreated();
@ -260,7 +273,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
bool selected = (selection == FullSelection);
int captionw = width() - st::msgPadding.left() - st::msgPadding.right();
int captionw = width - st::msgPadding.left() - st::msgPadding.right();
auto outbg = _parent->hasOutLayout();
if (displayLoading) {
@ -284,7 +297,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
auto inWebPage = (_parent->media() != this);
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width));
QPixmap thumb;
if (const auto normal = _dataMedia->thumbnail()) {
thumb = normal->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
@ -339,7 +352,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
bool over = ClickHandler::showAsActive(lnk);
p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
p.drawTextLeft(nameleft, linktop, width(), thumbed->_link, thumbed->_linkw);
p.drawTextLeft(nameleft, linktop, width, thumbed->_link, thumbed->_linkw);
}
} else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
@ -348,7 +361,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
statustop = st::msgFileStatusTop - topMinus;
bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width));
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
@ -386,7 +399,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
drawCornerDownload(p, selected);
}
auto namewidth = width() - nameleft - nameright;
auto namewidth = width - nameleft - nameright;
auto statuswidth = namewidth;
auto voiceStatusOverride = QString();
@ -470,9 +483,9 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
p.setFont(st::semiboldFont);
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
if (namewidth < named->_namew) {
p.drawTextLeft(nameleft, nametop, width(), st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle));
p.drawTextLeft(nameleft, nametop, width, st::semiboldFont->elided(named->_name, namewidth, Qt::ElideMiddle));
} else {
p.drawTextLeft(nameleft, nametop, width(), named->_name, named->_namew);
p.drawTextLeft(nameleft, nametop, width, named->_name, named->_namew);
}
}
@ -480,7 +493,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
p.setFont(st::normalFont);
p.setPen(status);
p.drawTextLeft(nameleft, statustop, width(), statusText);
p.drawTextLeft(nameleft, statustop, width, statusText);
if (_parent->data()->hasUnreadMediaFlag()) {
auto w = st::normalFont->width(statusText);
@ -490,14 +503,16 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(style::rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width()));
p.drawEllipse(style::rtlrect(nameleft + w + st::mediaUnreadSkip, statustop + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, width));
}
}
}
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
if (mode == LayoutMode::Full) {
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
}
}
}
@ -587,9 +602,20 @@ TextState Document::cornerDownloadTextState(
}
TextState Document::textState(QPoint point, StateRequest request) const {
return textState(point, { width(), height() }, request, LayoutMode::Full);
}
TextState Document::textState(
QPoint point,
QSize layout,
StateRequest request,
LayoutMode mode) const {
const auto width = layout.width();
const auto height = layout.height();
auto result = TextState(_parent);
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
if (width < st::msgPadding.left() + st::msgPadding.right() + 1) {
return result;
}
@ -607,14 +633,14 @@ TextState Document::textState(QPoint point, StateRequest request) const {
linktop = st::msgFileThumbLinkTop - topMinus;
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width()));
QRect rthumb(style::rtlrect(st::msgFileThumbPadding.left(), st::msgFileThumbPadding.top() - topMinus, st::msgFileThumbSize, st::msgFileThumbSize, width));
if ((_data->loading() || _data->uploading()) && rthumb.contains(point)) {
result.link = _cancell;
return result;
}
if (_data->status != FileUploadFailed) {
if (style::rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width()).contains(point)) {
if (style::rtlrect(nameleft, linktop, thumbed->_linkw, st::semiboldFont->height, width).contains(point)) {
result.link = (_data->loading() || _data->uploading())
? thumbed->_linkcancell
: dataLoaded()
@ -632,7 +658,7 @@ TextState Document::textState(QPoint point, StateRequest request) const {
if (const auto state = cornerDownloadTextState(point, request); state.link) {
return state;
}
QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width()));
QRect inner(style::rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, width));
if ((_data->loading() || _data->uploading()) && inner.contains(point) && !downloadInCorner()) {
result.link = _cancell;
return result;
@ -640,7 +666,7 @@ TextState Document::textState(QPoint point, StateRequest request) const {
}
if (const auto voice = Get<HistoryDocumentVoice>()) {
auto namewidth = width() - nameleft - nameright;
auto namewidth = width - nameleft - nameright;
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
const auto state = ::Media::Player::instance()->getState(AudioMsgId::Type::Voice);
@ -655,22 +681,24 @@ TextState Document::textState(QPoint point, StateRequest request) const {
}
}
auto painth = height();
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
if (point.y() >= bottom) {
result = TextState(_parent, captioned->_caption.getState(
point - QPoint(st::msgPadding.left(), bottom),
width() - st::msgPadding.left() - st::msgPadding.right(),
request.forText()));
return result;
}
auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
painth -= captioned->_caption.countHeight(captionw);
if (isBubbleBottom()) {
painth -= st::msgPadding.bottom();
auto painth = layout.height();
if (mode == LayoutMode::Full) {
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
if (point.y() >= bottom) {
result = TextState(_parent, captioned->_caption.getState(
point - QPoint(st::msgPadding.left(), bottom),
width - st::msgPadding.left() - st::msgPadding.right(),
request.forText()));
return result;
}
auto captionw = width - st::msgPadding.left() - st::msgPadding.right();
painth -= captioned->_caption.countHeight(captionw);
if (isBubbleBottom()) {
painth -= st::msgPadding.bottom();
}
}
}
if (QRect(0, 0, width(), painth).contains(point)
if (QRect(0, 0, width, painth).contains(point)
&& (!_data->loading() || downloadInCorner())
&& !_data->uploading()
&& !_data->isNull()) {
@ -831,6 +859,37 @@ bool Document::hideForwardedFrom() const {
return _data->isSong();
}
QSize Document::sizeForGrouping() const {
const auto height = st::msgFilePadding.top()
+ st::msgFileSize
+ st::msgFilePadding.bottom();
return { maxWidth(), height };
}
void Document::drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const QRect &geometry,
RectParts sides,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const {
p.translate(geometry.topLeft());
draw(p, geometry.width(), selection, ms, LayoutMode::Grouped);
p.translate(-geometry.topLeft());
}
TextState Document::getStateGrouped(
const QRect &geometry,
RectParts sides,
QPoint point,
StateRequest request) const {
point -= geometry.topLeft();
return textState(point, geometry.size(), request, LayoutMode::Grouped);
}
bool Document::voiceProgressAnimationCallback(crl::time now) {
if (anim::Disabled()) {
now += (2 * kAudioVoiceMsgUpdateView);

View File

@ -61,6 +61,23 @@ public:
QMargins bubbleMargins() const override;
bool hideForwardedFrom() const override;
QSize sizeForGrouping() const override;
void drawGrouped(
Painter &p,
const QRect &clip,
TextSelection selection,
crl::time ms,
const QRect &geometry,
RectParts sides,
RectParts corners,
not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const override;
TextState getStateGrouped(
const QRect &geometry,
RectParts sides,
QPoint point,
StateRequest request) const override;
bool voiceProgressAnimationCallback(crl::time now);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
@ -82,7 +99,22 @@ private:
bool showPause = false;
int realDuration = 0;
};
enum class LayoutMode {
Full,
Grouped,
};
void draw(
Painter &p,
int width,
TextSelection selection,
crl::time ms,
LayoutMode mode) const;
[[nodiscard]] TextState textState(
QPoint point,
QSize layout,
StateRequest request,
LayoutMode mode) const;
void ensureDataMediaCreated() const;
[[nodiscard]] Ui::Text::String createCaption();

View File

@ -45,6 +45,11 @@ enum class MediaInBubbleState {
Bottom,
};
struct BubbleSelectionInterval {
int top = 0;
int height = 0;
};
[[nodiscard]] QString DocumentTimestampLinkBase(
not_null<DocumentData*> document,
FullMsgId context);
@ -116,6 +121,12 @@ public:
[[nodiscard]] TextSelection unskipSelection(
TextSelection selection) const;
[[nodiscard]] virtual auto getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<BubbleSelectionInterval> {
return {};
}
// if we press and drag this link should we drag the item
[[nodiscard]] virtual bool dragItemByHandler(
const ClickHandlerPtr &p) const = 0;

View File

@ -22,6 +22,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_history.h"
namespace HistoryView {
namespace {
std::vector<Ui::GroupMediaLayout> LayoutPlaylist(
const std::vector<QSize> &sizes) {
Expects(!sizes.empty());
auto result = std::vector<Ui::GroupMediaLayout>();
result.reserve(sizes.size());
const auto width = ranges::max_element(
sizes,
std::less<>(),
&QSize::width)->width();
auto top = 0;
for (const auto &size : sizes) {
result.push_back({
.geometry = QRect(0, top, width, size.height()),
.sides = RectPart::Left | RectPart::Right
});
top += size.height();
}
result.front().sides |= RectPart::Top;
result.back().sides |= RectPart::Bottom;
return result;
}
} // namespace
GroupedMedia::Part::Part(
not_null<Element*> parent,
@ -39,7 +65,7 @@ GroupedMedia::GroupedMedia(
const auto truncated = ranges::view::all(
medias
) | ranges::view::transform([](const std::unique_ptr<Data::Media> &v) {
return not_null<Data::Media*>(v.get());
return v.get();
}) | ranges::view::take(kMaxSize);
const auto result = applyGroup(truncated);
@ -66,6 +92,13 @@ GroupedMedia::~GroupedMedia() {
base::take(_parts);
}
GroupedMedia::Mode GroupedMedia::DetectMode(not_null<Data::Media*> media) {
const auto document = media->document();
return (document && document->isSong())
? Mode::Playlist
: Mode::Grid;
}
QSize GroupedMedia::countOptimalSize() {
if (_caption.hasSkipBlock()) {
_caption.updateSkipBlock(
@ -81,11 +114,13 @@ QSize GroupedMedia::countOptimalSize() {
sizes.push_back(media->sizeForGrouping());
}
const auto layout = Ui::LayoutMediaGroup(
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
st::historyGroupSkip);
const auto layout = (_mode == Mode::Grid)
? Ui::LayoutMediaGroup(
sizes,
st::historyGroupWidthMax,
st::historyGroupWidthMin,
st::historyGroupSkip)
: LayoutPlaylist(sizes);
Assert(layout.size() == _parts.size());
auto maxWidth = 0;
@ -313,6 +348,33 @@ TextForMimeData GroupedMedia::selectedText(
return _caption.toTextForMimeData(selection);
}
auto GroupedMedia::getBubbleSelectionIntervals(
TextSelection selection) const
-> std::vector<BubbleSelectionInterval> {
auto result = std::vector<BubbleSelectionInterval>();
for (auto i = 0, count = int(_parts.size()); i != count; ++i) {
const auto &part = _parts[i];
if (!IsGroupItemSelection(selection, i)) {
continue;
}
const auto &geometry = part.geometry;
if (result.empty()
|| (result.back().top + result.back().height
< geometry.top())
|| (result.back().top > geometry.top() + geometry.height())) {
result.push_back({ geometry.top(), geometry.height() });
} else {
auto &last = result.back();
const auto newTop = std::min(last.top, geometry.top());
const auto newHeight = std::max(
last.top + last.height - newTop,
geometry.top() + geometry.height() - newTop);
last = BubbleSelectionInterval{ newTop, newHeight };
}
}
return result;
}
void GroupedMedia::clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) {
@ -339,7 +401,15 @@ bool GroupedMedia::applyGroup(const DataMediaRange &medias) {
return true;
}
auto modeChosen = false;
for (const auto media : medias) {
const auto mediaMode = DetectMode(media);
if (!modeChosen) {
_mode = mediaMode;
modeChosen = true;
} else if (mediaMode != _mode) {
continue;
}
_parts.push_back(Part(_parent, media));
}
if (_parts.empty()) {
@ -449,7 +519,7 @@ bool GroupedMedia::needsBubble() const {
}
bool GroupedMedia::computeNeedBubble() const {
if (!_caption.isEmpty()) {
if (!_caption.isEmpty() || _mode == Mode::Playlist) {
return true;
}
if (const auto item = _parent->data()) {
@ -467,9 +537,10 @@ bool GroupedMedia::computeNeedBubble() const {
}
bool GroupedMedia::needInfoDisplay() const {
return (_parent->data()->id < 0
|| _parent->isUnderCursor()
|| _parent->isLastAndSelfMessage());
return (_mode != Mode::Playlist)
&& (_parent->data()->id < 0
|| _parent->isUnderCursor()
|| _parent->isLastAndSelfMessage());
}
} // namespace HistoryView

View File

@ -60,6 +60,9 @@ public:
TextForMimeData selectedText(TextSelection selection) const override;
std::vector<BubbleSelectionInterval> getBubbleSelectionIntervals(
TextSelection selection) const override;
void clickHandlerActiveChanged(
const ClickHandlerPtr &p,
bool active) override;
@ -76,12 +79,14 @@ public:
HistoryMessageEdited *displayedEditBadge() const override;
bool skipBubbleTail() const override {
return isRoundedInBubbleBottom() && _caption.isEmpty();
return (_mode == Mode::Grid)
&& isRoundedInBubbleBottom()
&& _caption.isEmpty();
}
void updateNeedBubbleState() override;
bool needsBubble() const override;
bool customInfoLayout() const override {
return _caption.isEmpty();
return _caption.isEmpty() && (_mode != Mode::Playlist);
}
bool allowsFastShare() const override {
return true;
@ -95,6 +100,10 @@ public:
void parentTextUpdated() override;
private:
enum class Mode : char {
Grid,
Playlist,
};
struct Part {
Part(
not_null<Element*> parent,
@ -111,6 +120,8 @@ private:
};
[[nodiscard]] static Mode DetectMode(not_null<Data::Media*> media);
template <typename DataMediaRange>
bool applyGroup(const DataMediaRange &medias);
@ -131,6 +142,7 @@ private:
Ui::Text::String _caption;
std::vector<Part> _parts;
Mode _mode = Mode::Grid;
bool _needBubble = false;
};