namespace UIWidgets { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using UnityEngine; /// /// Track. /// /// Data type. /// Point type. [Serializable] public class Track : IObservable, INotifyPropertyChanged where TData : class, ITrackData where TPoint : IComparable { [SerializeField] string name; /// /// Name. /// public string Name { get { return name; } set { if (name != value) { name = value; NotifyPropertyChanged("Name"); } } } [SerializeField] ObservableList data = new ObservableList(); /// /// Data. /// public ObservableList Data { get { return data; } set { if (data != value) { data.OnChange -= DataChanged; data = value; data.OnChange += DataChanged; DataChanged(); NotifyPropertyChanged("Data"); } } } /// /// Max items at the same point. /// public int MaxItemsAtSamePoint { get; protected set; } /// /// Sorted data. /// protected List SortedData = new List(); readonly List visibleItems = new List(); /// /// First visible point. /// protected TPoint VisibleStart; /// /// Last visible point. /// protected TPoint VisibleEnd; /// /// First visible index. /// protected int VisibleIndexStart; /// /// Last visible index. /// protected int VisibleIndexEnd; /// /// Visible items. /// public ReadOnlyCollection VisibleItems { get { return visibleItems.AsReadOnly(); } } TrackLayout layout; /// /// Layout function. /// public TrackLayout Layout { get { return layout; } set { if (layout != value) { layout = value; SetOrder(); } } } /// /// Set minimal order of the items. /// public bool ItemsToTop { get; set; } bool separateGroups = true; /// /// If enabled, each group of the overlapped items will be processed separately; otherwise, all items at once. /// public bool SeparateGroups { get { return separateGroups; } set { if (separateGroups != value) { separateGroups = value; SetOrder(); } } } /// /// Default items comparison. /// /// First item. /// Second item. /// Comparison result. protected int DefaultComparison(TData x, TData y) { var result = x.StartPoint.CompareTo(y.StartPoint); if (result != 0) { return result; } result = x.EndPoint.CompareTo(y.EndPoint); if (result != 0) { return result; } return x.Order.CompareTo(y.Order); } /// /// Items comparison by the order value. /// /// First item. /// Second item. /// Comparison result. protected int GroupComparison(TData x, TData y) { var result = x.Order.CompareTo(y.Order); if (result != 0) { return result; } return -x.FixedOrder.CompareTo(y.FixedOrder); } /// /// Initializes a new instance of the class. /// public Track() { Data.OnChange += DataChanged; ItemsToTop = true; SetOrder(); } /// /// Get visible items. /// /// Output list. public void GetVisibleItems(List output) { output.AddRange(visibleItems); } /// /// Count of the items at the specified point. /// /// Point. /// Count of the items. protected int ItemsAtPoint(TPoint point) { return ItemsBetweenPoint(point, point); } /// /// Count of the items between specified points. /// /// Start point. /// End point. /// Count of the items. protected int ItemsBetweenPoint(TPoint startPoint, TPoint endPoint) { var start = FindIndexStart(startPoint); var end = FindIndexEnd(endPoint); return end - start + 1; } /// /// Update SortedData list. /// protected void UpdateSortedData() { SortedData.Clear(); SortedData.AddRange(Data); SortedData.Sort(DefaultComparison); } /// /// Process data changed event. /// protected void DataChanged() { SetOrder(); CalculateVisibleItems(); NotifyPropertyChanged("Data"); } /// /// Group of the overlapped items. /// protected List Group = new List(); /// /// Set order of the items. /// public void SetOrder() { UpdateSortedData(); CalculateOrder(); } /// /// Calculate order. /// protected void CalculateOrder() { if (SortedData.Count == 0) { MaxItemsAtSamePoint = 1; return; } if (!SeparateGroups) { Group.AddRange(SortedData); MaxItemsAtSamePoint = UpdateOrder(Group) + 1; Group.Clear(); return; } var first = SortedData[0]; Group.Add(first); var max_order = 0; var start = first.StartPoint; var end = first.EndPoint; for (int i = 1; i < SortedData.Count; i++) { var point = SortedData[i]; if (InRange(point, start, end)) { start = Min(start, point.StartPoint); end = Max(end, point.EndPoint); } else { max_order = Mathf.Max(max_order, UpdateOrder(Group)); Group.Clear(); start = point.StartPoint; end = point.EndPoint; } Group.Add(point); } max_order = Mathf.Max(max_order, UpdateOrder(Group)); Group.Clear(); MaxItemsAtSamePoint = max_order + 1; } /// /// Update items order for the specified group. /// /// Group. /// Max order in the group. protected int UpdateOrder(List group) { group.Sort(GroupComparison); SetOrder(group); return GetMaxOrder(group); } /// /// Find group with specified item. /// /// Item. protected void FindGroup(TData item) { if (SortedData.Count == 0) { return; } if (!SeparateGroups) { Group.AddRange(SortedData); return; } var start = SortedData[0].StartPoint; var end = SortedData[0].EndPoint; Group.Add(SortedData[0]); for (int i = 1; i < SortedData.Count; i++) { var point = SortedData[i]; if (InRange(point, start, end)) { start = Min(start, point.StartPoint); end = Max(end, point.EndPoint); } else { if (Group.Contains(item)) { return; } else { Group.Clear(); start = point.StartPoint; end = point.EndPoint; } } Group.Add(point); } } /// /// Set order of the item. /// /// Item. /// New item order. /// Previous item order. public virtual void SetItemOrder(TData item, int newOrder, int prevOrder) { if (newOrder < 0) { newOrder = 0; } if (ItemsToTop) { SetItemOrderTop(item, newOrder, prevOrder); } else { SetItemOrderCustom(item, newOrder, prevOrder); } SetOrder(Group); Group.Clear(); MaxItemsAtSamePoint = GetMaxOrder(SortedData) + 1; NotifyPropertyChanged("Data"); } /// /// Get maximal order for items in the specified list. /// /// List. /// Maximal order. protected int GetMaxOrder(List list) { var max_order = 0; for (int i = 0; i < list.Count; i++) { max_order = Mathf.Max(max_order, list[i].Order); } return max_order; } /// /// Get maximal order for items with FixedOrder in the specified list. /// /// List. /// Maximal order. protected int GetMaxFixedOrder(List list) { var max_order = 0; for (int i = 0; i < list.Count; i++) { if (list[i].FixedOrder) { max_order = Mathf.Max(max_order, list[i].Order); } } return max_order; } /// /// Set custom order of the item. /// /// Item. /// New item order. /// Previous item order. protected virtual void SetItemOrderCustom(TData item, int newOrder, int prevOrder) { var max_group_order = 0; foreach (var b in SortedData) { if (ReferenceEquals(b, item) && (prevOrder == -1)) { continue; } max_group_order = Math.Max(max_group_order, b.Order); } newOrder = Math.Min(newOrder, max_group_order); item.Order = newOrder; item.FixedOrder = true; UpdateSortedData(); FindGroup(item); Group.Sort(GroupComparison); // disable FixedOrder for other items with the same order for (int i = 0; i < Group.Count; i++) { var next_item = Group[i]; if ((next_item.Order < item.Order) || ReferenceEquals(next_item, item)) { continue; } if (next_item.Order > item.Order) { break; } next_item.FixedOrder = false; } Group.Sort(GroupComparison); } /// /// Set minimal order of the item. /// /// Item. /// New item order. /// Previous item order. protected virtual void SetItemOrderTop(TData item, int newOrder, int prevOrder) { item.Order = newOrder; UpdateSortedData(); FindGroup(item); Group.Sort(GroupComparison); Group.Remove(item); if (newOrder < Group.Count) { Group.Insert(newOrder, item); } else { Group.Add(item); } } /// /// Set order for the specified items. /// /// Items. protected void SetOrder(List items) { if (Layout != null) { Layout.Set(items); } } /// /// Get minimal point. /// /// Point 1. /// Point 2. /// Minimal point. protected TPoint Min(TPoint point1, TPoint point2) { return point1.CompareTo(point2) >= 0 ? point2 : point1; } /// /// Get maximum point. /// /// Point 1. /// Point 2. /// Maximum point. protected TPoint Max(TPoint point1, TPoint point2) { return point1.CompareTo(point2) >= 0 ? point1 : point2; } /// /// Calculate visible items. /// protected void CalculateVisibleItems() { var new_start = FindIndexStart(VisibleStart); var new_end = FindIndexEnd(VisibleEnd); if ((new_start == VisibleIndexStart) && (new_end == VisibleIndexEnd)) { return; } VisibleIndexStart = new_start; VisibleIndexEnd = new_end; visibleItems.Clear(); for (int i = VisibleIndexStart; i <= VisibleIndexEnd; i++) { if (i < 0) { continue; } if (IsVisible(SortedData[i])) { visibleItems.Add(SortedData[i]); } } var dragged_index = GetDraggedIndex(); var dragged_index_visible = (dragged_index >= VisibleIndexStart) && (dragged_index <= VisibleIndexEnd); if ((dragged_index >= 0) && (!dragged_index_visible)) { visibleItems.Add(SortedData[dragged_index]); } } /// /// Get index of the dragged item. /// /// Index. protected int GetDraggedIndex() { for (int index = 0; index < SortedData.Count; index++) { if (SortedData[index].IsDragged) { return index; } } return -1; } /// /// Is data visible? /// /// Item. /// true if data visible; otherwise false. protected bool IsVisible(TData item) { return InRange(item, VisibleStart, VisibleEnd); } /// /// Is data located between specified points. /// /// Item. /// Start point. /// End point. /// true if data located between specified points; otherwise false. protected bool InRange(TData item, TPoint startPoint, TPoint endPoint) { var start = (startPoint.CompareTo(item.StartPoint) <= 0) || (startPoint.CompareTo(item.EndPoint) < 0); var end = (endPoint.CompareTo(item.StartPoint) > 0) || (endPoint.CompareTo(item.EndPoint) >= 0); return start && end; } /// /// Find index of the first data located at or after specified point. /// /// Point. /// Index of the first data located at or after specified point; -1 if data not found. protected int FindIndexStart(TPoint point) { // NOTE: replace with binary search for (int i = 0; i < SortedData.Count; i++) { if ((point.CompareTo(SortedData[i].StartPoint) <= 0) || (point.CompareTo(SortedData[i].EndPoint) <= 0)) { return i; } } return -1; } /// /// Find index of the last data located at or after specified point. /// /// Point. /// Index of the last data located at or after specified point; -1 if data not found. protected int FindIndexEnd(TPoint point) { // NOTE: replace with binary search for (int i = SortedData.Count - 1; i >= 0; i--) { if ((point.CompareTo(SortedData[i].StartPoint) >= 0) || (point.CompareTo(SortedData[i].EndPoint) >= 0)) { return i; } } return -1; } /// /// Reset visible range. /// public void VisibleRangeReset() { VisibleIndexStart = -1; VisibleIndexEnd = -1; visibleItems.Clear(); } /// /// Set visible range. /// /// Start point. /// End point. public void VisibleRangeSet(TPoint startPoint, TPoint endPoint) { VisibleStart = startPoint; VisibleEnd = endPoint; CalculateVisibleItems(); } /// /// Occurs when a property value changes. /// public event OnChange OnChange; /// /// Occurs when a property value changes. /// public event PropertyChangedEventHandler PropertyChanged; /// /// Raise PropertyChanged event. /// /// Property name. protected void NotifyPropertyChanged(string propertyName) { var c_handlers = OnChange; if (c_handlers != null) { c_handlers(); } var handlers = PropertyChanged; if (handlers != null) { handlers(this, new PropertyChangedEventArgs(propertyName)); } } /// /// Copy data to target. /// /// Target. public void CopyTo(Track target) { target.Name = Name; } /// /// Process the destroy event. /// protected virtual void OnDestroy() { if (data != null) { data.OnChange -= DataChanged; } } } }