init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
57
Telegram/SourceFiles/layout/abstract_layout_item.cpp
Normal file
57
Telegram/SourceFiles/layout/abstract_layout_item.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/abstract_layout_item.h"
|
||||
|
||||
AbstractLayoutItem::AbstractLayoutItem() {
|
||||
}
|
||||
|
||||
int AbstractLayoutItem::maxWidth() const {
|
||||
return _maxw;
|
||||
}
|
||||
int AbstractLayoutItem::minHeight() const {
|
||||
return _minh;
|
||||
}
|
||||
|
||||
int AbstractLayoutItem::resizeGetHeight(int width) {
|
||||
_width = qMin(width, _maxw);
|
||||
_height = _minh;
|
||||
return _height;
|
||||
}
|
||||
|
||||
int AbstractLayoutItem::width() const {
|
||||
return _width;
|
||||
}
|
||||
int AbstractLayoutItem::height() const {
|
||||
return _height;
|
||||
}
|
||||
|
||||
void AbstractLayoutItem::setPosition(int position) {
|
||||
_position = position;
|
||||
}
|
||||
int AbstractLayoutItem::position() const {
|
||||
return _position;
|
||||
}
|
||||
|
||||
void AbstractLayoutItem::setShiftX(int x) {
|
||||
_shiftX = x;
|
||||
}
|
||||
|
||||
void AbstractLayoutItem::setShiftY(int y) {
|
||||
_shiftY = y;
|
||||
}
|
||||
|
||||
QPoint AbstractLayoutItem::shift() const {
|
||||
return { _shiftX, _shiftY };
|
||||
}
|
||||
|
||||
bool AbstractLayoutItem::hasPoint(QPoint point) const {
|
||||
return QRect(0, 0, width(), height()).contains(point);
|
||||
}
|
||||
|
||||
AbstractLayoutItem::~AbstractLayoutItem() {
|
||||
}
|
||||
61
Telegram/SourceFiles/layout/abstract_layout_item.h
Normal file
61
Telegram/SourceFiles/layout/abstract_layout_item.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/runtime_composer.h"
|
||||
#include "ui/click_handler.h"
|
||||
|
||||
class PaintContextBase {
|
||||
public:
|
||||
PaintContextBase(crl::time ms, bool selecting)
|
||||
: ms(ms)
|
||||
, selecting(selecting) {
|
||||
}
|
||||
crl::time ms = 0;
|
||||
bool selecting = false;
|
||||
|
||||
};
|
||||
|
||||
class AbstractLayoutItem
|
||||
: public RuntimeComposer<AbstractLayoutItem>
|
||||
, public ClickHandlerHost {
|
||||
public:
|
||||
AbstractLayoutItem();
|
||||
|
||||
AbstractLayoutItem(const AbstractLayoutItem &other) = delete;
|
||||
AbstractLayoutItem &operator=(
|
||||
const AbstractLayoutItem &other) = delete;
|
||||
|
||||
[[nodiscard]] int maxWidth() const;
|
||||
[[nodiscard]] int minHeight() const;
|
||||
virtual int resizeGetHeight(int width);
|
||||
|
||||
[[nodiscard]] int width() const;
|
||||
[[nodiscard]] int height() const;
|
||||
|
||||
virtual void setPosition(int position);
|
||||
[[nodiscard]] int position() const;
|
||||
|
||||
void setShiftX(int x);
|
||||
void setShiftY(int y);
|
||||
[[nodiscard]] QPoint shift() const;
|
||||
|
||||
[[nodiscard]] bool hasPoint(QPoint point) const;
|
||||
|
||||
virtual ~AbstractLayoutItem();
|
||||
|
||||
protected:
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _maxw = 0;
|
||||
int _minh = 0;
|
||||
int _position = 0; // < 0 means removed from layout
|
||||
int _shiftX = 0;
|
||||
int _shiftY = 0;
|
||||
|
||||
};
|
||||
120
Telegram/SourceFiles/layout/layout_document_generic_preview.cpp
Normal file
120
Telegram/SourceFiles/layout/layout_document_generic_preview.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/layout_document_generic_preview.h"
|
||||
|
||||
#include "data/data_document.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_media_view.h"
|
||||
|
||||
namespace Layout {
|
||||
|
||||
const style::icon *DocumentGenericPreview::icon() const {
|
||||
switch (index) {
|
||||
case 0: return &st::mediaviewFileBlue;
|
||||
case 1: return &st::mediaviewFileGreen;
|
||||
case 2: return &st::mediaviewFileRed;
|
||||
case 3: return &st::mediaviewFileYellow;
|
||||
}
|
||||
Unexpected("Color index in DocumentGenericPreview::icon.");
|
||||
}
|
||||
|
||||
DocumentGenericPreview DocumentGenericPreview::Create(
|
||||
DocumentData *document) {
|
||||
auto colorIndex = 0;
|
||||
|
||||
const auto name = (document
|
||||
? (document->filename().isEmpty()
|
||||
? (document->sticker()
|
||||
? tr::lng_in_dlg_sticker(tr::now)
|
||||
: u"Unknown File"_q)
|
||||
: document->filename())
|
||||
: tr::lng_message_empty(tr::now)).toLower();
|
||||
auto lastDot = name.lastIndexOf('.');
|
||||
const auto mime = document ? document->mimeString() : QString();
|
||||
if (name.endsWith(u".doc"_q) ||
|
||||
name.endsWith(u".docx"_q) ||
|
||||
name.endsWith(u".txt"_q) ||
|
||||
name.endsWith(u".psd"_q) ||
|
||||
mime.startsWith(u"text/"_q)) {
|
||||
colorIndex = 0;
|
||||
} else if (
|
||||
name.endsWith(u".xls"_q) ||
|
||||
name.endsWith(u".xlsx"_q) ||
|
||||
name.endsWith(u".csv"_q)) {
|
||||
colorIndex = 1;
|
||||
} else if (
|
||||
name.endsWith(u".pdf"_q) ||
|
||||
name.endsWith(u".ppt"_q) ||
|
||||
name.endsWith(u".pptx"_q) ||
|
||||
name.endsWith(u".key"_q)) {
|
||||
colorIndex = 2;
|
||||
} else if (
|
||||
name.endsWith(u".zip"_q) ||
|
||||
name.endsWith(u".rar"_q) ||
|
||||
name.endsWith(u".ai"_q) ||
|
||||
name.endsWith(u".mp3"_q) ||
|
||||
name.endsWith(u".mov"_q) ||
|
||||
name.endsWith(u".avi"_q)) {
|
||||
colorIndex = 3;
|
||||
} else {
|
||||
auto ch = (lastDot >= 0 && lastDot + 1 < name.size())
|
||||
? name.at(lastDot + 1)
|
||||
: (name.isEmpty()
|
||||
? (mime.isEmpty() ? '0' : mime.at(0))
|
||||
: name.at(0));
|
||||
colorIndex = (ch.unicode() % 4) & 3;
|
||||
}
|
||||
|
||||
const auto ext = document
|
||||
? ((lastDot < 0 || lastDot + 2 > name.size())
|
||||
? name
|
||||
: name.mid(lastDot + 1))
|
||||
: QString();
|
||||
|
||||
switch (colorIndex) {
|
||||
case 0: return {
|
||||
.index = colorIndex,
|
||||
.color = st::msgFile1Bg,
|
||||
.dark = st::msgFile1BgDark,
|
||||
.over = st::msgFile1BgOver,
|
||||
.selected = st::msgFile1BgSelected,
|
||||
.ext = ext,
|
||||
};
|
||||
case 1: return {
|
||||
.index = colorIndex,
|
||||
.color = st::msgFile2Bg,
|
||||
.dark = st::msgFile2BgDark,
|
||||
.over = st::msgFile2BgOver,
|
||||
.selected = st::msgFile2BgSelected,
|
||||
.ext = ext,
|
||||
};
|
||||
case 2: return {
|
||||
.index = colorIndex,
|
||||
.color = st::msgFile3Bg,
|
||||
.dark = st::msgFile3BgDark,
|
||||
.over = st::msgFile3BgOver,
|
||||
.selected = st::msgFile3BgSelected,
|
||||
.ext = ext,
|
||||
};
|
||||
case 3: return {
|
||||
.index = colorIndex,
|
||||
.color = st::msgFile4Bg,
|
||||
.dark = st::msgFile4BgDark,
|
||||
.over = st::msgFile4BgOver,
|
||||
.selected = st::msgFile4BgSelected,
|
||||
.ext = ext,
|
||||
};
|
||||
}
|
||||
Unexpected("Color index in CreateDocumentGenericPreview.");
|
||||
}
|
||||
|
||||
// Ui::CachedRoundCorners DocumentCorners(int32 colorIndex) {
|
||||
// return Ui::CachedRoundCorners(Ui::Doc1Corners + (colorIndex & 3));
|
||||
// }
|
||||
|
||||
} // namespace Layout
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Layout {
|
||||
|
||||
struct DocumentGenericPreview final {
|
||||
static DocumentGenericPreview Create(DocumentData *document);
|
||||
const style::icon *icon() const;
|
||||
const int index;
|
||||
const style::color &color;
|
||||
const style::color &dark;
|
||||
const style::color &over;
|
||||
const style::color &selected;
|
||||
const QString ext;
|
||||
};
|
||||
|
||||
// Ui::CachedRoundCorners DocumentCorners(int colorIndex);
|
||||
|
||||
} // namespace Layout
|
||||
22
Telegram/SourceFiles/layout/layout_item_base.cpp
Normal file
22
Telegram/SourceFiles/layout/layout_item_base.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/layout_item_base.h"
|
||||
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
|
||||
[[nodiscard]] HistoryView::TextState LayoutItemBase::getState(
|
||||
QPoint point,
|
||||
StateRequest request) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] TextSelection LayoutItemBase::adjustSelection(
|
||||
TextSelection selection,
|
||||
TextSelectType type) const {
|
||||
return selection;
|
||||
}
|
||||
33
Telegram/SourceFiles/layout/layout_item_base.h
Normal file
33
Telegram/SourceFiles/layout/layout_item_base.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "layout/abstract_layout_item.h"
|
||||
|
||||
namespace HistoryView {
|
||||
struct TextState;
|
||||
struct StateRequest;
|
||||
} // namespace HistoryView
|
||||
|
||||
class LayoutItemBase : public AbstractLayoutItem {
|
||||
public:
|
||||
using TextState = HistoryView::TextState;
|
||||
using StateRequest = HistoryView::StateRequest;
|
||||
|
||||
using AbstractLayoutItem::AbstractLayoutItem;
|
||||
|
||||
virtual void initDimensions() = 0;
|
||||
|
||||
[[nodiscard]] virtual TextState getState(
|
||||
QPoint point,
|
||||
StateRequest request) const;
|
||||
[[nodiscard]] virtual TextSelection adjustSelection(
|
||||
TextSelection selection,
|
||||
TextSelectType type) const;
|
||||
|
||||
};
|
||||
391
Telegram/SourceFiles/layout/layout_mosaic.cpp
Normal file
391
Telegram/SourceFiles/layout/layout_mosaic.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/layout_mosaic.h"
|
||||
|
||||
#include "styles/style_chat_helpers.h"
|
||||
|
||||
namespace Mosaic::Layout {
|
||||
|
||||
AbstractMosaicLayout::AbstractMosaicLayout(int bigWidth)
|
||||
: _bigWidth(bigWidth) {
|
||||
}
|
||||
|
||||
int AbstractMosaicLayout::rowHeightAt(int row) const {
|
||||
Expects(row >= 0 && row < _rows.size());
|
||||
|
||||
return _rows[row].height;
|
||||
}
|
||||
|
||||
int AbstractMosaicLayout::countDesiredHeight(int newWidth) {
|
||||
auto result = 0;
|
||||
for (auto &row : _rows) {
|
||||
layoutRow(row, newWidth ? newWidth : _width);
|
||||
result += row.height;
|
||||
}
|
||||
return _padding.top() + result + _padding.bottom();
|
||||
}
|
||||
|
||||
FoundItem AbstractMosaicLayout::findByPoint(const QPoint &globalPoint) const {
|
||||
auto sx = globalPoint.x() - _padding.left();
|
||||
auto sy = globalPoint.y() - _padding.top();
|
||||
auto row = -1;
|
||||
auto col = -1;
|
||||
auto sel = -1;
|
||||
bool exact = true;
|
||||
if (sy >= 0) {
|
||||
row = 0;
|
||||
for (auto rows = rowsCount(); row < rows; ++row) {
|
||||
const auto rowHeight = _rows[row].height;
|
||||
if (sy < rowHeight) {
|
||||
break;
|
||||
}
|
||||
sy -= rowHeight;
|
||||
}
|
||||
} else {
|
||||
row = 0;
|
||||
exact = false;
|
||||
}
|
||||
if (row >= rowsCount()) {
|
||||
row = rowsCount() - 1;
|
||||
exact = false;
|
||||
}
|
||||
if (sx < 0) {
|
||||
sx = 0;
|
||||
exact = false;
|
||||
}
|
||||
if (sx >= 0 && row >= 0 && row < rowsCount()) {
|
||||
const auto columnsCount = _rows[row].items.size();
|
||||
col = 0;
|
||||
for (int cols = columnsCount; col < cols; ++col) {
|
||||
const auto item = itemAt(row, col);
|
||||
const auto width = item->width();
|
||||
if (sx < width) {
|
||||
break;
|
||||
}
|
||||
sx -= width;
|
||||
sx -= _rightSkip;
|
||||
}
|
||||
if (col >= columnsCount) {
|
||||
col = columnsCount - 1;
|
||||
exact = false;
|
||||
}
|
||||
|
||||
sel = ::Layout::PositionToIndex(row, + col);
|
||||
} else {
|
||||
row = col = -1;
|
||||
}
|
||||
return { sel, exact, QPoint(sx, sy) };
|
||||
}
|
||||
|
||||
QRect AbstractMosaicLayout::findRect(int index) const {
|
||||
const auto clip = QRect(0, 0, _width, 100);
|
||||
const auto fromX = style::RightToLeft()
|
||||
? (_width - clip.x() - clip.width())
|
||||
: clip.x();
|
||||
const auto toX = style::RightToLeft()
|
||||
? (_width - clip.x())
|
||||
: (clip.x() + clip.width());
|
||||
const auto rows = _rows.size();
|
||||
auto top = 0;
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
auto &inlineRow = _rows[row];
|
||||
// if ((top + inlineRow.height) > clip.top()) {
|
||||
auto left = 0;
|
||||
if (row == (rows - 1)) {
|
||||
// context.lastRow = true;
|
||||
}
|
||||
for (const auto &item : inlineRow.items) {
|
||||
if (left >= toX) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto w = item->width();
|
||||
if ((left + w) > fromX) {
|
||||
if (item->position() == index) {
|
||||
return QRect(
|
||||
left + _padding.left(),
|
||||
top + _padding.top(),
|
||||
item->width(),
|
||||
item->height());
|
||||
}
|
||||
}
|
||||
left += w;
|
||||
left += _rightSkip;
|
||||
}
|
||||
// }
|
||||
top += inlineRow.height;
|
||||
}
|
||||
return QRect();
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::addItems(
|
||||
gsl::span<const not_null<AbstractLayoutItem*>> items) {
|
||||
_rows.reserve(items.size());
|
||||
auto row = Row();
|
||||
row.items.reserve(kInlineItemsMaxPerRow);
|
||||
auto sumWidth = 0;
|
||||
for (const auto &item : items) {
|
||||
addItem(item, row, sumWidth);
|
||||
}
|
||||
rowFinalize(row, sumWidth, true);
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::setRightSkip(int rightSkip) {
|
||||
_rightSkip = rightSkip;
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::setPadding(QMargins padding) {
|
||||
_padding = padding;
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::setFullWidth(int w) {
|
||||
_width = w;
|
||||
}
|
||||
|
||||
bool AbstractMosaicLayout::empty() const {
|
||||
return _rows.empty();
|
||||
}
|
||||
|
||||
int AbstractMosaicLayout::rowsCount() const {
|
||||
return _rows.size();
|
||||
}
|
||||
|
||||
not_null<AbstractLayoutItem*> AbstractMosaicLayout::itemAt(
|
||||
int row,
|
||||
int column) const {
|
||||
Expects((row >= 0)
|
||||
&& (row < _rows.size())
|
||||
&& (column >= 0)
|
||||
&& (column < _rows[row].items.size()));
|
||||
|
||||
return _rows[row].items[column];
|
||||
}
|
||||
|
||||
not_null<AbstractLayoutItem*> AbstractMosaicLayout::itemAt(int index) const {
|
||||
const auto &[row, column] = ::Layout::IndexToPosition(index);
|
||||
return itemAt(row, column);
|
||||
}
|
||||
|
||||
AbstractLayoutItem *AbstractMosaicLayout::maybeItemAt(
|
||||
int row,
|
||||
int column) const {
|
||||
if ((row >= 0)
|
||||
&& (row < _rows.size())
|
||||
&& (column >= 0)
|
||||
&& (column < _rows[row].items.size())) {
|
||||
return _rows[row].items[column];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AbstractLayoutItem *AbstractMosaicLayout::maybeItemAt(int index) const {
|
||||
const auto &[row, column] = ::Layout::IndexToPosition(index);
|
||||
return maybeItemAt(row, column);
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::clearRows(bool resultsDeleted) {
|
||||
if (!resultsDeleted) {
|
||||
for (const auto &row : _rows) {
|
||||
for (const auto &item : row.items) {
|
||||
item->setPosition(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_rows.clear();
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::forEach(
|
||||
Fn<void(not_null<const AbstractLayoutItem*>)> callback) {
|
||||
for (const auto &row : _rows) {
|
||||
for (const auto &item : row.items) {
|
||||
callback(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::paint(
|
||||
Fn<void(not_null<AbstractLayoutItem*>, QPoint)> paintItem,
|
||||
const QRect &clip) const {
|
||||
auto top = _padding.top();
|
||||
const auto fromX = style::RightToLeft()
|
||||
? (_width - clip.x() - clip.width())
|
||||
: clip.x();
|
||||
const auto toX = style::RightToLeft()
|
||||
? (_width - clip.x())
|
||||
: (clip.x() + clip.width());
|
||||
const auto rows = _rows.size();
|
||||
for (auto row = 0; row != rows; ++row) {
|
||||
if (top >= clip.top() + clip.height()) {
|
||||
break;
|
||||
}
|
||||
auto &inlineRow = _rows[row];
|
||||
if ((top + inlineRow.height) > clip.top()) {
|
||||
auto left = _padding.left();
|
||||
if (row == (rows - 1)) {
|
||||
// context.lastRow = true;
|
||||
}
|
||||
for (const auto &item : inlineRow.items) {
|
||||
if (left >= toX) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto w = item->width();
|
||||
if ((left + w) > fromX) {
|
||||
paintItem(item, QPoint(left, top));
|
||||
}
|
||||
left += w;
|
||||
left += _rightSkip;
|
||||
}
|
||||
}
|
||||
top += inlineRow.height;
|
||||
}
|
||||
}
|
||||
|
||||
int AbstractMosaicLayout::validateExistingRows(
|
||||
Fn<bool(not_null<const AbstractLayoutItem*>, int)> checkItem,
|
||||
int count) {
|
||||
auto until = 0;
|
||||
auto untilRow = 0;
|
||||
auto untilCol = 0;
|
||||
while (until < count) {
|
||||
if ((untilRow >= _rows.size())
|
||||
|| checkItem(_rows[untilRow].items[untilCol], until)) {
|
||||
break;
|
||||
}
|
||||
++until;
|
||||
if (++untilCol == _rows[untilRow].items.size()) {
|
||||
++untilRow;
|
||||
untilCol = 0;
|
||||
}
|
||||
}
|
||||
if (until == count) { // All items are layed out.
|
||||
if (untilRow == _rows.size()) { // Nothing changed.
|
||||
return until;
|
||||
}
|
||||
|
||||
{
|
||||
const auto rows = _rows.size();
|
||||
auto skip = untilCol;
|
||||
for (auto i = untilRow; i < rows; ++i) {
|
||||
for (const auto &item : _rows[i].items) {
|
||||
if (skip) {
|
||||
--skip;
|
||||
} else {
|
||||
item->setPosition(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!untilCol) { // All good rows are filled.
|
||||
_rows.resize(untilRow);
|
||||
return until;
|
||||
}
|
||||
_rows.resize(untilRow + 1);
|
||||
_rows[untilRow].items.resize(untilCol);
|
||||
_rows[untilRow].maxWidth = ranges::accumulate(
|
||||
_rows[untilRow].items,
|
||||
0,
|
||||
[](int w, auto &row) { return w + row->maxWidth(); });
|
||||
layoutRow(_rows[untilRow], _width);
|
||||
return until;
|
||||
}
|
||||
if (untilRow && !untilCol) { // Remove last row, maybe it is not full.
|
||||
--untilRow;
|
||||
untilCol = _rows[untilRow].items.size();
|
||||
}
|
||||
until -= untilCol;
|
||||
|
||||
for (auto i = untilRow; i < _rows.size(); ++i) {
|
||||
for (const auto &item : _rows[i].items) {
|
||||
item->setPosition(-1);
|
||||
}
|
||||
}
|
||||
_rows.resize(untilRow);
|
||||
|
||||
return until;
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::addItem(
|
||||
not_null<AbstractLayoutItem*> item,
|
||||
Row &row,
|
||||
int &sumWidth) {
|
||||
// item->preload();
|
||||
|
||||
using namespace ::Layout;
|
||||
item->setPosition(PositionToIndex(_rows.size(), row.items.size()));
|
||||
if (rowFinalize(row, sumWidth, false)) {
|
||||
item->setPosition(PositionToIndex(_rows.size(), 0));
|
||||
}
|
||||
|
||||
sumWidth += item->maxWidth();
|
||||
if (!row.items.empty() && _rightSkip) {
|
||||
sumWidth += _rightSkip;
|
||||
}
|
||||
|
||||
row.items.push_back(item);
|
||||
}
|
||||
|
||||
bool AbstractMosaicLayout::rowFinalize(Row &row, int &sumWidth, bool force) {
|
||||
if (row.items.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto full = (row.items.size() >= kInlineItemsMaxPerRow);
|
||||
// Currently use the same GIFs layout for all widget sizes.
|
||||
const auto big = (sumWidth >= _bigWidth);
|
||||
if (full || big || force) {
|
||||
row.maxWidth = (full || big) ? sumWidth : 0;
|
||||
layoutRow(row, _width);
|
||||
_rows.push_back(std::move(row));
|
||||
row = Row();
|
||||
row.items.reserve(kInlineItemsMaxPerRow);
|
||||
sumWidth = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AbstractMosaicLayout::layoutRow(Row &row, int fullWidth) {
|
||||
const auto count = int(row.items.size());
|
||||
Assert(count <= kInlineItemsMaxPerRow);
|
||||
|
||||
// Enumerate items in the order of growing maxWidth()
|
||||
// for that sort item indices by maxWidth().
|
||||
int indices[kInlineItemsMaxPerRow];
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
indices[i] = i;
|
||||
}
|
||||
std::sort(indices, indices + count, [&](int a, int b) {
|
||||
return row.items[a]->maxWidth() < row.items[b]->maxWidth();
|
||||
});
|
||||
|
||||
auto desiredWidth = row.maxWidth;
|
||||
row.height = 0;
|
||||
auto availableWidth = fullWidth - _padding.left() - _padding.right();
|
||||
for (auto i = 0; i < count; ++i) {
|
||||
const auto index = indices[i];
|
||||
const auto &item = row.items[index];
|
||||
const auto w = desiredWidth
|
||||
? (item->maxWidth() * availableWidth / desiredWidth)
|
||||
: item->maxWidth();
|
||||
const auto actualWidth = std::max(w, st::inlineResultsMinWidth);
|
||||
row.height = std::max(
|
||||
row.height,
|
||||
item->resizeGetHeight(actualWidth));
|
||||
if (desiredWidth) {
|
||||
availableWidth -= actualWidth;
|
||||
desiredWidth -= row.items[index]->maxWidth();
|
||||
if (index > 0 && _rightSkip) {
|
||||
availableWidth -= _rightSkip;
|
||||
desiredWidth -= _rightSkip;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Mosaic::Layout
|
||||
156
Telegram/SourceFiles/layout/layout_mosaic.h
Normal file
156
Telegram/SourceFiles/layout/layout_mosaic.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "layout/abstract_layout_item.h"
|
||||
#include "layout/layout_position.h"
|
||||
|
||||
namespace Mosaic::Layout {
|
||||
|
||||
struct FoundItem {
|
||||
int index = -1;
|
||||
bool exact = false;
|
||||
QPoint relative;
|
||||
};
|
||||
|
||||
class AbstractMosaicLayout {
|
||||
public:
|
||||
AbstractMosaicLayout(int bigWidth);
|
||||
|
||||
[[nodiscard]] int rowHeightAt(int row) const;
|
||||
[[nodiscard]] int countDesiredHeight(int newWidth);
|
||||
|
||||
[[nodiscard]] FoundItem findByPoint(const QPoint &globalPoint) const;
|
||||
|
||||
[[nodiscard]] QRect findRect(int index) const;
|
||||
|
||||
void setRightSkip(int rightSkip);
|
||||
void setPadding(QMargins padding);
|
||||
void setFullWidth(int w);
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] int rowsCount() const;
|
||||
|
||||
void clearRows(bool resultsDeleted);
|
||||
|
||||
protected:
|
||||
void addItems(gsl::span<const not_null<AbstractLayoutItem*>> items);
|
||||
|
||||
[[nodiscard]] not_null<AbstractLayoutItem*> itemAt(int row, int column) const;
|
||||
[[nodiscard]] not_null<AbstractLayoutItem*> itemAt(int index) const;
|
||||
|
||||
[[nodiscard]] AbstractLayoutItem *maybeItemAt(int row, int column) const;
|
||||
[[nodiscard]] AbstractLayoutItem *maybeItemAt(int index) const;
|
||||
|
||||
void forEach(Fn<void(not_null<const AbstractLayoutItem*>)> callback);
|
||||
|
||||
void paint(
|
||||
Fn<void(not_null<AbstractLayoutItem*>, QPoint)> paintItem,
|
||||
const QRect &clip) const;
|
||||
int validateExistingRows(
|
||||
Fn<bool(not_null<const AbstractLayoutItem*>, int)> checkItem,
|
||||
int count);
|
||||
|
||||
private:
|
||||
static constexpr auto kInlineItemsMaxPerRow = 5;
|
||||
struct Row {
|
||||
int maxWidth = 0;
|
||||
int height = 0;
|
||||
std::vector<AbstractLayoutItem*> items;
|
||||
};
|
||||
|
||||
void addItem(not_null<AbstractLayoutItem*> item, Row &row, int &sumWidth);
|
||||
bool rowFinalize(Row &row, int &sumWidth, bool force);
|
||||
void layoutRow(Row &row, int fullWidth);
|
||||
|
||||
int _bigWidth;
|
||||
int _width = 0;
|
||||
int _rightSkip = 0;
|
||||
QMargins _padding;
|
||||
std::vector<Row> _rows;
|
||||
|
||||
};
|
||||
|
||||
template <
|
||||
typename ItemBase,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<AbstractLayoutItem, ItemBase>>>
|
||||
class MosaicLayout final : public AbstractMosaicLayout {
|
||||
using Parent = AbstractMosaicLayout;
|
||||
|
||||
public:
|
||||
using Parent::Parent;
|
||||
|
||||
void addItems(const std::vector<not_null<ItemBase*>> &items) {
|
||||
Parent::addItems({
|
||||
reinterpret_cast<const not_null<AbstractLayoutItem*>*>(
|
||||
items.data()),
|
||||
items.size() });
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<ItemBase*> itemAt(int row, int column) const {
|
||||
return Downcast(Parent::itemAt(row, column));
|
||||
}
|
||||
[[nodiscard]] not_null<ItemBase*> itemAt(int index) const {
|
||||
return Downcast(Parent::itemAt(index));
|
||||
}
|
||||
[[nodiscard]] ItemBase *maybeItemAt(int row, int column) const {
|
||||
return Downcast(Parent::maybeItemAt(row, column));
|
||||
}
|
||||
[[nodiscard]] ItemBase *maybeItemAt(int index) const {
|
||||
return Downcast(Parent::maybeItemAt(index));
|
||||
}
|
||||
|
||||
void forEach(Fn<void(not_null<const ItemBase*>)> callback) {
|
||||
Parent::forEach([&](
|
||||
not_null<const AbstractLayoutItem*> item) {
|
||||
callback(Downcast(item));
|
||||
});
|
||||
}
|
||||
|
||||
void paint(
|
||||
Fn<void(not_null<ItemBase*>, QPoint)> paintItem,
|
||||
const QRect &clip) const {
|
||||
Parent::paint([&](
|
||||
not_null<AbstractLayoutItem*> item,
|
||||
QPoint point) {
|
||||
paintItem(Downcast(item), point);
|
||||
}, clip);
|
||||
}
|
||||
|
||||
int validateExistingRows(
|
||||
Fn<bool(not_null<const ItemBase*>, int)> checkItem,
|
||||
int count) {
|
||||
return Parent::validateExistingRows([&](
|
||||
not_null<const AbstractLayoutItem*> item,
|
||||
int until) {
|
||||
return checkItem(Downcast(item), until);
|
||||
}, count);
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] static not_null<ItemBase*> Downcast(
|
||||
not_null<AbstractLayoutItem*> item) {
|
||||
return static_cast<ItemBase*>(item.get());
|
||||
}
|
||||
[[nodiscard]] static ItemBase *Downcast(
|
||||
AbstractLayoutItem *item) {
|
||||
return static_cast<ItemBase*>(item);
|
||||
}
|
||||
[[nodiscard]] static not_null<const ItemBase*> Downcast(
|
||||
not_null<const AbstractLayoutItem*> item) {
|
||||
return static_cast<const ItemBase*>(item.get());
|
||||
}
|
||||
[[nodiscard]] static const ItemBase *Downcast(
|
||||
const AbstractLayoutItem *item) {
|
||||
return static_cast<const ItemBase*>(item);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Mosaic::Layout
|
||||
31
Telegram/SourceFiles/layout/layout_position.cpp
Normal file
31
Telegram/SourceFiles/layout/layout_position.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/layout_position.h"
|
||||
|
||||
namespace Layout {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMatrixRowShift = 40000;
|
||||
|
||||
} // namespace
|
||||
|
||||
Layout::Position IndexToPosition(int index) {
|
||||
return {
|
||||
(index >= 0) ? (index / kMatrixRowShift) : -1,
|
||||
(index >= 0) ? (index % kMatrixRowShift) : -1 };
|
||||
}
|
||||
|
||||
int PositionToIndex(int row, int column) {
|
||||
return row * kMatrixRowShift + column;
|
||||
}
|
||||
|
||||
int PositionToIndex(const Layout::Position &position) {
|
||||
return PositionToIndex(position.row, position.column);
|
||||
}
|
||||
|
||||
} // namespace Layout
|
||||
21
Telegram/SourceFiles/layout/layout_position.h
Normal file
21
Telegram/SourceFiles/layout/layout_position.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Layout {
|
||||
|
||||
struct Position {
|
||||
int row = -1;
|
||||
int column = -1;
|
||||
};
|
||||
|
||||
[[nodiscard]] Position IndexToPosition(int index);
|
||||
[[nodiscard]] int PositionToIndex(int row, int column);
|
||||
[[nodiscard]] int PositionToIndex(const Position &position);
|
||||
|
||||
} // namespace Layout
|
||||
42
Telegram/SourceFiles/layout/layout_selection.cpp
Normal file
42
Telegram/SourceFiles/layout/layout_selection.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "layout/layout_selection.h"
|
||||
|
||||
bool IsSubGroupSelection(TextSelection selection) {
|
||||
return (selection.from == 0xFFFF) && (selection.to != 0xFFFF);
|
||||
}
|
||||
|
||||
bool IsGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index) {
|
||||
Expects(index >= 0 && index < 0x0F);
|
||||
|
||||
return IsSubGroupSelection(selection) && (selection.to & (1 << index));
|
||||
}
|
||||
|
||||
TextSelection AddGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index) {
|
||||
Expects(index >= 0 && index < 0x0F);
|
||||
|
||||
const auto bit = uint16(1U << index);
|
||||
return TextSelection(
|
||||
0xFFFF,
|
||||
IsSubGroupSelection(selection) ? (selection.to | bit) : bit);
|
||||
}
|
||||
|
||||
TextSelection RemoveGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index) {
|
||||
Expects(index >= 0 && index < 0x0F);
|
||||
|
||||
const auto bit = uint16(1U << index);
|
||||
return IsSubGroupSelection(selection)
|
||||
? TextSelection(0xFFFF, selection.to & ~bit)
|
||||
: selection;
|
||||
}
|
||||
26
Telegram/SourceFiles/layout/layout_selection.h
Normal file
26
Telegram/SourceFiles/layout/layout_selection.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/text/text.h"
|
||||
|
||||
constexpr auto FullSelection = TextSelection { 0xFFFF, 0xFFFF };
|
||||
|
||||
[[nodiscard]] bool IsSubGroupSelection(TextSelection selection);
|
||||
|
||||
[[nodiscard]] bool IsGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index);
|
||||
|
||||
[[nodiscard]] TextSelection AddGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index);
|
||||
|
||||
[[nodiscard]] TextSelection RemoveGroupItemSelection(
|
||||
TextSelection selection,
|
||||
int index);
|
||||
Reference in New Issue
Block a user