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
314 lines
7.4 KiB
C++
314 lines
7.4 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/wrap/table_layout.h"
|
|
|
|
#include "ui/painter.h"
|
|
#include "ui/ui_utility.h"
|
|
#include "styles/style_widgets.h"
|
|
|
|
#include <QtGui/QPainterPath>
|
|
|
|
namespace Ui {
|
|
|
|
TableLayout::TableLayout(QWidget *parent, const style::Table &st)
|
|
: RpWidget(parent)
|
|
, _st(st) {
|
|
}
|
|
|
|
TableLayout::~TableLayout() {
|
|
_rowsLifetime.destroy();
|
|
|
|
auto taken = std::move(_rows);
|
|
for (auto &row : taken) {
|
|
row.label.destroy();
|
|
row.value.destroy();
|
|
}
|
|
}
|
|
|
|
void TableLayout::paintEvent(QPaintEvent *e) {
|
|
if (_rows.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto p = QPainter(this);
|
|
auto hq = PainterHighQualityEnabler(p);
|
|
|
|
const auto half = _st.border / 2.;
|
|
const auto inner = QRectF(rect()).marginsRemoved(
|
|
{ half, half, half, half });
|
|
|
|
auto labels = QRegion();
|
|
auto yfrom = half;
|
|
auto ytill = height() - half;
|
|
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
|
|
const auto &row = _rows[i];
|
|
yfrom = row.top + half;
|
|
if (!row.value) {
|
|
const auto till = (i + 1 == count)
|
|
? (inner.y() + inner.height())
|
|
: (_rows[i + 1].top - half);
|
|
labels += QRect(inner.x(), yfrom, inner.width(), till - yfrom);
|
|
} else if (row.label) {
|
|
break;
|
|
}
|
|
}
|
|
for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
|
|
const auto index = count - i - 1;
|
|
const auto &row = _rows[index];
|
|
if (!row.value) {
|
|
const auto from = row.top + half;
|
|
labels += QRect(inner.x(), from, inner.width(), ytill - from);
|
|
} else if (row.label) {
|
|
break;
|
|
}
|
|
ytill = row.top - half;
|
|
}
|
|
|
|
if (ytill > yfrom) {
|
|
labels += QRect(0, yfrom, _valueLeft, ytill - yfrom);
|
|
}
|
|
if (!labels.isEmpty()) {
|
|
p.setClipRegion(labels);
|
|
p.setBrush(_st.headerBg);
|
|
p.setPen(Qt::NoPen);
|
|
p.drawRoundedRect(inner, _st.radius, _st.radius);
|
|
p.setClipping(false);
|
|
}
|
|
|
|
auto path = QPainterPath();
|
|
path.addRoundedRect(inner, _st.radius, _st.radius);
|
|
for (auto i = 1, count = int(_rows.size()); i != count; ++i) {
|
|
const auto y = _rows[i].top - half;
|
|
path.moveTo(half, y);
|
|
path.lineTo(width() - half, y);
|
|
}
|
|
if (ytill > yfrom) {
|
|
path.moveTo(_valueLeft - half, yfrom);
|
|
path.lineTo(_valueLeft - half, ytill);
|
|
}
|
|
|
|
auto pen = _st.borderFg->p;
|
|
pen.setWidth(_st.border);
|
|
p.setPen(pen);
|
|
p.setBrush(Qt::NoBrush);
|
|
p.drawPath(path);
|
|
}
|
|
|
|
int TableLayout::resizeGetHeight(int newWidth) {
|
|
_inResize = true;
|
|
auto guard = gsl::finally([&] { _inResize = false; });
|
|
|
|
auto available = newWidth - 3 * _st.border;
|
|
const auto labelMax = int(base::SafeRound(
|
|
_st.labelMaxWidth * available));
|
|
const auto valueMin = available - labelMax;
|
|
if (labelMax <= 0 || valueMin <= 0 || _rows.empty()) {
|
|
return 0;
|
|
}
|
|
auto label = _st.labelMinWidth;
|
|
for (auto &row : _rows) {
|
|
const auto natural = (row.label && row.value)
|
|
? (row.label->naturalWidth()
|
|
+ row.labelMargin.left()
|
|
+ row.labelMargin.right())
|
|
: 0;
|
|
if (natural < 0 || natural >= labelMax) {
|
|
label = labelMax;
|
|
break;
|
|
} else if (natural > label) {
|
|
label = natural;
|
|
}
|
|
}
|
|
_valueLeft = _st.border * 2 + label;
|
|
|
|
auto result = _st.border;
|
|
for (auto &row : _rows) {
|
|
updateRowGeometry(row, newWidth, result);
|
|
result += rowVerticalSkip(row);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void TableLayout::visibleTopBottomUpdated(
|
|
int visibleTop,
|
|
int visibleBottom) {
|
|
for (auto &row : _rows) {
|
|
if (row.label) {
|
|
setChildVisibleTopBottom(
|
|
row.label,
|
|
visibleTop,
|
|
visibleBottom);
|
|
}
|
|
if (row.value) {
|
|
setChildVisibleTopBottom(
|
|
row.value,
|
|
visibleTop,
|
|
visibleBottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TableLayout::updateRowGeometry(
|
|
const Row &row,
|
|
int width,
|
|
int top) const {
|
|
if (row.label && row.value) {
|
|
row.label->resizeToNaturalWidth(_valueLeft
|
|
- 2 * _st.border
|
|
- row.labelMargin.left()
|
|
- row.labelMargin.right());
|
|
row.value->resizeToNaturalWidth(width
|
|
- _valueLeft
|
|
- _st.border
|
|
- row.valueMargin.left()
|
|
- row.valueMargin.right());
|
|
} else if (row.label) {
|
|
row.label->resizeToNaturalWidth(width
|
|
- 2 * _st.border
|
|
- row.labelMargin.left()
|
|
- row.valueMargin.right());
|
|
} else {
|
|
row.value->resizeToNaturalWidth(width
|
|
- 2 * _st.border
|
|
- row.labelMargin.left()
|
|
- row.valueMargin.right());
|
|
}
|
|
updateRowPosition(row, width, top);
|
|
}
|
|
|
|
void TableLayout::updateRowPosition(
|
|
const Row &row,
|
|
int width,
|
|
int top) const {
|
|
row.top = top;
|
|
if (row.label && row.value) {
|
|
row.label->moveToLeft(
|
|
_st.border + row.labelMargin.left(),
|
|
top + row.labelMargin.top(),
|
|
width);
|
|
row.value->moveToLeft(
|
|
_valueLeft + row.valueMargin.left(),
|
|
top + row.valueMargin.top(),
|
|
width);
|
|
} else if (row.label) {
|
|
row.label->moveToLeft(
|
|
_st.border + row.labelMargin.left(),
|
|
top + row.valueMargin.top(),
|
|
width);
|
|
} else {
|
|
row.value->moveToLeft(
|
|
_st.border + row.labelMargin.left(),
|
|
top + row.valueMargin.top(),
|
|
width);
|
|
}
|
|
}
|
|
|
|
void TableLayout::insertRow(
|
|
int atPosition,
|
|
object_ptr<RpWidget> &&label,
|
|
object_ptr<RpWidget> &&value,
|
|
const style::margins &labelMargin,
|
|
const style::margins &valueMargin) {
|
|
Expects(atPosition >= 0 && atPosition <= _rows.size());
|
|
Expects(!_inResize);
|
|
|
|
const auto wlabel = label ? AttachParentChild(this, label) : nullptr;
|
|
const auto wvalue = value ? AttachParentChild(this, value) : nullptr;
|
|
if (wlabel || wvalue) {
|
|
_rows.insert(begin(_rows) + atPosition, {
|
|
std::move(label),
|
|
std::move(value),
|
|
labelMargin,
|
|
valueMargin,
|
|
});
|
|
if (wlabel) {
|
|
wlabel->heightValue(
|
|
) | rpl::on_next_done([=] {
|
|
if (!_inResize) {
|
|
childHeightUpdated(wlabel);
|
|
}
|
|
}, [=] {
|
|
removeChild(wlabel);
|
|
}, _rowsLifetime);
|
|
}
|
|
if (wvalue) {
|
|
wvalue->heightValue(
|
|
) | rpl::on_next_done([=] {
|
|
if (!_inResize) {
|
|
childHeightUpdated(wvalue);
|
|
}
|
|
}, [=] {
|
|
removeChild(wvalue);
|
|
}, _rowsLifetime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TableLayout::childHeightUpdated(RpWidget *child) {
|
|
auto it = ranges::find_if(_rows, [child](const Row &row) {
|
|
return (row.label == child) || (row.value == child);
|
|
});
|
|
const auto end = _rows.end();
|
|
Assert(it != end);
|
|
|
|
auto top = it->top;
|
|
const auto outer = width();
|
|
for (; it != end; ++it) {
|
|
const auto &row = *it;
|
|
updateRowPosition(row, outer, top);
|
|
top += rowVerticalSkip(row);
|
|
}
|
|
resize(width(), _rows.empty() ? 0 : top);
|
|
}
|
|
|
|
void TableLayout::removeChild(RpWidget *child) {
|
|
const auto it = ranges::find_if(_rows, [child](const Row &row) {
|
|
return (row.label == child) || (row.value == child);
|
|
});
|
|
if (auto e = end(_rows); it != e) {
|
|
auto top = it->top;
|
|
auto removed = std::move(*it);
|
|
auto next = _rows.erase(it);
|
|
const auto outer = width();
|
|
for (e = end(_rows); next != e; ++next) {
|
|
auto &row = *next;
|
|
updateRowPosition(row, outer, top);
|
|
top += rowVerticalSkip(row);
|
|
}
|
|
resize(width(), _rows.empty() ? 0 : top);
|
|
|
|
if (removed.label.data() == child) {
|
|
removed.value.destroy();
|
|
} else {
|
|
removed.label.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
int TableLayout::rowVerticalSkip(const Row &row) const {
|
|
const auto labelHeight = row.label
|
|
? (row.labelMargin.top()
|
|
+ row.label->heightNoMargins()
|
|
+ row.labelMargin.bottom())
|
|
: 0;
|
|
const auto valueHeight = row.value
|
|
? (row.valueMargin.top()
|
|
+ row.value->heightNoMargins()
|
|
+ row.valueMargin.bottom())
|
|
: 0;
|
|
return std::max(labelHeight, valueHeight) + _st.border;
|
|
}
|
|
|
|
void TableLayout::clear() {
|
|
while (!_rows.empty()) {
|
|
const auto &row = _rows.front();
|
|
removeChild(row.value ? row.value.data() : row.label.data());
|
|
}
|
|
}
|
|
|
|
} // namespace Ui
|