A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Collections.Generic;
6using System.Linq;
7using Markdig.Extensions.Tables;
8using osu.Framework.Allocation;
9using osu.Framework.Layout;
10
11namespace osu.Framework.Graphics.Containers.Markdown
12{
13 /// <summary>
14 /// Visualises a table.
15 /// </summary>
16 /// <code>
17 /// | heading 1 | heading 2 |
18 /// | --------- | --------- |
19 /// | cell 1 | cell 2 |
20 /// </code>
21 public class MarkdownTable : CompositeDrawable
22 {
23 private GridContainer tableContainer;
24
25 private readonly Table table;
26
27 private readonly LayoutValue columnDefinitionCache = new LayoutValue(Invalidation.DrawSize, conditions: (s, _) => s.Parent != null);
28 private readonly LayoutValue rowDefinitionCache = new LayoutValue(Invalidation.DrawSize, conditions: (s, _) => s.Parent != null);
29
30 public MarkdownTable(Table table)
31 {
32 this.table = table;
33
34 AutoSizeAxes = Axes.Y;
35 RelativeSizeAxes = Axes.X;
36
37 table.NormalizeUsingHeaderRow();
38
39 AddLayout(columnDefinitionCache);
40 AddLayout(rowDefinitionCache);
41 }
42
43 [BackgroundDependencyLoader]
44 private void load()
45 {
46 List<List<Drawable>> rows = new List<List<Drawable>>();
47
48 foreach (var tableRow in table.OfType<TableRow>())
49 {
50 var row = new List<Drawable>();
51
52 for (int c = 0; c < tableRow.Count; c++)
53 {
54 var columnDimensions = table.ColumnDefinitions[c];
55 row.Add(CreateTableCell((TableCell)tableRow[c], columnDimensions, rows.Count == 0));
56 }
57
58 rows.Add(row);
59 }
60
61 InternalChild = tableContainer = new GridContainer
62 {
63 AutoSizeAxes = Axes.Y,
64 RelativeSizeAxes = Axes.X,
65 Content = rows.Select(x => x.ToArray()).ToArray()
66 };
67 }
68
69 protected override void Update()
70 {
71 base.Update();
72
73 if (!columnDefinitionCache.IsValid)
74 {
75 computeColumnDefinitions();
76 columnDefinitionCache.Validate();
77 }
78 }
79
80 protected override void UpdateAfterChildren()
81 {
82 base.UpdateAfterChildren();
83
84 if (!rowDefinitionCache.IsValid)
85 {
86 computeRowDefinitions();
87 rowDefinitionCache.Validate();
88 }
89 }
90
91 private void computeColumnDefinitions()
92 {
93 if (table.Count == 0)
94 return;
95
96 Span<float> columnWidths = stackalloc float[tableContainer.Content[0].Count];
97
98 // Compute the maximum width of each column
99 for (int r = 0; r < tableContainer.Content.Count; r++)
100 {
101 for (int c = 0; c < tableContainer.Content[r].Count; c++)
102 columnWidths[c] = Math.Max(columnWidths[c], ((MarkdownTableCell)tableContainer.Content[r][c]).ContentWidth);
103 }
104
105 float totalWidth = 0;
106 for (int i = 0; i < columnWidths.Length; i++)
107 totalWidth += columnWidths[i];
108
109 var columnDimensions = new Dimension[columnWidths.Length];
110
111 if (totalWidth < DrawWidth)
112 {
113 // The columns will fit within the table, use absolute column widths
114 for (int i = 0; i < columnWidths.Length; i++)
115 columnDimensions[i] = new Dimension(GridSizeMode.Absolute, columnWidths[i]);
116 }
117 else
118 {
119 // The columns will overflow the table, must convert them to a relative size
120 for (int i = 0; i < columnWidths.Length; i++)
121 columnDimensions[i] = new Dimension(GridSizeMode.Relative, columnWidths[i] / totalWidth);
122 }
123
124 tableContainer.ColumnDimensions = columnDimensions;
125 }
126
127 private void computeRowDefinitions()
128 {
129 if (table.Count == 0)
130 return;
131
132 var rowDefinitions = new Dimension[tableContainer.Content.Count];
133 for (int r = 0; r < tableContainer.Content.Count; r++)
134 rowDefinitions[r] = new Dimension(GridSizeMode.Absolute, tableContainer.Content[r].Max(c => ((MarkdownTableCell)c).ContentHeight));
135
136 tableContainer.RowDimensions = rowDefinitions;
137 }
138
139 protected virtual MarkdownTableCell CreateTableCell(TableCell cell, TableColumnDefinition definition, bool isHeading) => new MarkdownTableCell(cell, definition);
140 }
141}