File: ValueTracking\TreeViewItemBase.cs
Web Access
Project: ..\..\..\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_ckcrqypr_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.VisualStudio.LanguageServices.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.ValueTracking
{
    internal class TreeViewItemBase : ViewModelBase
    {
        public ObservableCollection<TreeViewItemBase> ChildItems { get; } = new();
        public TreeViewItemBase? Parent { get; set; }
 
        public virtual string AutomationName { get; } = string.Empty;
 
        private bool _isExpanded = false;
        public virtual bool IsNodeExpanded
        {
            get => _isExpanded;
            set => SetProperty(ref _isExpanded, value);
        }
 
        private bool _isSelected = false;
        public bool IsNodeSelected
        {
            get => _isSelected;
            set => SetProperty(ref _isSelected, value);
        }
 
        private bool _isLoading;
        public bool IsLoading
        {
            get => _isLoading;
            set => SetProperty(ref _isLoading, value);
        }
 
        public TreeViewItemBase()
        {
            ChildItems.CollectionChanged += ChildItems_CollectionChanged;
        }
 
        /// <summary>
        /// Returns the next logical item in the tree that could be seen (Parent is expanded)
        /// </summary>
        public TreeViewItemBase GetNextInTree()
        {
            if (IsNodeExpanded && ChildItems.Any())
            {
                return ChildItems.First();
            }
 
            var sibling = GetSibling(next: true);
            if (sibling is not null)
            {
                return sibling;
            }
 
            return Parent?.GetSibling(next: true) ?? this;
        }
 
        /// <summary>
        /// Returns the previous logical item in the tree that could be seen (Parent is expanded)
        /// </summary>
        public TreeViewItemBase GetPreviousInTree()
        {
            var sibling = GetSibling(next: false);
            if (sibling is not null)
            {
                return sibling.GetLastVisibleDescendentOrSelf();
            }
 
            return Parent ?? this;
        }
 
        private TreeViewItemBase GetLastVisibleDescendentOrSelf()
        {
            if (!IsNodeExpanded || ChildItems.Count == 0)
            {
                return this;
            }
 
            var lastChild = ChildItems.Last();
            return lastChild.GetLastVisibleDescendentOrSelf();
        }
 
        private TreeViewItemBase? GetSibling(bool next = true)
        {
            if (Parent is null)
            {
                return null;
            }
 
            var thisIndex = Parent.ChildItems.IndexOf(this);
            var siblingIndex = next ? thisIndex + 1 : thisIndex - 1;
 
            if (siblingIndex < 0 || siblingIndex >= Parent.ChildItems.Count)
            {
                return null;
            }
 
            return Parent.ChildItems[siblingIndex];
        }
 
        private void ChildItems_CollectionChanged(object _, NotifyCollectionChangedEventArgs args)
        {
            if (args.Action is not NotifyCollectionChangedAction.Add and not NotifyCollectionChangedAction.Remove)
            {
                return;
            }
 
            SetParents(args.OldItems, null);
            SetParents(args.NewItems, this);
 
            static void SetParents(IList? items, TreeViewItemBase? parent)
            {
                if (items is null)
                {
                    return;
                }
 
                foreach (var item in items.Cast<TreeViewItemBase>())
                {
                    item.Parent = parent;
                }
            }
        }
    }
}