Add support types for tables, revise and work around layout issues

This commit is contained in:
John Lewin 2022-09-01 02:06:24 -07:00
parent 034565965e
commit 50d3513bbe
8 changed files with 283 additions and 137 deletions

View file

@ -20,12 +20,17 @@ namespace Markdig.Renderers.Agg
public class ParagraphX : FlowLeftRightWithWrapping, IHardBreak
{
public ParagraphX(bool bottomMargin)
{
{
// Adding HAnchor and initial fixed width properties to resolve excess vertical whitespace added during collapse to width 0
//
// TODO: Revise impact to FlowLeftRightWithWrapping
this.HAnchor = HAnchor.Stretch;
this.Width = 5000;
if (bottomMargin)
{
Margin = new BorderDouble(0, 0, 0, 12);
}
}
}
}
//public class ParagraphRenderer :

View file

@ -1,126 +0,0 @@
// Copyright (c) Nicolas Musset. All rights reserved.
// Copyright (c) 2022, John Lewin
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System;
using Markdig.Extensions.Tables;
using MatterHackers.Agg;
using MatterHackers.Agg.Platform;
using MatterHackers.Agg.UI;
using MatterHackers.MatterControl;
namespace Markdig.Renderers.Agg
{
public class AggTableRenderer : AggObjectRenderer<Table>
{
protected override void Write(AggRenderer renderer, Table table)
{
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
if (table == null) throw new ArgumentNullException(nameof(table));
var aggTable = new FlowLayoutWidget(FlowDirection.TopToBottom)
{
HAnchor = HAnchor.Fit,
VAnchor = VAnchor.Fit,
Margin = new BorderDouble(top: 12),
};
// TODO: Use Markdig parser data to drive column/cell widths
//foreach (var tableColumnDefinition in table.ColumnDefinitions)
// Width = (tableColumnDefinition?.Width ?? 0) != 0 ? tableColumnDefinition.Width : <or auto>
renderer.Push(aggTable);
foreach (var rowObj in table)
{
var row = (TableRow)rowObj;
var aggRow = new AggTableRow()
{
IsHeadingRow = row.IsHeader,
};
renderer.Push(aggRow);
if (row.IsHeader)
{
// Update to desired header row styling and/or moving into AggTableRow for consistency
aggRow.BackgroundColor = MatterHackers.MatterControl.AppContext.Theme.TabBarBackground;
}
for (var i = 0; i < row.Count; i++)
{
var cellObj = row[i];
var cell = (TableCell)cellObj;
// Fixed width cells just to get something initially on screen
var aggCellBox = new GuiWidget()
{
Width = 200,
Height = 25,
};
// TODO: Cell Width - implement next, might be easy to track and perform in AggTableRow
/* (Spec)
* If any line of the markdown source is longer than the column width (see --columns), then the
* table will take up the full text width and the cell contents will wrap, with the relative cell
* widths determined by the number of dashes in the line separating the table header from the table
* body. (For example ---|- would make the first column 3/4 and the second column 1/4 of the full
* text width.) On the other hand, if no lines are wider than column width, then cell contents will
* not be wrapped, and the cells will be sized to their contents.
*/
// Cell box above enforces boundaries, use flow for layout
var aggCellFlow = new FlowLayoutWidget()
{
HAnchor = HAnchor.Stretch,
};
if (table.ColumnDefinitions.Count > 0)
{
// TODO: Ideally we'd be driving column width from metadata rather than hard-coded
// See example below from WPF implementation
//
// Grab the column definition, or fall back to a default
var columnIndex = cell.ColumnIndex < 0 || cell.ColumnIndex >= table.ColumnDefinitions.Count
? i
: cell.ColumnIndex;
columnIndex = columnIndex >= table.ColumnDefinitions.Count ? table.ColumnDefinitions.Count - 1 : columnIndex;
// TODO: revise alignment via Agg types that produce aligned text
var columnDefinition = table.ColumnDefinitions[columnIndex];
var alignment = columnDefinition.Alignment;
if (alignment.HasValue)
{
switch (alignment)
{
case TableColumnAlign.Center:
aggCellFlow.HAnchor |= HAnchor.Center;
break;
case TableColumnAlign.Right:
aggCellFlow.HAnchor |= HAnchor.Right;
break;
case TableColumnAlign.Left:
aggCellFlow.HAnchor |= HAnchor.Left;
break;
}
}
}
renderer.Push(aggCellBox);
renderer.Push(aggCellFlow);
renderer.Write(cell);
renderer.Pop();
renderer.Pop();
}
// Pop row
renderer.Pop();
}
// Pop table
renderer.Pop();
}
}
}

View file

@ -0,0 +1,38 @@
// Copyright (c) Nicolas Musset. All rights reserved.
// Copyright (c) 2022, John Lewin
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using Markdig.Extensions.Tables;
using MatterHackers.Agg.UI;
namespace Markdig.Renderers.Agg
{
public class AggTable : FlowLayoutWidget
{
public List<AggTableColumn> Columns { get; }
public List<AggTableRow> Rows { get; }
public AggTable(Table table) : base(FlowDirection.TopToBottom)
{
this.Columns = table.ColumnDefinitions.Select(c => new AggTableColumn(c)).ToList();
}
public override void OnLayout(LayoutEventArgs layoutEventArgs)
{
if (this.Columns?.Count > 0)
{
foreach (var column in this.Columns)
{
column.SetCellWidths();
}
}
base.OnLayout(layoutEventArgs);
}
}
}

View file

@ -0,0 +1,53 @@
// Copyright (c) 2016-2017 Nicolas Musset. All rights reserved.
// Copyright (c) 2022, John Lewin
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System;
using System.Linq;
using MatterHackers.Agg.UI;
namespace Markdig.Renderers.Agg
{
// Parent container to restrict bounds
public class AggTableCell : GuiWidget
{
public AggTableCell()
{
// TODO: drive from column once width calculation is performed
Width = 300;
Height = 25;
// Use event rather than OnLayout as it only seems to produce the desired effect
this.Layout += AggTableCell_Layout;
}
// TODO: Investigate. Without this solution, child content is wrapped and clipped, leaving only the last text block visible
private void AggTableCell_Layout(object sender, EventArgs e)
{
Console.WriteLine(Parent?.Name);
if (this.Children.Count > 0 && this.Children.First() is FlowLeftRightWithWrapping wrappedChild
&& wrappedChild.Height != this.Height)
{
//using (this.LayoutLock())
{
//// Set height to ensure bounds grow to content after reflow
//this.Height = wrappedChild.Height;
if (this.Parent is AggTableRow parentRow)
{
parentRow.CellHeightChanged(wrappedChild.Height);
}
}
this.ContentWidth = wrappedChild.ContentWidth;
}
}
public double ContentWidth { get; private set; }
// TODO: Use to align child content when bounds are less than current
public HAnchor FlowHAnchor { get; set; }
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) Nicolas Musset. All rights reserved.
// Copyright (c) 2022, John Lewin
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using Markdig.Extensions.Tables;
namespace Markdig.Renderers.Agg
{
public class AggTableColumn
{
private TableColumnDefinition ColumnDefinition;
public AggTableColumn(TableColumnDefinition definition)
{
this.ColumnDefinition = definition;
}
public List<AggTableCell> Cells { get; } = new List<AggTableCell>();
public void SetCellWidths()
{
double cellPadding = 10;
if (this.Cells.Count == 0)
{
return;
}
// TODO: Column/cell width theortically is:
//
// Case A. Expanding to the maximum content width of cells in column to grow each
// cell to a minimum value.
//
// Case B. Contracting when the aggregate column widths exceed the bounds
// of the parent container.
//
// Case C. Distributing percentages across fixed bounds of the parent container
//
// Other cases...
// This block attempts to implement Case A by finding the max content width per cells in each column
//
// Collect max content widths from each cell in this column
double maxCellWidth = this.Cells.Select(c => c.ContentWidth).Max() + cellPadding * 2;
// Apply max width to cells in this column
foreach (var cell in this.Cells)
{
if (cell.Width != maxCellWidth)
{
cell.Width = maxCellWidth;
}
}
}
}
}

View file

@ -0,0 +1,91 @@
// Copyright (c) Nicolas Musset. All rights reserved.
// Copyright (c) 2022, John Lewin
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System;
using Markdig.Extensions.Tables;
using MatterHackers.Agg;
using MatterHackers.Agg.UI;
namespace Markdig.Renderers.Agg
{
public class AggTableRenderer : AggObjectRenderer<Table>
{
protected override void Write(AggRenderer renderer, Table mdTable)
{
if (renderer == null) throw new ArgumentNullException(nameof(renderer));
if (mdTable == null) throw new ArgumentNullException(nameof(mdTable));
var aggTable = new AggTable(mdTable)
{
Margin = new BorderDouble(top: 12),
};
renderer.Push(aggTable);
foreach (var rowObj in mdTable)
{
var mdRow = (TableRow)rowObj;
var aggRow = new AggTableRow()
{
IsHeadingRow = mdRow.IsHeader,
};
renderer.Push(aggRow);
if (mdRow.IsHeader)
{
// Update to desired header row styling and/or move into AggTableRow for consistency
aggRow.BackgroundColor = MatterHackers.MatterControl.AppContext.Theme.TabBarBackground;
}
for (var i = 0; i < mdRow.Count; i++)
{
var mdCell = (TableCell)mdRow[i];
var aggCell = new AggTableCell();
aggRow.Cells.Add(aggCell);
if (mdTable.ColumnDefinitions.Count > 0)
{
// Grab the column definition, or fall back to a default
var columnIndex = mdCell.ColumnIndex < 0 || mdCell.ColumnIndex >= mdTable.ColumnDefinitions.Count
? i
: mdCell.ColumnIndex;
columnIndex = columnIndex >= mdTable.ColumnDefinitions.Count ? mdTable.ColumnDefinitions.Count - 1 : columnIndex;
aggTable.Columns[columnIndex].Cells.Add(aggCell);
if (mdTable.ColumnDefinitions[columnIndex].Alignment.HasValue)
{
switch (mdTable.ColumnDefinitions[columnIndex].Alignment)
{
case TableColumnAlign.Center:
aggCell.FlowHAnchor |= HAnchor.Center;
break;
case TableColumnAlign.Right:
aggCell.FlowHAnchor |= HAnchor.Right;
break;
case TableColumnAlign.Left:
aggCell.FlowHAnchor |= HAnchor.Left;
break;
}
}
}
renderer.Push(aggCell);
renderer.Write(mdCell);
renderer.Pop();
}
// Pop row
renderer.Pop();
}
// Pop table
renderer.Pop();
}
}
}

View file

@ -3,6 +3,8 @@
// This file is licensed under the MIT license.
// See the LICENSE.md file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Markdig.Renderers.Agg.Inlines;
using MatterHackers.Agg;
@ -10,19 +12,19 @@ using MatterHackers.Agg.UI;
namespace Markdig.Renderers.Agg
{
public class AggTableRow: FlowLayoutWidget
public class AggTableRow : FlowLayoutWidget
{
public AggTableRow()
{
this.VAnchor = VAnchor.Fit;
this.Margin = new BorderDouble(3, 4, 0, 12);
// Hack to force content on-screen (seemingly not working when set late/after constructor)
VAnchor = VAnchor.Absolute;
Height = 25;
this.Margin = new BorderDouble(10, 4);
this.VAnchor = VAnchor.Absolute;
this.Height = 25;
}
public bool IsHeadingRow { get; set; }
public bool IsHeadingRow { get; set; }
public List<AggTableCell> Cells { get; } = new List<AggTableCell>();
public double RowHeight { get; private set; }
// Override AddChild to push styles to child elements when table rows are resolved to the tree
public override GuiWidget AddChild(GuiWidget childToAdd, int indexInChildrenList = -1)
@ -48,5 +50,29 @@ namespace Markdig.Renderers.Agg
return base.AddChild(childToAdd, indexInChildrenList);
}
internal void CellHeightChanged(double newHeight)
{
double cellPadding = 2;
double height = newHeight + 2 * cellPadding;
//double maxChildHeight = this.Cells.Select(c => c.Height).Max();
if (this.RowHeight != height)
{
foreach (var cell in this.Cells)
{
using (cell.LayoutLock())
{
cell.Height = height;
}
}
using (this.LayoutLock())
{
this.Height = this.RowHeight = height;
}
}
}
}
}

@ -1 +1 @@
Subproject commit 2cfd64bdeacd33f5be83556f54757b15101af146
Subproject commit ae83bc8ff30bc77e0cc3c2596beeda0c560d5e36