mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +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 {
|
bool MediaFile::canBeGrouped() const {
|
||||||
return _document->isVideoFile();
|
return _document->isVideoFile() || _document->isSong();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaFile::hasReplyPreview() const {
|
bool MediaFile::hasReplyPreview() const {
|
||||||
|
|
|
@ -138,27 +138,101 @@ QString FastReplyText() {
|
||||||
return tr::lng_fast_reply(tr::now);
|
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 &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 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) {
|
if (tailSide == RectPart::Right) {
|
||||||
parts |= RectPart::BottomLeft;
|
parts |= RectPart::BottomLeft;
|
||||||
p.fillRect(rect.x() + rect.width() - st::historyMessageRadius, rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
|
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;
|
auto &tail = selected ? st::historyBubbleTailOutRightSelected : st::historyBubbleTailOutRight;
|
||||||
tail.paint(p, rect.x() + rect.width(), rect.y() + rect.height() - tail.height(), outerWidth);
|
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) {
|
} else if (tailSide == RectPart::Left) {
|
||||||
parts |= RectPart::BottomRight;
|
parts |= RectPart::BottomRight;
|
||||||
p.fillRect(rect.x(), rect.y() + rect.height() - st::historyMessageRadius, st::historyMessageRadius, st::historyMessageRadius, bg);
|
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);
|
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);
|
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);
|
p.fillRect(rect.x() - tail.width(), rect.y() + rect.height(), st::historyMessageRadius + tail.width(), st::msgShadow, *sh);
|
||||||
} else {
|
} else if (!(skip & RectPart::Bottom)) {
|
||||||
parts |= RectPart::FullBottom;
|
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) {
|
style::color FromNameFg(PeerId peerId, bool selected) {
|
||||||
|
@ -535,20 +609,52 @@ void Message::draw(
|
||||||
auto entry = logEntryOriginal();
|
auto entry = logEntryOriginal();
|
||||||
auto mediaDisplayed = media && media->isDisplayed();
|
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()
|
auto skipTail = isAttachedToNext()
|
||||||
|| (media && media->skipBubbleTail())
|
|| (media && media->skipBubbleTail())
|
||||||
|| (keyboard != nullptr)
|
|| (keyboard != nullptr)
|
||||||
|| (context() == Context::Replies && data()->isDiscussionPost());
|
|| (context() == Context::Replies && data()->isDiscussionPost());
|
||||||
auto displayTail = skipTail ? RectPart::None : (outbg && !Core::App().settings().chatWide()) ? RectPart::Right : RectPart::Left;
|
auto displayTail = skipTail
|
||||||
PaintBubble(p, g, width(), selected, outbg, displayTail);
|
? RectPart::None
|
||||||
|
: (outbg && !Core::App().settings().chatWide())
|
||||||
|
? RectPart::Right
|
||||||
|
: RectPart::Left;
|
||||||
|
PaintBubble(
|
||||||
|
p,
|
||||||
|
g,
|
||||||
|
width(),
|
||||||
|
selected,
|
||||||
|
mediaSelectionIntervals,
|
||||||
|
outbg,
|
||||||
|
displayTail);
|
||||||
|
|
||||||
auto inner = g;
|
auto inner = g;
|
||||||
paintCommentsButton(p, inner, selected);
|
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);
|
auto trect = inner.marginsRemoved(st::msgPadding);
|
||||||
if (mediaOnBottom) {
|
if (mediaOnBottom) {
|
||||||
trect.setHeight(trect.height() + st::msgPadding.bottom());
|
trect.setHeight(trect.height() + st::msgPadding.bottom());
|
||||||
|
@ -591,11 +697,16 @@ void Message::draw(
|
||||||
? !media->customInfoLayout()
|
? !media->customInfoLayout()
|
||||||
: true);
|
: true);
|
||||||
if (needDrawInfo) {
|
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) {
|
if (g != inner) {
|
||||||
const auto o = p.opacity();
|
const auto o = p.opacity();
|
||||||
p.setOpacity(0.3);
|
p.setOpacity(0.3);
|
||||||
const auto color = selected
|
const auto color = bottomSelected
|
||||||
? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected)
|
? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected)
|
||||||
: (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
: (outbg ? st::msgOutDateFg : st::msgInDateFg);
|
||||||
p.fillRect(inner.left(), inner.top() + inner.height() - st::lineWidth, inner.width(), st::lineWidth, color);
|
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 };
|
return { newWidth, newHeight };
|
||||||
}
|
}
|
||||||
|
|
||||||
void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::time ms) const {
|
void Document::draw(
|
||||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
|
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();
|
ensureDataMediaCreated();
|
||||||
|
|
||||||
|
@ -260,7 +273,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
|
||||||
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
|
bool loaded = dataLoaded(), displayLoading = _data->displayLoading();
|
||||||
bool selected = (selection == FullSelection);
|
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();
|
auto outbg = _parent->hasOutLayout();
|
||||||
|
|
||||||
if (displayLoading) {
|
if (displayLoading) {
|
||||||
|
@ -284,7 +297,7 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
|
||||||
|
|
||||||
auto inWebPage = (_parent->media() != this);
|
auto inWebPage = (_parent->media() != this);
|
||||||
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
|
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;
|
QPixmap thumb;
|
||||||
if (const auto normal = _dataMedia->thumbnail()) {
|
if (const auto normal = _dataMedia->thumbnail()) {
|
||||||
thumb = normal->pixSingle(thumbed->_thumbw, 0, st::msgFileThumbSize, st::msgFileThumbSize, roundRadius);
|
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);
|
bool over = ClickHandler::showAsActive(lnk);
|
||||||
p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
|
p.setFont(over ? st::semiboldFont->underline() : st::semiboldFont);
|
||||||
p.setPen(outbg ? (selected ? st::msgFileThumbLinkOutFgSelected : st::msgFileThumbLinkOutFg) : (selected ? st::msgFileThumbLinkInFgSelected : st::msgFileThumbLinkInFg));
|
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 {
|
} else {
|
||||||
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
|
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;
|
statustop = st::msgFileStatusTop - topMinus;
|
||||||
bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - 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);
|
p.setPen(Qt::NoPen);
|
||||||
if (selected) {
|
if (selected) {
|
||||||
p.setBrush(outbg ? st::msgFileOutBgSelected : st::msgFileInBgSelected);
|
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);
|
drawCornerDownload(p, selected);
|
||||||
}
|
}
|
||||||
auto namewidth = width() - nameleft - nameright;
|
auto namewidth = width - nameleft - nameright;
|
||||||
auto statuswidth = namewidth;
|
auto statuswidth = namewidth;
|
||||||
|
|
||||||
auto voiceStatusOverride = QString();
|
auto voiceStatusOverride = QString();
|
||||||
|
@ -470,9 +483,9 @@ void Document::draw(Painter &p, const QRect &r, TextSelection selection, crl::ti
|
||||||
p.setFont(st::semiboldFont);
|
p.setFont(st::semiboldFont);
|
||||||
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
|
p.setPen(outbg ? (selected ? st::historyFileNameOutFgSelected : st::historyFileNameOutFg) : (selected ? st::historyFileNameInFgSelected : st::historyFileNameInFg));
|
||||||
if (namewidth < named->_namew) {
|
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 {
|
} 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);
|
auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
|
||||||
p.setFont(st::normalFont);
|
p.setFont(st::normalFont);
|
||||||
p.setPen(status);
|
p.setPen(status);
|
||||||
p.drawTextLeft(nameleft, statustop, width(), statusText);
|
p.drawTextLeft(nameleft, statustop, width, statusText);
|
||||||
|
|
||||||
if (_parent->data()->hasUnreadMediaFlag()) {
|
if (_parent->data()->hasUnreadMediaFlag()) {
|
||||||
auto w = st::normalFont->width(statusText);
|
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);
|
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>()) {
|
if (mode == LayoutMode::Full) {
|
||||||
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
|
if (auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
captioned->_caption.draw(p, st::msgPadding.left(), bottom, captionw, style::al_left, 0, -1, selection);
|
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 {
|
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);
|
auto result = TextState(_parent);
|
||||||
|
|
||||||
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
if (width < st::msgPadding.left() + st::msgPadding.right() + 1) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,14 +633,14 @@ TextState Document::textState(QPoint point, StateRequest request) const {
|
||||||
linktop = st::msgFileThumbLinkTop - topMinus;
|
linktop = st::msgFileThumbLinkTop - topMinus;
|
||||||
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - 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)) {
|
if ((_data->loading() || _data->uploading()) && rthumb.contains(point)) {
|
||||||
result.link = _cancell;
|
result.link = _cancell;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_data->status != FileUploadFailed) {
|
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())
|
result.link = (_data->loading() || _data->uploading())
|
||||||
? thumbed->_linkcancell
|
? thumbed->_linkcancell
|
||||||
: dataLoaded()
|
: dataLoaded()
|
||||||
|
@ -632,7 +658,7 @@ TextState Document::textState(QPoint point, StateRequest request) const {
|
||||||
if (const auto state = cornerDownloadTextState(point, request); state.link) {
|
if (const auto state = cornerDownloadTextState(point, request); state.link) {
|
||||||
return state;
|
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()) {
|
if ((_data->loading() || _data->uploading()) && inner.contains(point) && !downloadInCorner()) {
|
||||||
result.link = _cancell;
|
result.link = _cancell;
|
||||||
return result;
|
return result;
|
||||||
|
@ -640,7 +666,7 @@ TextState Document::textState(QPoint point, StateRequest request) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto voice = Get<HistoryDocumentVoice>()) {
|
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;
|
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
|
||||||
if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
|
if (QRect(nameleft, nametop, namewidth, waveformbottom - nametop).contains(point)) {
|
||||||
const auto state = ::Media::Player::instance()->getState(AudioMsgId::Type::Voice);
|
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();
|
auto painth = layout.height();
|
||||||
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
if (mode == LayoutMode::Full) {
|
||||||
if (point.y() >= bottom) {
|
if (const auto captioned = Get<HistoryDocumentCaptioned>()) {
|
||||||
result = TextState(_parent, captioned->_caption.getState(
|
if (point.y() >= bottom) {
|
||||||
point - QPoint(st::msgPadding.left(), bottom),
|
result = TextState(_parent, captioned->_caption.getState(
|
||||||
width() - st::msgPadding.left() - st::msgPadding.right(),
|
point - QPoint(st::msgPadding.left(), bottom),
|
||||||
request.forText()));
|
width - st::msgPadding.left() - st::msgPadding.right(),
|
||||||
return result;
|
request.forText()));
|
||||||
}
|
return result;
|
||||||
auto captionw = width() - st::msgPadding.left() - st::msgPadding.right();
|
}
|
||||||
painth -= captioned->_caption.countHeight(captionw);
|
auto captionw = width - st::msgPadding.left() - st::msgPadding.right();
|
||||||
if (isBubbleBottom()) {
|
painth -= captioned->_caption.countHeight(captionw);
|
||||||
painth -= st::msgPadding.bottom();
|
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->loading() || downloadInCorner())
|
||||||
&& !_data->uploading()
|
&& !_data->uploading()
|
||||||
&& !_data->isNull()) {
|
&& !_data->isNull()) {
|
||||||
|
@ -831,6 +859,37 @@ bool Document::hideForwardedFrom() const {
|
||||||
return _data->isSong();
|
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) {
|
bool Document::voiceProgressAnimationCallback(crl::time now) {
|
||||||
if (anim::Disabled()) {
|
if (anim::Disabled()) {
|
||||||
now += (2 * kAudioVoiceMsgUpdateView);
|
now += (2 * kAudioVoiceMsgUpdateView);
|
||||||
|
|
|
@ -61,6 +61,23 @@ public:
|
||||||
QMargins bubbleMargins() const override;
|
QMargins bubbleMargins() const override;
|
||||||
bool hideForwardedFrom() 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);
|
bool voiceProgressAnimationCallback(crl::time now);
|
||||||
|
|
||||||
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
|
||||||
|
@ -82,7 +99,22 @@ private:
|
||||||
bool showPause = false;
|
bool showPause = false;
|
||||||
int realDuration = 0;
|
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;
|
void ensureDataMediaCreated() const;
|
||||||
|
|
||||||
[[nodiscard]] Ui::Text::String createCaption();
|
[[nodiscard]] Ui::Text::String createCaption();
|
||||||
|
|
|
@ -45,6 +45,11 @@ enum class MediaInBubbleState {
|
||||||
Bottom,
|
Bottom,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BubbleSelectionInterval {
|
||||||
|
int top = 0;
|
||||||
|
int height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] QString DocumentTimestampLinkBase(
|
[[nodiscard]] QString DocumentTimestampLinkBase(
|
||||||
not_null<DocumentData*> document,
|
not_null<DocumentData*> document,
|
||||||
FullMsgId context);
|
FullMsgId context);
|
||||||
|
@ -116,6 +121,12 @@ public:
|
||||||
[[nodiscard]] TextSelection unskipSelection(
|
[[nodiscard]] TextSelection unskipSelection(
|
||||||
TextSelection selection) const;
|
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
|
// if we press and drag this link should we drag the item
|
||||||
[[nodiscard]] virtual bool dragItemByHandler(
|
[[nodiscard]] virtual bool dragItemByHandler(
|
||||||
const ClickHandlerPtr &p) const = 0;
|
const ClickHandlerPtr &p) const = 0;
|
||||||
|
|
|
@ -22,6 +22,32 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "styles/style_history.h"
|
#include "styles/style_history.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
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(
|
GroupedMedia::Part::Part(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
@ -39,7 +65,7 @@ GroupedMedia::GroupedMedia(
|
||||||
const auto truncated = ranges::view::all(
|
const auto truncated = ranges::view::all(
|
||||||
medias
|
medias
|
||||||
) | ranges::view::transform([](const std::unique_ptr<Data::Media> &v) {
|
) | ranges::view::transform([](const std::unique_ptr<Data::Media> &v) {
|
||||||
return not_null<Data::Media*>(v.get());
|
return v.get();
|
||||||
}) | ranges::view::take(kMaxSize);
|
}) | ranges::view::take(kMaxSize);
|
||||||
const auto result = applyGroup(truncated);
|
const auto result = applyGroup(truncated);
|
||||||
|
|
||||||
|
@ -66,6 +92,13 @@ GroupedMedia::~GroupedMedia() {
|
||||||
base::take(_parts);
|
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() {
|
QSize GroupedMedia::countOptimalSize() {
|
||||||
if (_caption.hasSkipBlock()) {
|
if (_caption.hasSkipBlock()) {
|
||||||
_caption.updateSkipBlock(
|
_caption.updateSkipBlock(
|
||||||
|
@ -81,11 +114,13 @@ QSize GroupedMedia::countOptimalSize() {
|
||||||
sizes.push_back(media->sizeForGrouping());
|
sizes.push_back(media->sizeForGrouping());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto layout = Ui::LayoutMediaGroup(
|
const auto layout = (_mode == Mode::Grid)
|
||||||
sizes,
|
? Ui::LayoutMediaGroup(
|
||||||
st::historyGroupWidthMax,
|
sizes,
|
||||||
st::historyGroupWidthMin,
|
st::historyGroupWidthMax,
|
||||||
st::historyGroupSkip);
|
st::historyGroupWidthMin,
|
||||||
|
st::historyGroupSkip)
|
||||||
|
: LayoutPlaylist(sizes);
|
||||||
Assert(layout.size() == _parts.size());
|
Assert(layout.size() == _parts.size());
|
||||||
|
|
||||||
auto maxWidth = 0;
|
auto maxWidth = 0;
|
||||||
|
@ -313,6 +348,33 @@ TextForMimeData GroupedMedia::selectedText(
|
||||||
return _caption.toTextForMimeData(selection);
|
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(
|
void GroupedMedia::clickHandlerActiveChanged(
|
||||||
const ClickHandlerPtr &p,
|
const ClickHandlerPtr &p,
|
||||||
bool active) {
|
bool active) {
|
||||||
|
@ -339,7 +401,15 @@ bool GroupedMedia::applyGroup(const DataMediaRange &medias) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto modeChosen = false;
|
||||||
for (const auto media : medias) {
|
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));
|
_parts.push_back(Part(_parent, media));
|
||||||
}
|
}
|
||||||
if (_parts.empty()) {
|
if (_parts.empty()) {
|
||||||
|
@ -449,7 +519,7 @@ bool GroupedMedia::needsBubble() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GroupedMedia::computeNeedBubble() const {
|
bool GroupedMedia::computeNeedBubble() const {
|
||||||
if (!_caption.isEmpty()) {
|
if (!_caption.isEmpty() || _mode == Mode::Playlist) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (const auto item = _parent->data()) {
|
if (const auto item = _parent->data()) {
|
||||||
|
@ -467,9 +537,10 @@ bool GroupedMedia::computeNeedBubble() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GroupedMedia::needInfoDisplay() const {
|
bool GroupedMedia::needInfoDisplay() const {
|
||||||
return (_parent->data()->id < 0
|
return (_mode != Mode::Playlist)
|
||||||
|| _parent->isUnderCursor()
|
&& (_parent->data()->id < 0
|
||||||
|| _parent->isLastAndSelfMessage());
|
|| _parent->isUnderCursor()
|
||||||
|
|| _parent->isLastAndSelfMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -60,6 +60,9 @@ public:
|
||||||
|
|
||||||
TextForMimeData selectedText(TextSelection selection) const override;
|
TextForMimeData selectedText(TextSelection selection) const override;
|
||||||
|
|
||||||
|
std::vector<BubbleSelectionInterval> getBubbleSelectionIntervals(
|
||||||
|
TextSelection selection) const override;
|
||||||
|
|
||||||
void clickHandlerActiveChanged(
|
void clickHandlerActiveChanged(
|
||||||
const ClickHandlerPtr &p,
|
const ClickHandlerPtr &p,
|
||||||
bool active) override;
|
bool active) override;
|
||||||
|
@ -76,12 +79,14 @@ public:
|
||||||
HistoryMessageEdited *displayedEditBadge() const override;
|
HistoryMessageEdited *displayedEditBadge() const override;
|
||||||
|
|
||||||
bool skipBubbleTail() const override {
|
bool skipBubbleTail() const override {
|
||||||
return isRoundedInBubbleBottom() && _caption.isEmpty();
|
return (_mode == Mode::Grid)
|
||||||
|
&& isRoundedInBubbleBottom()
|
||||||
|
&& _caption.isEmpty();
|
||||||
}
|
}
|
||||||
void updateNeedBubbleState() override;
|
void updateNeedBubbleState() override;
|
||||||
bool needsBubble() const override;
|
bool needsBubble() const override;
|
||||||
bool customInfoLayout() const override {
|
bool customInfoLayout() const override {
|
||||||
return _caption.isEmpty();
|
return _caption.isEmpty() && (_mode != Mode::Playlist);
|
||||||
}
|
}
|
||||||
bool allowsFastShare() const override {
|
bool allowsFastShare() const override {
|
||||||
return true;
|
return true;
|
||||||
|
@ -95,6 +100,10 @@ public:
|
||||||
void parentTextUpdated() override;
|
void parentTextUpdated() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum class Mode : char {
|
||||||
|
Grid,
|
||||||
|
Playlist,
|
||||||
|
};
|
||||||
struct Part {
|
struct Part {
|
||||||
Part(
|
Part(
|
||||||
not_null<Element*> parent,
|
not_null<Element*> parent,
|
||||||
|
@ -111,6 +120,8 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] static Mode DetectMode(not_null<Data::Media*> media);
|
||||||
|
|
||||||
template <typename DataMediaRange>
|
template <typename DataMediaRange>
|
||||||
bool applyGroup(const DataMediaRange &medias);
|
bool applyGroup(const DataMediaRange &medias);
|
||||||
|
|
||||||
|
@ -131,6 +142,7 @@ private:
|
||||||
|
|
||||||
Ui::Text::String _caption;
|
Ui::Text::String _caption;
|
||||||
std::vector<Part> _parts;
|
std::vector<Part> _parts;
|
||||||
|
Mode _mode = Mode::Grid;
|
||||||
bool _needBubble = false;
|
bool _needBubble = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue