mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-19 07:37:11 +02:00
Implement multi-song albums display.
This commit is contained in:
parent
cc28ba4284
commit
399b03beb2
7 changed files with 357 additions and 61 deletions
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue