// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; namespace osu.Framework.Graphics.Containers { /// /// A container which tabulates s. /// public class TableContainer : CompositeDrawable { private readonly GridContainer grid; public TableContainer() { InternalChild = grid = new GridContainer { RelativeSizeAxes = Axes.Both }; } private Drawable[,] content; /// /// The content of this , arranged in a 2D rectangular array. /// /// Null elements are allowed to represent blank rows/cells. /// /// [CanBeNull] public Drawable[,] Content { get => content; set { if (content == value) return; content = value; updateContent(); } } private TableColumn[] columns = Array.Empty(); /// /// Describes the columns of this . /// Each index of this array applies to the respective column index inside . /// public TableColumn[] Columns { [NotNull] get => columns; [CanBeNull] set { value ??= Array.Empty(); if (columns == value) return; columns = value; updateContent(); } } private Dimension rowSize = new Dimension(); /// /// Explicit dimensions for rows. The dimension is applied to every row of this /// public Dimension RowSize { [NotNull] get => rowSize; [CanBeNull] set { value ??= new Dimension(); if (rowSize == value) return; rowSize = value; updateContent(); } } private bool showHeaders = true; /// /// Whether to display a row with column headers at the top of the table. /// public bool ShowHeaders { get => showHeaders; set { if (showHeaders == value) return; showHeaders = value; updateContent(); } } public override Axes RelativeSizeAxes { get => base.RelativeSizeAxes; set { base.RelativeSizeAxes = value; updateGridSize(); } } /// /// Controls which are automatically sized w.r.t. . /// Children's are ignored for automatic sizing. /// Most notably, and of children /// do not affect automatic sizing to avoid circular size dependencies. /// It is not allowed to manually set (or / ) /// on any which are automatically sized. /// public new Axes AutoSizeAxes { get => base.AutoSizeAxes; set { base.AutoSizeAxes = value; updateGridSize(); } } /// /// The total number of rows in the content, including the header. /// private int totalRows => (content?.GetLength(0) ?? 0) + (ShowHeaders ? 1 : 0); /// /// The total number of columns in the content, including the header. /// private int totalColumns { get { if (columns == null || !showHeaders) return content?.GetLength(1) ?? 0; return Math.Max(columns.Length, content?.GetLength(1) ?? 0); } } /// /// Adds content to the underlying grid. /// private void updateContent() { grid.Content = getContentWithHeaders().ToJagged(); grid.ColumnDimensions = columns.Select(c => c.Dimension).ToArray(); grid.RowDimensions = Enumerable.Repeat(RowSize, totalRows).ToArray(); updateAnchors(); } /// /// Adds headers, if required, and returns the resulting content. is not modified in the process. /// /// The content, with headers added if required. private Drawable[,] getContentWithHeaders() { if (!ShowHeaders || Columns == null || Columns.Length == 0) return content; int rowCount = totalRows; int columnCount = totalColumns; var result = new Drawable[rowCount, columnCount]; for (int row = 0; row < rowCount; row++) { for (int col = 0; col < columnCount; col++) { if (row == 0) result[row, col] = CreateHeader(col, col >= Columns?.Length ? null : Columns?[col]); else if (col < content.GetLength(1)) result[row, col] = content[row - 1, col]; } } return result; } /// /// Ensures that all cells have the correct anchors defined by . /// private void updateAnchors() { if (grid.Content == null) return; int rowCount = totalRows; int columnCount = totalColumns; for (int row = 0; row < rowCount; row++) { for (int col = 0; col < columnCount; col++) { if (col >= columns.Length) break; Drawable child = grid.Content[row][col]; if (child == null) continue; child.Origin = columns[col].Anchor; child.Anchor = columns[col].Anchor; } } } /// /// Keeps the grid autosized in our autosized axes, and relative-sized in our non-autosized axes. /// private void updateGridSize() { grid.RelativeSizeAxes = Axes.None; grid.AutoSizeAxes = Axes.None; if ((AutoSizeAxes & Axes.X) == 0) grid.RelativeSizeAxes |= Axes.X; else grid.AutoSizeAxes |= Axes.X; if ((AutoSizeAxes & Axes.Y) == 0) grid.RelativeSizeAxes |= Axes.Y; else grid.AutoSizeAxes |= Axes.Y; } /// /// Creates the content for a cell in the header row of the table. /// /// The column index. /// The column definition. /// The cell content. protected virtual Drawable CreateHeader(int index, [CanBeNull] TableColumn column) => new SpriteText { Text = column?.Header ?? string.Empty }; } /// /// Defines a column of the . /// public class TableColumn { /// /// The localisable text to be displayed in the cell. /// public readonly LocalisableString Header; /// /// The anchor of all cells in this column of the . /// public readonly Anchor Anchor; /// /// The dimension of the column in the table. /// public readonly Dimension Dimension; /// /// Constructs a new . /// /// The localisable text to be displayed in the cell. /// The anchor of all cells in this column of the . /// The dimension of the column in the table. public TableColumn(LocalisableString? header = null, Anchor anchor = Anchor.TopLeft, Dimension dimension = null) { Header = header ?? string.Empty; Anchor = anchor; Dimension = dimension ?? new Dimension(); } } }