Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add DrawLine event to TreeView for advanced rendering manipulation #2765

Merged
merged 7 commits into from
Jul 25, 2023
Merged
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
65 changes: 54 additions & 11 deletions Terminal.Gui/Views/TreeView/Branch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,10 @@ public virtual void FetchChildren ()

if (Depth >= tree.MaxDepth) {
children = Enumerable.Empty<T> ();
}
else {
} else {
children = tree.TreeBuilder.GetChildren (this.Model) ?? Enumerable.Empty<T> ();
}

this.ChildBranches = children.ToDictionary (k => k, val => new Branch<T> (tree, this, val));
}

Expand All @@ -95,6 +94,11 @@ public virtual int GetWidth (ConsoleDriver driver)
/// <param name="availableWidth"></param>
public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y, int availableWidth)
{
var cells = new List<RuneCell> ();
int? indexOfExpandCollapseSymbol = null;
int indexOfModelText;


// true if the current line of the tree is the selected one and control has focus
bool isSelected = tree.IsSelected (Model);

Expand All @@ -110,15 +114,15 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y,

// if we have scrolled to the right then bits of the prefix will have dispeared off the screen
int toSkip = tree.ScrollOffsetHorizontal;
var attr = symbolColor;

driver.SetAttribute (symbolColor);
// Draw the line prefix (all parallel lanes or whitespace and an expand/collapse/leaf symbol)
foreach (Rune r in prefix) {

if (toSkip > 0) {
toSkip--;
} else {
driver.AddRune (r);
cells.Add (NewRuneCell (attr, r));
availableWidth -= r.GetColumns ();
}
}
Expand All @@ -141,23 +145,31 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y,
color = new Attribute (color.Background, color.Foreground);
}

driver.SetAttribute (color);
attr = color;
}

if (toSkip > 0) {
toSkip--;
} else {
driver.AddRune (expansion);
indexOfExpandCollapseSymbol = cells.Count;
cells.Add (NewRuneCell (attr, expansion));
availableWidth -= expansion.GetColumns ();
}

// horizontal scrolling has already skipped the prefix but now must also skip some of the line body
if (toSkip > 0) {

// For the event record a negative location for where model text starts since it
// is pushed off to the left because of scrolling
indexOfModelText = -toSkip;

if (toSkip > lineBody.Length) {
lineBody = "";
} else {
lineBody = lineBody.Substring (toSkip);
}
} else {
indexOfModelText = cells.Count;
}

// If body of line is too long
Expand Down Expand Up @@ -186,16 +198,47 @@ public virtual void Draw (ConsoleDriver driver, ColorScheme colorScheme, int y,
}
}

driver.SetAttribute (modelColor);
driver.AddStr (lineBody);
attr = modelColor;
cells.AddRange (lineBody.Select (r => NewRuneCell (attr, new Rune (r))));

if (availableWidth > 0) {
driver.SetAttribute (symbolColor);
driver.AddStr (new string (' ', availableWidth));
attr = symbolColor;
cells.AddRange (
Enumerable.Repeat (
NewRuneCell (attr, new Rune (' ')),
availableWidth
));
}

var e = new DrawTreeViewLineEventArgs<T> {
Model = Model,
Y = y,
RuneCells = cells,
Tree = tree,
IndexOfExpandCollapseSymbol = indexOfExpandCollapseSymbol,
IndexOfModelText = indexOfModelText,
};
tree.OnDrawLine (e);

if (!e.Handled) {
foreach (var cell in cells) {
driver.SetAttribute (cell.ColorScheme.Normal);
driver.AddRune (cell.Rune);
}
}

driver.SetAttribute (colorScheme.Normal);
}

private static RuneCell NewRuneCell (Attribute attr, Rune r)
{
return new RuneCell {
Rune = r,
ColorScheme = new ColorScheme (attr)
};
}


/// <summary>
/// Gets all characters to render prior to the current branches line. This includes indentation
/// whitespace and any tree branches (if enabled).
Expand Down
66 changes: 66 additions & 0 deletions Terminal.Gui/Views/TreeView/DrawTreeViewLineEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This code is based on http://objectlistview.sourceforge.net (GPLv3 tree/list controls
// by [email protected]). Phillip has explicitly granted permission for his design
// and code to be used in this library under the MIT license.

using System.Collections.Generic;

namespace Terminal.Gui {
/// <summary>
/// Event args for the <see cref="TreeView{T}.DrawLine"/> event
/// </summary>
/// <typeparam name="T"></typeparam>
public class DrawTreeViewLineEventArgs<T> where T : class {

/// <summary>
/// The object at this line in the tree
/// </summary>
public T Model { get; init; }

/// <summary>
/// The <see cref="TreeView{T}"/> that is performing the
/// rendering.
/// </summary>
public TreeView<T> Tree { get; init; }

/// <summary>
/// The line within tree view bounds that is being rendered
/// </summary>
public int Y { get; init; }

/// <summary>
/// Set to true to cancel drawing (e.g. if you have already manually
/// drawn content).
/// </summary>
public bool Handled { get; set; }

/// <summary>
/// The rune and color of each symbol that will be rendered. Note
/// that only <see cref="ColorScheme.Normal"/> is respected. You
/// can modify these to change what is rendered.
/// </summary>
/// <remarks>
/// Changing the length of this collection may result in corrupt rendering
/// </remarks>
public List<RuneCell> RuneCells { get; init; }

/// <summary>
/// The notional index in <see cref="RuneCells"/> which contains the first
/// character of the <see cref="TreeView{T}.AspectGetter"/> text (i.e.
/// after all branch lines and expansion/collapse sybmols).
/// </summary>
/// <remarks>
/// May be negative or outside of bounds of <see cref="RuneCells"/> if the view
/// has been scrolled horizontally.
/// </remarks>

public int IndexOfModelText { get; init; }

/// <summary>
/// If line contains a branch that can be expanded/collapsed then this is
/// the index in <see cref="RuneCells"/> at which the symbol is (or null for
/// leaf elements).
/// </summary>
public int? IndexOfExpandCollapseSymbol { get; init; }

}
}
53 changes: 32 additions & 21 deletions Terminal.Gui/Views/TreeView/TreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ public Key ObjectActivationKey {
/// </summary>
public event EventHandler<SelectionChangedEventArgs<T>> SelectionChanged;

/// <summary>
/// Called once for each visible row during rendering. Can be used
/// to make last minute changes to color or text rendered
/// </summary>
public event EventHandler<DrawTreeViewLineEventArgs<T>> DrawLine;

/// <summary>
/// The root objects in the tree, note that this collection is of root objects only.
/// </summary>
Expand Down Expand Up @@ -557,10 +563,9 @@ internal IReadOnlyCollection<Branch<T>> BuildLineMap ()
List<Branch<T>> toReturn = new List<Branch<T>> ();

foreach (var root in roots.Values) {

var toAdd = AddToLineMap (root, false, out var isMatch);
if(isMatch)
{
if (isMatch) {
toReturn.AddRange (toAdd);
}
}
Expand All @@ -574,41 +579,38 @@ internal IReadOnlyCollection<Branch<T>> BuildLineMap ()

private bool IsFilterMatch (Branch<T> branch)
{
return Filter?.IsMatch(branch.Model) ?? true;
return Filter?.IsMatch (branch.Model) ?? true;
}

private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch,bool parentMatches, out bool match)
private IEnumerable<Branch<T>> AddToLineMap (Branch<T> currentBranch, bool parentMatches, out bool match)
{
bool weMatch = IsFilterMatch(currentBranch);
bool weMatch = IsFilterMatch (currentBranch);
bool anyChildMatches = false;
var toReturn = new List<Branch<T>>();
var children = new List<Branch<T>>();

var toReturn = new List<Branch<T>> ();
var children = new List<Branch<T>> ();

if (currentBranch.IsExpanded) {
foreach (var subBranch in currentBranch.ChildBranches.Values) {

foreach (var sub in AddToLineMap (subBranch, weMatch, out var childMatch)) {

if(childMatch)
{
children.Add(sub);

if (childMatch) {
children.Add (sub);
anyChildMatches = true;
}
}
}
}

if(parentMatches || weMatch || anyChildMatches)
{
if (parentMatches || weMatch || anyChildMatches) {
match = true;
toReturn.Add(currentBranch);
}
else{
toReturn.Add (currentBranch);
} else {
match = false;
}
toReturn.AddRange(children);

toReturn.AddRange (children);
return toReturn;
}

Expand Down Expand Up @@ -1421,8 +1423,17 @@ protected virtual void OnSelectionChanged (SelectionChangedEventArgs<T> e)
{
SelectionChanged?.Invoke (this, e);
}
}

/// <summary>
/// Raises the DrawLine event
/// </summary>
/// <param name="e"></param>
internal void OnDrawLine (DrawTreeViewLineEventArgs<T> e)
{
DrawLine?.Invoke (this, e);
}

}
class TreeSelection<T> where T : class {

public Branch<T> Origin { get; }
Expand Down
Loading
Loading