diff --git a/src/LiveChartsCore/Drawing/Layouts/CoreStackLayout.cs b/src/LiveChartsCore/Drawing/Layouts/CoreStackLayout.cs new file mode 100644 index 000000000..dd943153d --- /dev/null +++ b/src/LiveChartsCore/Drawing/Layouts/CoreStackLayout.cs @@ -0,0 +1,211 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System.Collections.Generic; +using System; + +namespace LiveChartsCore.Drawing.Layouts; + +/// +/// Defines a the stack panel geometry. +/// +/// The type of the drawing context. +/// The type of the background geometry. +public abstract class CoreStackLayout + : CoreGeometry, IDrawable + where TDrawingContext : DrawingContext + where TBackgroundGeometry : CoreSizedGeometry, IDrawable, new() +{ + private readonly TBackgroundGeometry _backgroundGeometry = new(); + + /// + /// Initializes a new instance of the class. + /// + protected CoreStackLayout() + { } + + /// + /// Initializes a new instance of the class, + /// and uses the specified geometry as background. + /// + /// The background gemetry. + protected CoreStackLayout(TBackgroundGeometry backgroundGeometry) + { + _backgroundGeometry = backgroundGeometry; + } + + /// + /// Gets or sets the panel orientation. + /// + public ContainerOrientation Orientation { get; set; } + + /// + /// Gets or sets the vertical alignment. + /// + public Align VerticalAlignment { get; set; } + + /// + /// Gets or sets the horizontal alignment. + /// + public Align HorizontalAlignment { get; set; } + + /// + /// Gets or sets the padding. + /// + public Padding Padding { get; set; } = new(); + + /// + /// Gets or sets the maximum width. When the maximum with is reached, a new row is created. + /// + public double MaxWidth { get; set; } + + /// + /// Gets or sets the maximum height. When the maximum height is reached, a new column is created. + /// + public double MaxHeight { get; set; } + + /// + public IEnumerable> Children { get; set; } = []; + + /// + public void Draw(TDrawingContext context) + { + var size = Measure(); + + _backgroundGeometry.X = X; + _backgroundGeometry.Y = Y; + _backgroundGeometry.Height = size.Height; + _backgroundGeometry.Width = size.Width; + _backgroundGeometry.Fill = Fill; + _backgroundGeometry.Stroke = Stroke; + + _backgroundGeometry.Draw(context); + } + + /// + public override LvcSize Measure() + { + var xl = Padding.Left; + var yl = Padding.Top; + var rowHeight = -1f; + var columnWidth = -1f; + var mx = 0f; + var my = 0f; + + List line = []; + + LvcSize alignCurrentLine() + { + var mx = -1f; + var my = -1f; + + foreach (var child in line) + { + if (Orientation == ContainerOrientation.Horizontal) + child.Drawable.Y = VerticalAlignment switch + { + Align.Start => yl, + Align.Middle => yl + (rowHeight - child.Size.Height) / 2f, + Align.End => yl + rowHeight - child.Size.Height, + _ => throw new NotImplementedException() + }; + else + child.Drawable.X = HorizontalAlignment switch + { + Align.Start => xl, + Align.Middle => xl + (columnWidth - child.Size.Width) / 2f, + Align.End => xl + columnWidth - child.Size.Width, + _ => throw new NotImplementedException() + }; + + child.Drawable.Parent = this; + + if (child.Size.Width > mx) mx = child.Size.Width; + if (child.Size.Height > my) my = child.Size.Height; + } + + line = []; + return new LvcSize(mx, my); + } + + foreach (var child in Children) + { + var childSize = child.Measure(); + + if (Orientation == ContainerOrientation.Horizontal) + { + if (xl + childSize.Width > MaxWidth) + { + var lineSize = alignCurrentLine(); + xl = Padding.Left; + yl += lineSize.Height; + rowHeight = -1f; + } + + if (rowHeight < childSize.Height) rowHeight = childSize.Height; + child.X = xl; + + xl += childSize.Width; + } + else + { + if (yl + childSize.Height > MaxHeight) + { + var lineSize = alignCurrentLine(); + yl = Padding.Top; + xl += lineSize.Width; + columnWidth = -1f; + } + + if (columnWidth < childSize.Width) columnWidth = childSize.Width; + child.Y = yl; + + yl += childSize.Height; + } + + if (xl > mx) mx = xl; + if (yl > my) my = yl; + line.Add(new MeasureResult(child, childSize)); + } + + if (line.Count > 0) + { + var lineSize = alignCurrentLine(); + + if (Orientation == ContainerOrientation.Horizontal) + yl += lineSize.Height; + else + xl += lineSize.Width; + + if (xl > mx) mx = xl; + if (yl > my) my = yl; + } + + return new LvcSize(mx + Padding.Right, my + Padding.Bottom); + } + + private class MeasureResult(IDrawable visual, LvcSize size) + { + public IDrawable Drawable { get; set; } = visual; + public LvcSize Size { get; set; } = size; + } +} diff --git a/src/LiveChartsCore/Drawing/Layouts/CoreTableLayout.cs b/src/LiveChartsCore/Drawing/Layouts/CoreTableLayout.cs new file mode 100644 index 000000000..d6dc4fb11 --- /dev/null +++ b/src/LiveChartsCore/Drawing/Layouts/CoreTableLayout.cs @@ -0,0 +1,265 @@ +// The MIT License(MIT) +// +// Copyright(c) 2021 Alberto Rodriguez Orozco & LiveCharts Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LiveChartsCore.Drawing.Layouts; + +/// +/// Defines a the stack panel geometry. +/// +/// The type of the drawing context. +/// The type of the background geometry. +public abstract class CoreTableLayout + : CoreGeometry, IDrawable + where TDrawingContext : DrawingContext + where TBackgroundGeometry : CoreSizedGeometry, IDrawable, new() +{ + private LvcSize[,] _measuredSizes = new LvcSize[0, 0]; + private readonly Dictionary> _positions = []; + private int _maxRow = 0; + private int _maxColumn = 0; + private readonly TBackgroundGeometry _backgroundGeometry = new(); + + /// + /// Initializes a new instance of the class. + /// + protected CoreTableLayout() + { } + + /// + /// Initializes a new instance of the class, + /// and uses the specified geometry as background. + /// + /// The background gemetry. + protected CoreTableLayout(TBackgroundGeometry backgroundGeometry) + { + _backgroundGeometry = backgroundGeometry; + } + + /// + /// Gets or sets the panel orientation. + /// + public ContainerOrientation Orientation { get; set; } + + /// + /// Gets or sets the vertical alignment. + /// + public Align VerticalAlignment { get; set; } + + /// + /// Gets or sets the horizontal alignment. + /// + public Align HorizontalAlignment { get; set; } + + /// + /// Gets or sets the padding. + /// + public Padding Padding { get; set; } = new(); + + /// + /// Gets or sets the maximum width. When the maximum with is reached, a new row is created. + /// + public double MaxWidth { get; set; } + + /// + /// Gets or sets the maximum height. When the maximum height is reached, a new column is created. + /// + public double MaxHeight { get; set; } + + /// + IEnumerable> IDrawable.Children + { + get => _positions.Values.SelectMany(row => row.Values.Select(col => col.Drawable)); + set => throw new Exception( + $"Unable to set the Children prroperty of a table, instead please use the " + + $"{nameof(AddChild)} method."); + } + + /// + /// Adds a child to the layout. + /// + /// The row index. + /// The column index. + /// The visual to add. + /// The cell horizontal alignment, if null the alignment will be defined by the layout. + /// The cell vertical alignment, if null the alignment will be defined by the layout. + public void AddChild( + IDrawable child, + int row, int column, + Align? horizontalAlign = null, + Align? verticalAlign = null) + { + if (!_positions.TryGetValue(row, out var r)) + _positions.Add(row, r = []); + + r[column] = new(row, column, child, verticalAlign, horizontalAlign); + + if (row > _maxRow) _maxRow = row; + if (column > _maxColumn) _maxColumn = column; + } + + /// + /// Removes a child from the layout. + /// + /// The row. + /// The column. + public void RemoveChildAt(int row, int column) + { + var r = _positions[row]; + _ = r.Remove(column); + if (r.Count == 0) _ = _positions.Remove(row); + } + + /// + public void Draw(TDrawingContext context) + { + var controlSize = Measure(); + + float w, h = Padding.Top; + + for (var r = 0; r <= _maxRow; r++) + { + if (!_positions.TryGetValue(r, out var row)) continue; + + var rowHeight = _measuredSizes[r, _maxColumn + 1].Height; + w = Padding.Left; + + for (var c = 0; c <= _maxColumn; c++) + { + var columnWidth = _measuredSizes[_maxRow + 1, c].Width; + if (!row.TryGetValue(c, out var cell)) + { + w += columnWidth; + continue; + } + + cell.Drawable.X = (cell.HorizontalAlign ?? HorizontalAlignment) switch + { + Align.Start => w, + Align.Middle => w + (columnWidth - _measuredSizes[r, c].Width) * 0.5f, + Align.End => w + columnWidth - _measuredSizes[r, c].Width, + _ => throw new NotImplementedException(), + }; + cell.Drawable.Y = (cell.VerticalAlign ?? VerticalAlignment) switch + { + Align.Start => h, + Align.Middle => h + (rowHeight - _measuredSizes[r, c].Height) * 0.5f, + Align.End => h + rowHeight - _measuredSizes[r, c].Height, + _ => throw new NotImplementedException(), + }; + + cell.Drawable.Parent = this; + w += columnWidth; + } + + h += rowHeight; + } + + _backgroundGeometry.X = X; + _backgroundGeometry.Y = Y; + _backgroundGeometry.Height = controlSize.Height; + _backgroundGeometry.Width = controlSize.Width; + _backgroundGeometry.Fill = Fill; + _backgroundGeometry.Stroke = Stroke; + + _backgroundGeometry.Draw(context); + } + + /// + public override LvcSize Measure() + { + var maxH = Padding.Top; + _measuredSizes = new LvcSize[_maxRow + 2, _maxColumn + 2]; + + var mr = _maxRow + 1; + var mc = _maxColumn + 1; + + for (var r = 0; r <= _maxRow; r++) + { + if (!_positions.TryGetValue(r, out var row)) continue; + + for (var c = 0; c <= _maxColumn; c++) + { + var cellSize = _measuredSizes[r, c]; + var rowSize = _measuredSizes[r, mc]; + var columnSize = _measuredSizes[mr, c]; + + if (!row.TryGetValue(c, out var cell)) continue; + + var childSize = cell.Drawable.Measure(); + + if (cellSize.Width < childSize.Width) + _measuredSizes[r, c] = new(childSize.Width, _measuredSizes[r, c].Height); + if (cellSize.Height < childSize.Height) + _measuredSizes[r, c] = new(_measuredSizes[r, c].Width, childSize.Height); + if (rowSize.Height < childSize.Height) + _measuredSizes[r, mc] = new(0, childSize.Height); + if (columnSize.Width < childSize.Width) + _measuredSizes[mr, c] = new(childSize.Width, 0); + } + + maxH += _measuredSizes[r, _maxColumn + 1].Height; + } + + var maxW = Padding.Left; + for (var c = 0; c <= _maxColumn; c++) + maxW += _measuredSizes[_maxRow + 1, c].Width; + + return new(maxW + Padding.Right, maxH + Padding.Bottom); + } + + private class TableCell( + int row, + int column, + IDrawable visualElement, + Align? verticalAlign = null, + Align? horizontalAlign = null) + { + /// + /// Gets the row. + /// + public int Row { get; } = row; + + /// + /// Gets the column. + /// + public int Column { get; } = column; + + /// + /// Gets or sets the vertical alignment. + /// + public Align? VerticalAlign { get; } = verticalAlign; + + /// + /// Gets or sets the horizontal alignment. + /// + public Align? HorizontalAlign { get; } = horizontalAlign; + + /// + /// Gets the visual element. + /// + public IDrawable Drawable { get; } = visualElement; + } +} diff --git a/src/LiveChartsCore/VisualElements/TableLayout.cs b/src/LiveChartsCore/VisualElements/TableLayout.cs index e650ddb86..d05030930 100644 --- a/src/LiveChartsCore/VisualElements/TableLayout.cs +++ b/src/LiveChartsCore/VisualElements/TableLayout.cs @@ -305,7 +305,6 @@ public class TableCell( Align? verticalAlign = null, Align? horizontalAlign = null) { - /// /// Gets the row. /// diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/StackLayout.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/StackLayout.cs similarity index 95% rename from src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/StackLayout.cs rename to src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/StackLayout.cs index 2d0968d67..8c2ea1272 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/StackLayout.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/StackLayout.cs @@ -22,8 +22,9 @@ using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Layouts; +using LiveChartsCore.SkiaSharpView.Drawing.Geometries; -namespace LiveChartsCore.SkiaSharpView.Drawing.Geometries; +namespace LiveChartsCore.SkiaSharpView.Drawing.Layouts; /// public class StackLayout : CoreStackLayout diff --git a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/TableLayout.cs b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/TableLayout.cs similarity index 95% rename from src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/TableLayout.cs rename to src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/TableLayout.cs index a97de9e3c..8dcca0012 100644 --- a/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Geometries/TableLayout.cs +++ b/src/skiasharp/LiveChartsCore.SkiaSharp/Drawing/Layouts/TableLayout.cs @@ -22,8 +22,9 @@ using LiveChartsCore.Drawing; using LiveChartsCore.Drawing.Layouts; +using LiveChartsCore.SkiaSharpView.Drawing.Geometries; -namespace LiveChartsCore.SkiaSharpView.Drawing.Geometries; +namespace LiveChartsCore.SkiaSharpView.Drawing.Layouts; /// public class TableLayout : CoreTableLayout