Skip to content

Commit

Permalink
Tree view improvements
Browse files Browse the repository at this point in the history
1. Tree view will appear at the top of its containing parent and not at the bottom
2. Added top and left padding options
3. Added the ability to pause/resume tree layout and used it to improve performance in the inspector
4. Improved code which avoids creating tree nodes which are not yet visible
  • Loading branch information
tzachshabtay committed Feb 14, 2019
1 parent 913e152 commit f0fb511
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 37 deletions.
18 changes: 18 additions & 0 deletions Source/AGS.API/UI/Controls/Tree/ITreeViewComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ public interface ITreeViewComponent : IComponent
/// <value>The vertical spacing.</value>
float VerticalSpacing { get; set; }

/// <summary>
/// Gets or sets the top padding for the tree view.
/// </summary>
/// <value>The top padding.</value>
float TopPadding { get; set; }

/// <summary>
/// Gets or sets the left padding for the tree view.
/// </summary>
/// <value>The left padding.</value>
float LeftPadding { get; set; }

/// <summary>
/// Allow pausing/resuming the tree layout.
/// </summary>
/// <value><c>true</c> if layout paused; otherwise, <c>false</c>.</value>
bool LayoutPaused { get; set; }

/// <summary>
/// Gets or sets whether to allow selecting nodes in the tree.
/// </summary>
Expand Down
12 changes: 4 additions & 8 deletions Source/Editor/AGS.Editor/GameView/GameDebugTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class GameDebugTree : IDebugTab
private readonly List<RoomSubscriber> _roomSubscribers;
private readonly InspectorPanel _inspector;
private readonly ConcurrentDictionary<string, ITreeStringNode> _entitiesToNodes;
private IPanel _treePanel, _scrollingPanel, _contentsPanel, _parent;
private IPanel _scrollingPanel, _contentsPanel, _parent;
private ITextBox _searchBox;

private IEnabledComponent _lastSelectedEnabled;
Expand All @@ -34,7 +34,6 @@ public class GameDebugTree : IDebugTab
private bool _lastEnabled;
private bool _lastClickThrough;

const float _padding = 42f;
const float _gutterSize = 15f;

const string _moreRoomsPrefix = "More Rooms";
Expand Down Expand Up @@ -75,17 +74,14 @@ public void Load(IPanel parent)
_scrollingPanel.Tint = Colors.Transparent;
_scrollingPanel.Border = factory.Graphics.Borders.SolidColor(GameViewColors.Border, 2f);
_contentsPanel = factory.UI.CreateScrollingPanel(_scrollingPanel);
_treePanel = factory.UI.GetPanel("GameDebugTreePanel", 1f, 1f, 0f, _contentsPanel.Height - _padding, _contentsPanel);
_treePanel.Tint = Colors.Transparent;
_treePanel.RenderLayer = _layer;
_treePanel.Pivot = new PointF(0f, 1f);
_treeView = _treePanel.AddComponent<ITreeViewComponent>();
_treeView = _contentsPanel.AddComponent<ITreeViewComponent>();
_treeView.OnNodeSelected.Subscribe(onTreeNodeSelected);
_treeView.TopPadding = 30f;
_treeView.LeftPadding = 5f;
parent.GetComponent<IScaleComponent>().PropertyChanged += (_, args) =>
{
if (args.PropertyName != nameof(IScaleComponent.Height)) return;
_contentsPanel.BaseSize = new SizeF(_contentsPanel.Width, parent.Height - _searchBox.Height - _gutterSize);
_treePanel.Y = _contentsPanel.Height - _padding;
_searchBox.Y = _parent.Height;
};
}
Expand Down
6 changes: 5 additions & 1 deletion Source/Editor/AGS.Editor/GameView/Inspector/AGSInspector.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using AGS.API;
using AGS.Engine;
using GuiLabs.Undo;
Expand Down Expand Up @@ -99,10 +101,11 @@ private void configureTree()
treeView.VerticalSpacing = 40f;
}

private void refreshTree()
private async void refreshTree()
{
var treeView = _treeView;
if (treeView == null) return;
treeView.LayoutPaused = true;
var root = new AGSTreeStringNode("", _font);
List<ITreeStringNode> toExpand = new List<ITreeStringNode>();
bool skipCategories = _props.Count == 1;
Expand Down Expand Up @@ -134,6 +137,7 @@ private void refreshTree()
{
treeView.Expand(node);
}
treeView.LayoutPaused = false;
}

private ITreeStringNode addToTree(ITreeStringNode parent, IProperty prop, bool isCategory)
Expand Down
16 changes: 6 additions & 10 deletions Source/Editor/AGS.Editor/GameView/Inspector/InspectorPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ public class InspectorPanel
private readonly IRenderLayer _layer;
private readonly AGSEditor _editor;
private readonly ActionManager _actions;
private IPanel _treePanel, _scrollingPanel, _contentsPanel, _parent;
private IPanel _scrollingPanel, _contentsPanel, _parent;
private ITextBox _searchBox;
private InspectorTreeNodeProvider _inspectorNodeView;
private readonly string _idPrefix;

const float _padding = 28f;
const float _gutterSize = 15f;

public InspectorPanel(AGSEditor editor, IRenderLayer layer, ActionManager actions, string idPrefix = "")
Expand Down Expand Up @@ -49,21 +48,19 @@ public void Load(IPanel parent, IForm parentForm)
_scrollingPanel.Border = factory.Graphics.Borders.SolidColor(GameViewColors.Border, 2f);
_contentsPanel = factory.UI.CreateScrollingPanel(_scrollingPanel);

_treePanel = factory.UI.GetPanel($"{_idPrefix}_InspectorPanel", 0f, 0f, 0f, _contentsPanel.Height - _padding, _contentsPanel);
_treePanel.Tint = Colors.Transparent;
_treePanel.RenderLayer = _layer;
_treePanel.Pivot = new PointF(0f, 1f);
var treeView = _treePanel.AddComponent<ITreeViewComponent>();
var treeView = _contentsPanel.AddComponent<ITreeViewComponent>();
treeView.SkipRenderingRoot = true;
treeView.LayoutPaused = true;

Inspector = new AGSInspector(_editor.Editor.Factory, _editor.Game.Settings, _editor.Editor.Settings, _actions, _editor.Project.Model, _editor, parentForm);
_treePanel.AddComponent<IInspectorComponent>(Inspector);
_contentsPanel.AddComponent<IInspectorComponent>(Inspector);
Inspector.ScrollingContainer = _contentsPanel;

_inspectorNodeView = new InspectorTreeNodeProvider(treeView.NodeViewProvider,
_editor.Editor.Events, _treePanel);
_editor.Editor.Events, _contentsPanel);
_inspectorNodeView.Resize(_contentsPanel.Width);
treeView.NodeViewProvider = _inspectorNodeView;
treeView.TopPadding = 30f;

_parent.Bind<IScaleComponent>(c => c.PropertyChanged += onParentPanelScaleChanged,
c => c.PropertyChanged -= onParentPanelScaleChanged);
Expand Down Expand Up @@ -96,7 +93,6 @@ private void onParentPanelScaleChanged(object sender, PropertyChangedEventArgs a
_contentsPanel.BaseSize = new SizeF(_contentsPanel.Width, _parent.Height - _searchBox.Height - _gutterSize);
_scrollingPanel.Y = _parent.Height - _searchBox.Height;
_searchBox.Y = _parent.Height;
_treePanel.Y = _contentsPanel.Height - _padding;
}

private void onSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public ITreeNodeView CreateNode(ITreeStringNode item, IRenderLayer layer, IObjec
expandButton.RenderLayer = layer;
label.RenderLayer = layer;
expandButton.Z = label.Z - 1;
parentPanel.Pivot = (0f, 1f);
horizontalPanel.Tint = Colors.Transparent;
parentPanel.Tint = Colors.Transparent;
verticalPanel.Tint = Colors.Transparent;
Expand Down
68 changes: 50 additions & 18 deletions Source/Engine/AGS.Engine/UI/Controls/Tree/AGSTreeViewComponent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading.Tasks;
using AGS.API;

Expand All @@ -9,19 +10,20 @@ namespace AGS.Engine
public class AGSTreeViewComponent : AGSComponent, ITreeViewComponent
{
private Node _root;
private IGameState _state;
private readonly IGameState _state;
private readonly IGameSettings _settings;
private IInObjectTreeComponent _treeComponent;
private ITreeStringNode _tree;
private IDrawableInfoComponent _drawable;
private ICropChildrenComponent _crop;
private IScrollingComponent _scrolling;
private IBoundingBoxComponent _box;
private IBoundingBoxComponent _scrollingBox, _box;
private string _searchFilter;
private TaskCompletionSource<object> _currentSearchToken;
private bool _skipRenderingRoot;
private bool _skipRenderingRoot, _layoutPaused;
private IEntity _scrollingContainer;

public AGSTreeViewComponent(ITreeNodeViewProvider provider, IGameState state)
public AGSTreeViewComponent(ITreeNodeViewProvider provider, IGameState state, IGameSettings settings)
{
HorizontalSpacing = 10f;
VerticalSpacing = 30f;
Expand All @@ -30,6 +32,7 @@ public AGSTreeViewComponent(ITreeNodeViewProvider provider, IGameState state)
OnNodeCollapsed = new AGSEvent<NodeEventArgs>();
AllowSelection = SelectionType.Single;
_state = state;
_settings = settings;
NodeViewProvider = provider;
}

Expand All @@ -52,6 +55,10 @@ public ITreeStringNode Tree

public float VerticalSpacing { get; set; }

public float TopPadding { get; set; }

public float LeftPadding { get; set; }

public SelectionType AllowSelection { get; set; }

public string SearchFilter
Expand All @@ -75,6 +82,16 @@ public bool SkipRenderingRoot
}
}

public bool LayoutPaused
{
get => _layoutPaused;
set
{
_layoutPaused = value;
RefreshLayout();
}
}

public IBlockingEvent<NodeEventArgs> OnNodeSelected { get; }

public IBlockingEvent<NodeEventArgs> OnNodeExpanded { get; }
Expand All @@ -90,7 +107,7 @@ public IEntity ScrollingContainer
if (value != null)
{
value.Bind<ICropChildrenComponent>(c => { _crop = c; c.PropertyChanged += onCropPropertyChanged; }, c => { _crop = null; c.PropertyChanged -= onCropPropertyChanged; });
value.Bind<IBoundingBoxComponent>(c => { _box = c; c.OnBoundingBoxesChanged.Subscribe(refreshTree); }, c => { _box = null; c.OnBoundingBoxesChanged.Unsubscribe(refreshTree); });
value.Bind<IBoundingBoxComponent>(c => { _scrollingBox = c; c.OnBoundingBoxesChanged.Subscribe(refreshTree); refreshTree(); }, c => { _scrollingBox = null; c.OnBoundingBoxesChanged.Unsubscribe(refreshTree); });
value.Bind<IScrollingComponent>(c => _scrolling = c, _ => _scrolling = null);
}
}
Expand All @@ -100,7 +117,8 @@ public override void Init()
{
base.Init();
Entity.Bind<IInObjectTreeComponent>(c => _treeComponent = c, _ => _treeComponent = null);
Entity.Bind<IDrawableInfoComponent>(c => _drawable = c, _ => _drawable = null);
Entity.Bind<IDrawableInfoComponent>(c => { _drawable = c; refreshTree(); }, _ => _drawable = null);
Entity.Bind<IBoundingBoxComponent>(c => { _box = c; c.OnBoundingBoxesChanged.Subscribe(refreshTree); refreshTree(); }, c => { _box = null; c.OnBoundingBoxesChanged.Unsubscribe(refreshTree); });
}

public override void Dispose()
Expand Down Expand Up @@ -190,14 +208,22 @@ private Node findNodeView(Node nodeView, ITreeStringNode node)

private void refreshTree()
{
if (_layoutPaused) return;
var root = _root;
if (root != null)
var box = _box;
var drawable = _drawable;
if (root != null && box != null && drawable != null)
{
float minY = 0f;
root.ResetOffsets(0f, 0f, HorizontalSpacing, -VerticalSpacing, ref minY);
var worldHeight = _settings.VirtualResolution.Height;
var layerHeight = drawable.RenderLayer.IndependentResolution?.Height ?? worldHeight;
float startY = MathUtils.Lerp(0f, 0f, worldHeight, layerHeight, box.WorldBoundingBox.Height) - TopPadding;
root.ResetOffsets(startY, startY, HorizontalSpacing, -VerticalSpacing, ref minY);
if (root.View?.ParentPanel != null)
root.View.ParentPanel.X = LeftPadding;
if (_scrolling != null)
{
_scrolling.ContentsHeight = Math.Abs(minY) + VerticalSpacing;
_scrolling.ContentsHeight = startY - minY + VerticalSpacing;
}
List<IObject> uiObjectsToAdd = new List<IObject>();
processTree(root, uiObjectsToAdd);
Expand Down Expand Up @@ -268,8 +294,8 @@ private Node buildTree(Node currentNode, ITreeStringNode actualNode)
if (currentNode == null || currentNode.Item != actualNode)
{
if (currentNode != null) removeFromUI(currentNode);
currentNode = new Node(actualNode, _state, () => _drawable, () => _treeComponent, () => _box, () => _crop,
null, this);
currentNode = new Node(actualNode, _state, () => _drawable, () => _treeComponent, () => _scrollingBox, () => _crop,
null, this, _settings);
}
int maxChildren = Math.Max(currentNode.Children.Count, actualNode.TreeNode.Children.Count);
for (int i = 0; i < maxChildren; i++)
Expand All @@ -279,8 +305,8 @@ private Node buildTree(Node currentNode, ITreeStringNode actualNode)
if (nodeChild == null && actualChild == null) continue;
if (nodeChild == null)
{
var newNode = new Node(actualChild, _state, () => _drawable, () => null, () => _box, () => _crop,
currentNode, this);
var newNode = new Node(actualChild, _state, () => _drawable, () => null, () => _scrollingBox, () => _crop,
currentNode, this, _settings);
newNode = buildTree(newNode, actualChild);
currentNode.Children.Add(newNode);
continue;
Expand Down Expand Up @@ -351,6 +377,7 @@ private class Node : IDisposable
private readonly Func<IBoundingBoxComponent> _box;
private readonly Func<ICropChildrenComponent> _crop;
private readonly IGameState _state;
private readonly IGameSettings _settings;

public enum SearchFilterMode
{
Expand All @@ -362,12 +389,13 @@ public enum SearchFilterMode

public Node(ITreeStringNode item, IGameState state, Func<IDrawableInfoComponent> drawable,
Func<IInObjectTreeComponent> treeObj, Func<IBoundingBoxComponent> box, Func<ICropChildrenComponent> crop,
Node parentNode, ITreeViewComponent tree)
Node parentNode, ITreeViewComponent tree, IGameSettings settings)
{
_tree = tree;
_state = state;
_drawable = drawable;
_treeObj = treeObj;
_settings = settings;
_box = box;
_crop = crop;
Item = item;
Expand Down Expand Up @@ -549,18 +577,22 @@ private bool passesFilter(string filter)

private void initView()
{
if (View != null) return;
if (View != null || _tree.LayoutPaused) return;
var crop = _crop();
var box = _box();
if (crop != null && box != null)
var drawable = _drawable();
if (crop != null && box != null && drawable != null)
{
if (OverallYOffset > crop.StartPoint.Y || OverallYOffset < crop.StartPoint.Y - box.GetBoundingBoxes(_state.Viewport).ViewportBox.Height)
var worldHeight = _settings.VirtualResolution.Height;
var layerHeight = drawable.RenderLayer.IndependentResolution?.Height ?? worldHeight;

var height = box.GetBoundingBoxes(_state.Viewport).ViewportBox.Height;
if (OverallYOffset > crop.StartPoint.Y + height || OverallYOffset < crop.StartPoint.Y)
{
return;
}
}
if (View != null) return; //The GetBoundingBoxes call above might trigger a bounding box change event, which can trigger another initView call. So we need to check again that view is not null so to not create two GUIs for the same node.
var drawable = _drawable();
var parentNode = Parent;
if (parentNode != null)
{
Expand Down

0 comments on commit f0fb511

Please sign in to comment.