| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- /*
- 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
|