table_layout.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/wrap/table_layout.h"
  8. #include "ui/painter.h"
  9. #include "ui/ui_utility.h"
  10. #include "styles/style_widgets.h"
  11. #include <QtGui/QPainterPath>
  12. namespace Ui {
  13. TableLayout::TableLayout(QWidget *parent, const style::Table &st)
  14. : RpWidget(parent)
  15. , _st(st) {
  16. }
  17. void TableLayout::paintEvent(QPaintEvent *e) {
  18. if (_rows.empty()) {
  19. return;
  20. }
  21. auto p = QPainter(this);
  22. auto hq = PainterHighQualityEnabler(p);
  23. const auto half = _st.border / 2.;
  24. const auto inner = QRectF(rect()).marginsRemoved(
  25. { half, half, half, half });
  26. auto labels = QRegion();
  27. auto yfrom = half;
  28. auto ytill = height() - half;
  29. for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
  30. const auto &row = _rows[i];
  31. yfrom = row.top + half;
  32. if (!row.value) {
  33. const auto till = (i + 1 == count)
  34. ? (inner.y() + inner.height())
  35. : (_rows[i + 1].top - half);
  36. labels += QRect(inner.x(), yfrom, inner.width(), till - yfrom);
  37. } else if (row.label) {
  38. break;
  39. }
  40. }
  41. for (auto i = 0, count = int(_rows.size()); i != count; ++i) {
  42. const auto index = count - i - 1;
  43. const auto &row = _rows[index];
  44. if (!row.value) {
  45. const auto from = row.top + half;
  46. labels += QRect(inner.x(), from, inner.width(), ytill - from);
  47. } else if (row.label) {
  48. break;
  49. }
  50. ytill = row.top - half;
  51. }
  52. if (ytill > yfrom) {
  53. labels += QRect(0, yfrom, _valueLeft, ytill - yfrom);
  54. }
  55. if (!labels.isEmpty()) {
  56. p.setClipRegion(labels);
  57. p.setBrush(_st.headerBg);
  58. p.setPen(Qt::NoPen);
  59. p.drawRoundedRect(inner, _st.radius, _st.radius);
  60. p.setClipping(false);
  61. }
  62. auto path = QPainterPath();
  63. path.addRoundedRect(inner, _st.radius, _st.radius);
  64. for (auto i = 1, count = int(_rows.size()); i != count; ++i) {
  65. const auto y = _rows[i].top - half;
  66. path.moveTo(half, y);
  67. path.lineTo(width() - half, y);
  68. }
  69. if (ytill > yfrom) {
  70. path.moveTo(_valueLeft - half, yfrom);
  71. path.lineTo(_valueLeft - half, ytill);
  72. }
  73. auto pen = _st.borderFg->p;
  74. pen.setWidth(_st.border);
  75. p.setPen(pen);
  76. p.setBrush(Qt::NoBrush);
  77. p.drawPath(path);
  78. }
  79. int TableLayout::resizeGetHeight(int newWidth) {
  80. _inResize = true;
  81. auto guard = gsl::finally([&] { _inResize = false; });
  82. auto available = newWidth - 3 * _st.border;
  83. const auto labelMax = int(base::SafeRound(
  84. _st.labelMaxWidth * available));
  85. const auto valueMin = available - labelMax;
  86. if (labelMax <= 0 || valueMin <= 0 || _rows.empty()) {
  87. return 0;
  88. }
  89. auto label = _st.labelMinWidth;
  90. for (auto &row : _rows) {
  91. const auto natural = (row.label && row.value)
  92. ? (row.label->naturalWidth()
  93. + row.labelMargin.left()
  94. + row.labelMargin.right())
  95. : 0;
  96. if (natural < 0 || natural >= labelMax) {
  97. label = labelMax;
  98. break;
  99. } else if (natural > label) {
  100. label = natural;
  101. }
  102. }
  103. _valueLeft = _st.border * 2 + label;
  104. auto result = _st.border;
  105. for (auto &row : _rows) {
  106. updateRowGeometry(row, newWidth, result);
  107. result += rowVerticalSkip(row);
  108. }
  109. return result;
  110. }
  111. void TableLayout::visibleTopBottomUpdated(
  112. int visibleTop,
  113. int visibleBottom) {
  114. for (auto &row : _rows) {
  115. if (row.label) {
  116. setChildVisibleTopBottom(
  117. row.label,
  118. visibleTop,
  119. visibleBottom);
  120. }
  121. if (row.value) {
  122. setChildVisibleTopBottom(
  123. row.value,
  124. visibleTop,
  125. visibleBottom);
  126. }
  127. }
  128. }
  129. void TableLayout::updateRowGeometry(
  130. const Row &row,
  131. int width,
  132. int top) const {
  133. if (row.label && row.value) {
  134. row.label->resizeToNaturalWidth(_valueLeft
  135. - 2 * _st.border
  136. - row.labelMargin.left()
  137. - row.labelMargin.right());
  138. row.value->resizeToNaturalWidth(width
  139. - _valueLeft
  140. - _st.border
  141. - row.valueMargin.left()
  142. - row.valueMargin.right());
  143. } else if (row.label) {
  144. row.label->resizeToNaturalWidth(width
  145. - 2 * _st.border
  146. - row.labelMargin.left()
  147. - row.valueMargin.right());
  148. } else {
  149. row.value->resizeToNaturalWidth(width
  150. - 2 * _st.border
  151. - row.labelMargin.left()
  152. - row.valueMargin.right());
  153. }
  154. updateRowPosition(row, width, top);
  155. }
  156. void TableLayout::updateRowPosition(
  157. const Row &row,
  158. int width,
  159. int top) const {
  160. row.top = top;
  161. if (row.label && row.value) {
  162. row.label->moveToLeft(
  163. _st.border + row.labelMargin.left(),
  164. top + row.labelMargin.top(),
  165. width);
  166. row.value->moveToLeft(
  167. _valueLeft + row.valueMargin.left(),
  168. top + row.valueMargin.top(),
  169. width);
  170. } else if (row.label) {
  171. row.label->moveToLeft(
  172. _st.border + row.labelMargin.left(),
  173. top + row.valueMargin.top(),
  174. width);
  175. } else {
  176. row.value->moveToLeft(
  177. _st.border + row.labelMargin.left(),
  178. top + row.valueMargin.top(),
  179. width);
  180. }
  181. }
  182. void TableLayout::insertRow(
  183. int atPosition,
  184. object_ptr<RpWidget> &&label,
  185. object_ptr<RpWidget> &&value,
  186. const style::margins &labelMargin,
  187. const style::margins &valueMargin) {
  188. Expects(atPosition >= 0 && atPosition <= _rows.size());
  189. Expects(!_inResize);
  190. const auto wlabel = label ? AttachParentChild(this, label) : nullptr;
  191. const auto wvalue = value ? AttachParentChild(this, value) : nullptr;
  192. if (wlabel || wvalue) {
  193. _rows.insert(begin(_rows) + atPosition, {
  194. std::move(label),
  195. std::move(value),
  196. labelMargin,
  197. valueMargin,
  198. });
  199. if (wlabel) {
  200. wlabel->heightValue(
  201. ) | rpl::start_with_next_done([=] {
  202. if (!_inResize) {
  203. childHeightUpdated(wlabel);
  204. }
  205. }, [=] {
  206. removeChild(wlabel);
  207. }, _rowsLifetime);
  208. }
  209. if (wvalue) {
  210. wvalue->heightValue(
  211. ) | rpl::start_with_next_done([=] {
  212. if (!_inResize) {
  213. childHeightUpdated(wvalue);
  214. }
  215. }, [=] {
  216. removeChild(wvalue);
  217. }, _rowsLifetime);
  218. }
  219. }
  220. }
  221. void TableLayout::childHeightUpdated(RpWidget *child) {
  222. auto it = ranges::find_if(_rows, [child](const Row &row) {
  223. return (row.label == child) || (row.value == child);
  224. });
  225. const auto end = _rows.end();
  226. Assert(it != end);
  227. auto top = it->top;
  228. const auto outer = width();
  229. for (; it != end; ++it) {
  230. const auto &row = *it;
  231. updateRowPosition(row, outer, top);
  232. top += rowVerticalSkip(row);
  233. }
  234. resize(width(), _rows.empty() ? 0 : top);
  235. }
  236. void TableLayout::removeChild(RpWidget *child) {
  237. auto it = ranges::find_if(_rows, [child](const Row &row) {
  238. return (row.label == child) || (row.value == child);
  239. });
  240. const auto end = _rows.end();
  241. Assert(it != end);
  242. auto top = it->top;
  243. const auto outer = width();
  244. for (auto next = it + 1; next != end; ++next) {
  245. auto &row = *next;
  246. updateRowPosition(row, outer, top);
  247. top += rowVerticalSkip(row);
  248. }
  249. it->label = nullptr;
  250. it->value = nullptr;
  251. _rows.erase(it);
  252. resize(width(), _rows.empty() ? 0 : top);
  253. }
  254. int TableLayout::rowVerticalSkip(const Row &row) const {
  255. const auto labelHeight = row.label
  256. ? (row.labelMargin.top()
  257. + row.label->heightNoMargins()
  258. + row.labelMargin.bottom())
  259. : 0;
  260. const auto valueHeight = row.value
  261. ? (row.valueMargin.top()
  262. + row.value->heightNoMargins()
  263. + row.valueMargin.bottom())
  264. : 0;
  265. return std::max(labelHeight, valueHeight) + _st.border;
  266. }
  267. void TableLayout::clear() {
  268. while (!_rows.empty()) {
  269. const auto &row = _rows.front();
  270. removeChild(row.value ? row.value.data() : row.label.data());
  271. }
  272. }
  273. } // namespace Ui