Skip to content
This repository has been archived by the owner on Sep 4, 2024. It is now read-only.

[Mac] Cache cell width to improve size calculations #813

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Xwt.XamMac/Xwt.Mac.CellViews/CellViewBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void QueueDraw ()
public void QueueResize ()
{
CurrentCellView.NeedsDisplay = true;
((ICellRenderer)CurrentCellView).CellContainer.InvalidateRowHeight ();
((ICellRenderer)CurrentCellView).CellContainer.QueueResize ();
}

public Rectangle CellBounds {
Expand Down
6 changes: 3 additions & 3 deletions Xwt.XamMac/Xwt.Mac.CellViews/CompositeCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ public void SetCurrentEventRow ()
}

bool recalculatingHeight = false;
public void InvalidateRowHeight ()
public void QueueResize ()
{
if (tablePosition != null && !recalculatingHeight) {
recalculatingHeight = true;
source.InvalidateRowHeight (tablePosition.Position);
source.QueueResizeRow (tablePosition.Position);
recalculatingHeight = false;
}
}
Expand Down Expand Up @@ -193,7 +193,7 @@ public override CGRect Frame {
height = Math.Max (height, c.Frame.Height);
}
if (Math.Abs(value.Height - height) > double.Epsilon)
InvalidateRowHeight ();
QueueResize ();

}
}
Expand Down
2 changes: 1 addition & 1 deletion Xwt.XamMac/Xwt.Mac.CellViews/ICellSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface ICellSource
object GetValue (object pos, int nField);
void SetValue (object pos, int nField, object value);
void SetCurrentEventRow (object pos);
void InvalidateRowHeight (object pos);
void QueueResizeRow (object pos);
List<NSTableColumn> Columns { get; }
NSTableView TableView { get; }
}
Expand Down
71 changes: 61 additions & 10 deletions Xwt.XamMac/Xwt.Mac/ListViewBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,25 @@ public override nfloat GetSizeToFitColumnWidth (NSTableView tableView, nint colu
{
var tableColumn = Backend.Columns[(int)column] as TableColumn;
var width = tableColumn.HeaderCell.CellSize.Width;
var rowWidths = Backend.ColumnRowWidths[(int)column];

CompositeCell templateCell = null;
for (int i = 0; i < tableView.RowCount; i++) {
if (rowWidths[i] > -1)
width = (nfloat)Math.Max (width, rowWidths[i]);
else {
var cellView = tableView.GetView (column, i, false) as CompositeCell;
if (cellView == null) { // use template for invisible rows
cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView?.Copy () as CompositeCell);
if (cellView != null)
cellView.ObjectValue = NSNumber.FromInt32 (i);
if (cellView == null) { // use template for invisible rows
cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView as CompositeCell);
if (cellView != null)
cellView.ObjectValue = NSNumber.FromInt32 (i);
}
if (cellView != null) {
var size = cellView.FittingSize;
rowWidths[i] = size.Width;
width = (nfloat)Math.Max (width, size.Width);
}
}
width = (nfloat)Math.Max (width, cellView.FittingSize.Width);
}
return width;
}
Expand All @@ -95,6 +104,9 @@ public override NSIndexSet GetSelectionIndexes(NSTableView tableView, NSIndexSet
IListDataSource source;
ListSource tsource;

List<List<nfloat>> ColumnRowWidths = new List<List<nfloat>> ();
List<nfloat> RowHeights = new List<nfloat> ();

protected override NSTableView CreateView ()
{
var listView = new NSTableViewBackend (this);
Expand Down Expand Up @@ -140,14 +152,40 @@ void HandleDoubleClick (object sender, EventArgs e)
});
}
}


public override NSTableColumn AddColumn (ListViewColumn col)
{
NSTableColumn tcol = base.AddColumn (col);
List<nfloat> widths;
var rows = (int)Table.RowCount;
if (rows > 0) {
widths = new List<nfloat> (rows);
for (int i = 0; i < rows; ++i) widths.Add (-1f);
} else
widths = new List<nfloat> ();
ColumnRowWidths.Add (widths);
return tcol;
}

public override void RemoveColumn (ListViewColumn col, object handle)
{
var tcol = (NSTableColumn)handle;
var index = Columns.IndexOf (tcol);
ColumnRowWidths.RemoveAt (index);
base.RemoveColumn (col, handle);
}

public virtual void SetSource (IListDataSource source, IBackend sourceBackend)
{
this.source = source;

RowHeights = new List<nfloat> ();
for (int i = 0; i < source.RowCount; i++)
RowHeights.Add (-1);
foreach (var colWidths in ColumnRowWidths) {
colWidths.Clear ();
for (int i = 0; i < source.RowCount; ++i) colWidths.Add (-1f);
}

tsource = new ListSource (source);
Table.DataSource = tsource;
Expand All @@ -157,30 +195,43 @@ public virtual void SetSource (IListDataSource source, IBackend sourceBackend)
// only the visible rows are reloaded.
source.RowInserted += (sender, e) => {
RowHeights.Insert (e.Row, -1);
foreach (var colWidths in ColumnRowWidths)
colWidths.Insert (e.Row, -1);
Table.ReloadData ();
};
source.RowDeleted += (sender, e) => {
RowHeights.RemoveAt (e.Row);
foreach (var colWidths in ColumnRowWidths)
colWidths.RemoveAt (e.Row);
Table.ReloadData ();
};
source.RowChanged += (sender, e) => {
UpdateRowHeight (e.Row);
Table.ReloadData (NSIndexSet.FromIndex (e.Row), NSIndexSet.FromNSRange (new NSRange (0, Table.ColumnCount)));
};
source.RowsReordered += (sender, e) => { RowHeights.Clear (); Table.ReloadData (); };
source.RowsReordered += (sender, e) => {
RowHeights.Clear ();
foreach (var colWidths in ColumnRowWidths)
colWidths.Clear ();
Table.ReloadData ();
};
}

public override void InvalidateRowHeight (object pos)
public override void QueueResizeRow (object pos)
{
UpdateRowHeight((int)pos);
}

List<nfloat> RowHeights = new List<nfloat> ();

bool updatingRowHeight;
public void UpdateRowHeight (nint row)
{
if (updatingRowHeight)
return;

// FIXME: this won't resize the columns, which might be needed for custom cells
// In order to resize horizontally we'll need trigger column autosizing.
foreach (var colWidths in ColumnRowWidths) // invalidate widths for full recalculation
colWidths[(int)row] = -1;
RowHeights[(int)row] = CalcRowHeight (row);
Table.NoteHeightOfRowsWithIndexesChanged (NSIndexSet.FromIndex (row));
}
Expand Down
4 changes: 2 additions & 2 deletions Xwt.XamMac/Xwt.Mac/TableViewBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ object IColumnContainerBackend.AddColumn (ListViewColumn col)
return AddColumn (col);
}

public void RemoveColumn (ListViewColumn col, object handle)
public virtual void RemoveColumn (ListViewColumn col, object handle)
{
var tcol = (NSTableColumn)handle;
cols.Remove (tcol);
Expand Down Expand Up @@ -271,7 +271,7 @@ public void StartEditingCell (int row, CellView cell)

public abstract void SetCurrentEventRow (object pos);

public abstract void InvalidateRowHeight (object pos);
public abstract void QueueResizeRow (object pos);

public bool BorderVisible {
get { return scroll.BorderType == NSBorderType.BezelBorder;}
Expand Down
70 changes: 54 additions & 16 deletions Xwt.XamMac/Xwt.Mac/TreeViewBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ public class TreeViewBackend: TableViewBackend<NSOutlineView,ITreeViewEventSink>
ITreeDataSource source;
TreeSource tsource;

List<Dictionary<TreePosition, nfloat>> ColumnRowWidths = new List<Dictionary<TreePosition, nfloat>> ();
Dictionary<TreeItem, nfloat> RowHeights = new Dictionary<TreeItem, nfloat> ();

class TreeDelegate: NSOutlineViewDelegate
{
public TreeViewBackend Backend;
Expand Down Expand Up @@ -95,17 +98,23 @@ public override nfloat GetSizeToFitColumnWidth (NSOutlineView outlineView, nint

CompositeCell templateCell = null;
for (int i = 0; i < outlineView.RowCount; i++) {
var cellView = outlineView.GetView (column, i, false) as CompositeCell;
if (cellView == null) { // use template for invisible rows
cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView?.Copy () as CompositeCell);
if (cellView != null)
cellView.ObjectValue = outlineView.ItemAtRow (i);
}
if (cellView != null) {
if (column == 0) // first column contains expanders
width = (nfloat)Math.Max (width, cellView.Frame.X + cellView.FittingSize.Width);
else
width = (nfloat)Math.Max (width, cellView.FittingSize.Width);
nfloat cellWidth;
var item = (TreeItem)outlineView.ItemAtRow (i);
if (Backend.ColumnRowWidths[(int)column].TryGetValue (item.Position, out cellWidth) && cellWidth > -1)
width = (nfloat)Math.Max (width, cellWidth);
else {
var cellView = outlineView.GetView (column, i, false) as CompositeCell;
if (cellView == null) { // use template for invisible rows
cellView = templateCell ?? (templateCell = (tableColumn as TableColumn)?.DataView as CompositeCell);
if (cellView != null)
cellView.ObjectValue = item;
}
if (cellView != null) {
// first column contains expanders
cellWidth = column == 0 ? cellView.Frame.X + cellView.FittingSize.Width : cellView.FittingSize.Width;
Backend.ColumnRowWidths[(int)column][item.Position] = cellWidth;
width = (nfloat)Math.Max (width, cellWidth);
}
}
}
return width;
Expand Down Expand Up @@ -148,8 +157,17 @@ public override NSTableColumn AddColumn (ListViewColumn col)
NSTableColumn tcol = base.AddColumn (col);
if (Tree.OutlineTableColumn == null)
Tree.OutlineTableColumn = tcol;
ColumnRowWidths.Add (new Dictionary<TreePosition, nfloat> ());
return tcol;
}

public override void RemoveColumn (ListViewColumn col, object handle)
{
var tcol = (NSTableColumn)handle;
var index = Columns.IndexOf (tcol);
ColumnRowWidths.RemoveAt (index);
base.RemoveColumn (col, handle);
}

public void SetSource (ITreeDataSource source, IBackend sourceBackend)
{
Expand All @@ -163,6 +181,8 @@ public void SetSource (ITreeDataSource source, IBackend sourceBackend)
Tree.ReloadItem (parent, parent == null || Tree.IsItemExpanded (parent));
};
source.NodeDeleted += (sender, e) => {
foreach (var colWidths in ColumnRowWidths)
colWidths.Remove (e.Child);
var parent = tsource.GetItem (e.Node);
var item = tsource.GetItem(e.Child);
if (item != null)
Expand All @@ -173,17 +193,24 @@ public void SetSource (ITreeDataSource source, IBackend sourceBackend)
var item = tsource.GetItem (e.Node);
if (item != null) {
Tree.ReloadItem (item, false);
foreach (var colWidths in ColumnRowWidths)
colWidths [e.Node] = -1;
UpdateRowHeight (item);
}
};
source.NodesReordered += (sender, e) => {
var parent = tsource.GetItem (e.Node);
foreach (var colWidths in ColumnRowWidths)
for (int i = 0; i < source.GetChildrenCount (e.Node); i++)
colWidths [source.GetChild (e.Node, i)] = -1;
Tree.ReloadItem (parent, parent == null || Tree.IsItemExpanded (parent));
};
source.Cleared += (sender, e) =>
{
Tree.ReloadData ();
RowHeights.Clear ();
foreach (var colWidths in ColumnRowWidths)
colWidths.Clear ();
};
}

Expand All @@ -197,12 +224,11 @@ public override void SetValue (object pos, int nField, object value)
source.SetValue ((TreePosition)pos, nField, value);
}

public override void InvalidateRowHeight (object pos)
public override void QueueResizeRow (object pos)
{
UpdateRowHeight (tsource.GetItem((TreePosition)pos));
}

Dictionary<TreeItem, nfloat> RowHeights = new Dictionary<TreeItem, nfloat> ();
bool updatingRowHeight;

void UpdateRowHeight (TreeItem pos)
Expand All @@ -211,8 +237,12 @@ void UpdateRowHeight (TreeItem pos)
return;
var row = Tree.RowForItem (pos);
if (row >= 0) {
// calculate new height now by reusing the visible cell to avoid using the template cell with unnecessary data reloads
// calculate new size now by reusing the visible cell to avoid using the template cell with unnecessary data reloads
// NOTE: cell reusing is not supported in Delegate.GetRowHeight and would require an other data reload to the template cell
// FIXME: this won't resize the columns, which might be needed for custom cells
// In order to resize horizontally we'll need trigger column autosizing.
foreach (var colWidths in ColumnRowWidths) // invalidate widths for full recalculation
colWidths [pos.Position] = -1;
RowHeights[pos] = CalcRowHeight (pos);
Table.NoteHeightOfRowsWithIndexesChanged (NSIndexSet.FromIndex (row));
} else // Invalidate the height, to force recalculation in Delegate.GetRowHeight
Expand All @@ -226,13 +256,21 @@ nfloat CalcRowHeight (TreeItem pos, bool tryReuse = true)
var row = Tree.RowForItem (pos);

for (int i = 0; i < Columns.Count; i++) {
var col = (TableColumn)Columns [i];
CompositeCell cell = tryReuse && row >= 0 ? Tree.GetView (i, row, false) as CompositeCell : null;
if (cell == null) {
cell = (Columns [i] as TableColumn)?.DataView as CompositeCell;
cell = col.DataView;
cell.ObjectValue = pos;
height = (nfloat)Math.Max (height, cell.FittingSize.Height);
} else {
height = (nfloat)Math.Max (height, cell.GetRequiredHeightForWidth (cell.Frame.Width));
nfloat cellWidth = -1;
ColumnRowWidths [i].TryGetValue (pos.Position, out cellWidth);
if (cellWidth <= 0)
cellWidth = cell.Frame.Width;
if (cellWidth <= 0)
height = (nfloat)Math.Max (height, cell.FittingSize.Height);
else
height = (nfloat)Math.Max (height, cell.GetRequiredHeightForWidth (cellWidth));
}
}
updatingRowHeight = false;
Expand Down