From ab14d79d959dac005df1982e67cb3958aa872622 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 11 Dec 2017 18:03:32 -0500 Subject: [PATCH 1/6] Start of a HistoryControl --- .../Assets/Editor/GitHub.Unity/Misc/Styles.cs | 17 + .../Editor/GitHub.Unity/UI/ChangesView.cs | 14 +- .../Editor/GitHub.Unity/UI/HistoryView.cs | 641 ++++++++---------- 3 files changed, 305 insertions(+), 367 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs index 264ea1291..c46e80bc9 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/Misc/Styles.cs @@ -64,6 +64,7 @@ class Styles headerDescriptionStyle, historyToolbarButtonStyle, historyLockStyle, + historyEntrySummaryStyle, historyEntryDetailsStyle, historyEntryDetailsRightStyle, historyFileTreeBoxStyle, @@ -396,6 +397,20 @@ public static GUIStyle HistoryLockStyle return historyLockStyle; } } + public static GUIStyle HistoryEntrySummaryStyle + { + get + { + if (historyEntrySummaryStyle == null) + { + historyEntrySummaryStyle = new GUIStyle(Label); + historyEntrySummaryStyle.name = "HistoryEntrySummaryStyle"; + + historyEntrySummaryStyle.contentOffset = new Vector2(BaseSpacing * 2, 0); + } + return historyEntrySummaryStyle; + } + } public static GUIStyle HistoryEntryDetailsStyle { @@ -412,6 +427,8 @@ public static GUIStyle HistoryEntryDetailsStyle historyEntryDetailsStyle.onNormal.textColor = Label.onNormal.textColor; historyEntryDetailsStyle.onFocused.background = Label.onFocused.background; historyEntryDetailsStyle.onFocused.textColor = Label.onFocused.textColor; + + historyEntryDetailsStyle.contentOffset = new Vector2(BaseSpacing * 2, 0); } return historyEntryDetailsStyle; } diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs index bbada2d66..f5bef89bb 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/ChangesView.cs @@ -25,13 +25,17 @@ class ChangesView : Subview [SerializeField] private string commitBody = ""; [SerializeField] private string commitMessage = ""; [SerializeField] private string currentBranch = "[unknown]"; - [SerializeField] private Vector2 scroll; - [SerializeField] private CacheUpdateEvent lastCurrentBranchChangedEvent; - [SerializeField] private CacheUpdateEvent lastStatusEntriesChangedEvent; + + [SerializeField] private Vector2 treeScroll; [SerializeField] private ChangesTree treeChanges; + [SerializeField] private List gitStatusEntries; + [SerializeField] private string changedFilesText = NoChangedFilesLabel; + [SerializeField] private CacheUpdateEvent lastCurrentBranchChangedEvent; + [SerializeField] private CacheUpdateEvent lastStatusEntriesChangedEvent; + public override void OnEnable() { base.OnEnable(); @@ -81,7 +85,7 @@ public override void OnGUI() GUILayout.BeginHorizontal(); GUILayout.BeginVertical(Styles.CommitFileAreaStyle); { - scroll = GUILayout.BeginScrollView(scroll); + treeScroll = GUILayout.BeginScrollView(treeScroll); { OnTreeGUI(new Rect(0f, 0f, Position.width, Position.height - rect.height + Styles.CommitAreaPadding)); } @@ -111,7 +115,7 @@ private void OnTreeGUI(Rect rect) treeChanges.FocusedTreeNodeStyle = Styles.FocusedTreeNode; treeChanges.FocusedActiveTreeNodeStyle = Styles.FocusedActiveTreeNode; - rect = treeChanges.Render(initialRect, rect, scroll, + rect = treeChanges.Render(initialRect, rect, treeScroll, node => { }, node => { }, diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index aa790d9a9..253e0582f 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -6,6 +6,252 @@ namespace GitHub.Unity { + struct HistoryControlRenderResult + { + public Rect Rect; + public bool RequiresRepaint; + } + + [Serializable] + class HistoryControl + { + private const string HistoryEntryDetailFormat = "{0} {1}"; + + [SerializeField] private List entries = new List(); + [SerializeField] private int statusAhead; + [SerializeField] private int selectedIndex; + + [NonSerialized] private Action rightClickNextRender; + [NonSerialized] private GitLogEntry rightClickNextRenderEntry; + [NonSerialized] private int controlId; + + public HistoryControlRenderResult Render(Rect containingRect, Rect rect, Vector2 scroll, Action singleClick = null, + Action doubleClick = null, Action rightClick = null) + { + var requiresRepaint = false; + + controlId = GUIUtility.GetControlID(FocusType.Keyboard); + + var treeHasFocus = GUIUtility.keyboardControl == controlId; + if (Event.current.type != EventType.Repaint) + { + if (rightClickNextRender != null) + { + rightClickNextRender.Invoke(rightClickNextRenderEntry); + rightClickNextRender = null; + //TODO: Default GitLogEntry + rightClickNextRenderEntry = new GitLogEntry(); + } + } + + rect = new Rect(rect.x, rect.y, rect.width, 0); + + for (var index = 0; index < entries.Count; index++) + { + var entry = entries[index]; + var isLocalCommit = index < statusAhead; + + var entryRect = new Rect(rect.x, rect.y, rect.width, Styles.HistoryEntryHeight); + + RenderEntry(entryRect, entry, isLocalCommit, index == selectedIndex); + + var entryRequiresRepaint = HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); + requiresRepaint = requiresRepaint || entryRequiresRepaint; + + rect.y += Styles.HistoryEntryHeight; + } + + return new HistoryControlRenderResult { + Rect = rect, + RequiresRepaint = requiresRepaint + }; + } + + private bool HandleInput(Rect rect, GitLogEntry entry, int index, Action singleClick = null, + Action doubleClick = null, Action rightClick = null) + { + var requiresRepaint = false; + var clickRect = new Rect(0f, rect.y, rect.width, rect.height); + if (Event.current.type == EventType.MouseDown && clickRect.Contains(Event.current.mousePosition)) + { + Event.current.Use(); + GUIUtility.keyboardControl = controlId; + + selectedIndex = index; + requiresRepaint = true; + var clickCount = Event.current.clickCount; + var mouseButton = Event.current.button; + + if (mouseButton == 0 && clickCount == 1 && singleClick != null) + { + singleClick(entry); + } + if (mouseButton == 0 && clickCount > 1 && doubleClick != null) + { + doubleClick(entry); + } + if (mouseButton == 1 && clickCount == 1 && rightClick != null) + { + rightClickNextRender = rightClick; + rightClickNextRenderEntry = entry; + } + } + + // Keyboard navigation if this child is the current selection + if (GUIUtility.keyboardControl == controlId && index == selectedIndex && Event.current.type == EventType.KeyDown) + { + var directionY = Event.current.keyCode == KeyCode.UpArrow ? -1 : Event.current.keyCode == KeyCode.DownArrow ? 1 : 0; + if (directionY != 0) + { + Event.current.Use(); + + if (directionY > 0) + { + requiresRepaint = SelectNext(index) != index; + } + else + { + requiresRepaint = SelectPrevious(index) != index; + } + } + } + + return requiresRepaint; + } + + private void RenderEntry(Rect entryRect, GitLogEntry entry, bool isLocalCommit, bool isSelected) + { + if (Event.current.type == EventType.Repaint) + { + var summaryRect = new Rect(entryRect.x, entryRect.y + Styles.BaseSpacing / 2, entryRect.width, Styles.HistorySummaryHeight + Styles.BaseSpacing); + var timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight - Styles.BaseSpacing / 2, entryRect.width, Styles.HistoryDetailsHeight); + + var hasKeyboardFocus = false; + + Styles.Label.Draw(entryRect, GUIContent.none, false, false, isSelected, hasKeyboardFocus); + Styles.HistoryEntrySummaryStyle.Draw(summaryRect, entry.Summary, false, false, isSelected, hasKeyboardFocus); + + var historyEntryDetail = string.Format(HistoryEntryDetailFormat, entry.PrettyTimeString, entry.AuthorName); + Styles.HistoryEntryDetailsStyle.Draw(timestampRect, historyEntryDetail, false, false, isSelected, hasKeyboardFocus); + + if (!string.IsNullOrEmpty(entry.MergeA)) + { + const float MergeIndicatorWidth = 10.28f; + const float MergeIndicatorHeight = 12f; + var mergeIndicatorRect = new Rect(entryRect.x + 7, summaryRect.y, MergeIndicatorWidth, MergeIndicatorHeight); + + GUI.DrawTexture(mergeIndicatorRect, Styles.MergeIcon); + + DrawTimelineRectAroundIconRect(entryRect, mergeIndicatorRect); + + summaryRect.Set(mergeIndicatorRect.xMax, summaryRect.y, summaryRect.width - MergeIndicatorWidth, + summaryRect.height); + } + + if (isLocalCommit && string.IsNullOrEmpty(entry.MergeA)) + { + const float LocalIndicatorSize = 6f; + var localIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), summaryRect.y + 5, LocalIndicatorSize, + LocalIndicatorSize); + + DrawTimelineRectAroundIconRect(entryRect, localIndicatorRect); + + GUI.DrawTexture(localIndicatorRect, Styles.LocalCommitIcon); + + summaryRect.Set(localIndicatorRect.xMax, summaryRect.y, summaryRect.width - LocalIndicatorSize, + summaryRect.height); + } + + if (!isLocalCommit && string.IsNullOrEmpty(entry.MergeA)) + { + const float NormalIndicatorWidth = 6f; + const float NormalIndicatorHeight = 6f; + + Rect normalIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), summaryRect.y + 5, + NormalIndicatorWidth, NormalIndicatorHeight); + + DrawTimelineRectAroundIconRect(entryRect, normalIndicatorRect); + + GUI.DrawTexture(normalIndicatorRect, Styles.DotIcon); + } + } + } + + private void DrawTimelineRectAroundIconRect(Rect parentRect, Rect iconRect) + { + Color timelineBarColor = new Color(0.51F, 0.51F, 0.51F, 0.2F); + + // Draw them lines + // + // First I need to figure out how large to make the top one: + // I'll subtract the entryRect.y from the mergeIndicatorRect.y to + // get the difference in length. then subtract a little more for padding + float topTimelineRectHeight = iconRect.y - parentRect.y - 2; + // Now let's create the rect + Rect topTimelineRect = new Rect( + parentRect.x + Styles.BaseSpacing, + parentRect.y, + 2, + topTimelineRectHeight); + + // And draw it + EditorGUI.DrawRect(topTimelineRect, timelineBarColor); + + // Let's do the same for the bottom + float bottomTimelineRectHeight = parentRect.yMax - iconRect.yMax - 2; + Rect bottomTimelineRect = new Rect( + parentRect.x + Styles.BaseSpacing, + parentRect.yMax - bottomTimelineRectHeight, + 2, + bottomTimelineRectHeight); + EditorGUI.DrawRect(bottomTimelineRect, timelineBarColor); + } + + public void Load(int loadAhead, List loadEntries) + { + statusAhead = loadAhead; + entries = loadEntries; + } + + private int SelectNext(int index) + { + index++; + + if (index < entries.Count) + { + selectedIndex = index; + } + else + { + index = -1; + } + + return index; + } + + private int SelectPrevious(int index) + { + index--; + + if (index >= 0) + { + selectedIndex = index; + } + else + { + selectedIndex = -1; + } + + return index; + } + } + + enum LogEntryState + { + Normal, + Local + } + [Serializable] class HistoryView : Subview { @@ -33,38 +279,22 @@ class HistoryView : Subview [NonSerialized] private bool currentLogHasUpdate; [NonSerialized] private bool currentRemoteHasUpdate; [NonSerialized] private bool currentTrackingStatusHasUpdate; - [NonSerialized] private int historyStartIndex; - [NonSerialized] private int historyStopIndex; - [NonSerialized] private int listID; - [NonSerialized] private int newSelectionIndex; - [NonSerialized] private float scrollOffset; - [NonSerialized] private DateTimeOffset scrollTime = DateTimeOffset.Now; - [NonSerialized] private int selectionIndex; - [NonSerialized] private bool useScrollTime; - - [SerializeField] private ChangesetTreeView changesetTree = new ChangesetTreeView(); - [SerializeField] private string currentRemoteName; - [SerializeField] private Vector2 detailsScroll; + [SerializeField] private bool hasItemsToCommit; [SerializeField] private bool hasRemote; - [SerializeField] private List history = new List(); - [SerializeField] private CacheUpdateEvent lastCurrentRemoteChangedEvent; - [SerializeField] private CacheUpdateEvent lastLogChangedEvent; - [SerializeField] private CacheUpdateEvent lastTrackingStatusChangedEvent; - [SerializeField] private Vector2 scroll; - [SerializeField] private string selectionID; - [SerializeField] private int statusAhead; - [SerializeField] private int statusBehind; + [SerializeField] private string currentRemoteName; - public override void InitializeView(IView parent) - { - base.InitializeView(parent); + [SerializeField] private Vector2 historyScroll; + [SerializeField] private HistoryControl historyControl; - selectionIndex = newSelectionIndex = -1; + [SerializeField] private List logEntries = new List(); - changesetTree.InitializeView(this); - changesetTree.Readonly = true; - } + [SerializeField] private int statusAhead; + [SerializeField] private int statusBehind; + + [SerializeField] private CacheUpdateEvent lastCurrentRemoteChangedEvent; + [SerializeField] private CacheUpdateEvent lastLogChangedEvent; + [SerializeField] private CacheUpdateEvent lastTrackingStatusChangedEvent; public override void OnEnable() { @@ -160,136 +390,31 @@ public void OnEmbeddedGUI() } GUILayout.EndHorizontal(); - // When history scroll actually changes, store time value of topmost visible entry. This is the value we use to reposition scroll on log update - not the pixel value. - if (history.Any()) - { - listID = GUIUtility.GetControlID(FocusType.Keyboard); - - // Only update time scroll - var lastScroll = scroll; - scroll = GUILayout.BeginScrollView(scroll); - if (lastScroll != scroll && !currentLogHasUpdate) - { - scrollTime = history[historyStartIndex].Time; - scrollOffset = scroll.y - historyStartIndex * EntryHeight; - useScrollTime = true; - } - // Handle only the selected range of history items - adding spacing for the rest - var start = Mathf.Max(0, historyStartIndex - HistoryExtraItemCount); - var stop = Mathf.Min(historyStopIndex + HistoryExtraItemCount, history.Count); - GUILayout.Space(start * EntryHeight); - for (var index = start; index < stop; ++index) - { - if (HistoryEntry(history[index], GetEntryState(index), selectionIndex == index)) - { - newSelectionIndex = index; - GUIUtility.keyboardControl = listID; - } - } - - GUILayout.Space((history.Count - stop) * EntryHeight); - - // Keyboard control - if (GUIUtility.keyboardControl == listID && Event.current.type == EventType.KeyDown) - { - var change = 0; - - if (Event.current.keyCode == KeyCode.DownArrow) - { - change = 1; - } - else if (Event.current.keyCode == KeyCode.UpArrow) - { - change = -1; - } - - if (change != 0) - { - newSelectionIndex = (selectionIndex + change) % history.Count; - if (newSelectionIndex < historyStartIndex || newSelectionIndex > historyStopIndex) - { - ScrollTo(newSelectionIndex, - (Position.height - Position.height * MaxChangelistHeightRatio - 30f - EntryHeight) * -.5f); - } - Event.current.Use(); - } - } - } - else + var rect = GUILayoutUtility.GetLastRect(); + historyScroll = GUILayout.BeginScrollView(historyScroll); { - GUILayout.BeginScrollView(scroll); + OnHistoryGUI(new Rect(0f, 0f, Position.width, Position.height - rect.height)); } - GUILayout.EndScrollView(); + } - // Selection info - if (selectionIndex >= 0 && history.Count > selectionIndex) - { - var selection = history[selectionIndex]; - - // Top bar for scrolling to selection or clearing it - GUILayout.BeginHorizontal(EditorStyles.toolbar); - { - if (GUILayout.Button(CommitDetailsTitle, Styles.HistoryToolbarButtonStyle)) - { - ScrollTo(selectionIndex); - } - if (GUILayout.Button(ClearSelectionButton, Styles.HistoryToolbarButtonStyle, GUILayout.ExpandWidth(false))) - { - newSelectionIndex = -2; - } - } - GUILayout.EndHorizontal(); - - // Log entry details - including changeset tree (if any changes are found) - if (changesetTree.Entries.Any()) - { - detailsScroll = GUILayout.BeginScrollView(detailsScroll, GUILayout.Height(250)); - { - HistoryDetailsEntry(selection); - - GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); - GUILayout.Label("Files changed", EditorStyles.boldLabel); - GUILayout.Space(-5); - - GUILayout.BeginHorizontal(Styles.HistoryFileTreeBoxStyle); - { - changesetTree.OnGUI(); - } - GUILayout.EndHorizontal(); - - GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); - } - GUILayout.EndScrollView(); - } - else - { - detailsScroll = GUILayout.BeginScrollView(detailsScroll, GUILayout.Height(246)); - HistoryDetailsEntry(selection); - GUILayout.EndScrollView(); - } - } - - // Handle culling and selection changes at the end of the last GUI frame - if (Event.current.type == EventType.Repaint) + private void OnHistoryGUI(Rect rect) + { + var initialRect = rect; + if (historyControl != null) { - CullHistory(); - currentLogHasUpdate = false; + var renderResult = historyControl.Render(initialRect, rect, historyScroll, + entry => { }, + entry => { }, + entry => { }); - if (newSelectionIndex >= 0 || newSelectionIndex == -2) - { - selectionIndex = newSelectionIndex == -2 ? -1 : newSelectionIndex; - newSelectionIndex = -1; - detailsScroll = Vector2.zero; - - if (selectionIndex >= 0) - { - changesetTree.UpdateEntries(history[selectionIndex].Changes); - } + rect = renderResult.Rect; + if (renderResult.RequiresRepaint) Redraw(); - } } + + GUILayout.Space(rect.y - initialRect.y); } private void RepositoryTrackingOnStatusChanged(CacheUpdateEvent cacheUpdateEvent) @@ -378,186 +503,20 @@ private void MaybeUpdateData() { currentLogHasUpdate = false; - history = Repository.CurrentLog; - - if (history.Any()) - { - // Make sure that scroll as much as possible focuses the same time period in the new entry list - if (useScrollTime) - { - var closestIndex = -1; - double closestDifference = Mathf.Infinity; - for (var index = 0; index < history.Count; ++index) - { - var diff = Math.Abs((history[index].Time - scrollTime).TotalSeconds); - if (diff < closestDifference) - { - closestDifference = diff; - closestIndex = index; - } - } - - ScrollTo(closestIndex, scrollOffset); - } - - CullHistory(); - } + logEntries = Repository.CurrentLog; - // Restore selection index or clear it - newSelectionIndex = -1; - if (!string.IsNullOrEmpty(selectionID)) - { - selectionIndex = Enumerable.Range(1, history.Count + 1) - .FirstOrDefault( - index => history[index - 1].CommitID.Equals(selectionID)) - 1; - - if (selectionIndex < 0) - { - selectionID = string.Empty; - } - } + BuildHistoryControl(); } } - private void ScrollTo(int index, float offset = 0f) - { - scroll.Set(scroll.x, EntryHeight * index + offset); - } - - private LogEntryState GetEntryState(int index) + private void BuildHistoryControl() { - return index < statusAhead ? LogEntryState.Local : LogEntryState.Normal; - } - - /// - /// Recalculate the range of history items to handle - based on what is visible, plus a bit of padding for fast scrolling - /// - private void CullHistory() - { - historyStartIndex = (int)Mathf.Clamp(scroll.y / EntryHeight, 0, history.Count); - historyStopIndex = - (int) - Mathf.Clamp( - historyStartIndex + - (Position.height - 2f * Mathf.Min(changesetTree.Height, Position.height * MaxChangelistHeightRatio)) / - EntryHeight + 1, 0, history.Count); - } - - private void RevertCommit() - { - var selection = history[selectionIndex]; - - var dialogTitle = "Revert commit"; - var dialogBody = string.Format(@"Are you sure you want to revert the following commit:""{0}""", selection.Summary); - - if (EditorUtility.DisplayDialog(dialogTitle, dialogBody, "Revert", "Cancel")) - { - Repository - .Revert(selection.CommitID) - .FinallyInUI((success, e) => { - if (!success) - { - EditorUtility.DisplayDialog(dialogTitle, - "Error reverting commit: " + e.Message, Localization.Cancel); - } - }) - .Start(); - } - } - - private bool HistoryEntry(GitLogEntry entry, LogEntryState state, bool selected) - { - var entryRect = GUILayoutUtility.GetRect(Styles.HistoryEntryHeight, Styles.HistoryEntryHeight); - - if (Event.current.type == EventType.Repaint) - { - var keyboardFocus = GUIUtility.keyboardControl == listID; - - var summaryRect = new Rect(entryRect.x, entryRect.y + (Styles.BaseSpacing / 2), entryRect.width, Styles.HistorySummaryHeight + Styles.BaseSpacing); - var timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight - (Styles.BaseSpacing / 2), entryRect.width, Styles.HistoryDetailsHeight); - - var contentOffset = new Vector2(Styles.BaseSpacing * 2, 0); - - Styles.Label.Draw(entryRect, "", false, false, selected, keyboardFocus); - - Styles.Label.contentOffset = contentOffset; - Styles.HistoryEntryDetailsStyle.contentOffset = contentOffset; - - Styles.Label.Draw(summaryRect, entry.Summary, false, false, selected, keyboardFocus); - Styles.HistoryEntryDetailsStyle.Draw(timestampRect, entry.PrettyTimeString + " " + entry.AuthorName, false, false, selected, keyboardFocus); - - if (!string.IsNullOrEmpty(entry.MergeA)) - { - const float MergeIndicatorWidth = 10.28f; - const float MergeIndicatorHeight = 12f; - var mergeIndicatorRect = new Rect(entryRect.x + 7, summaryRect.y, MergeIndicatorWidth, MergeIndicatorHeight); - - GUI.DrawTexture(mergeIndicatorRect, Styles.MergeIcon); - - DrawTimelineRectAroundIconRect(entryRect, mergeIndicatorRect); - - summaryRect.Set(mergeIndicatorRect.xMax, summaryRect.y, summaryRect.width - MergeIndicatorWidth, summaryRect.height); - } - - if (state == LogEntryState.Local && string.IsNullOrEmpty(entry.MergeA)) - { - const float LocalIndicatorSize = 6f; - var localIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), summaryRect.y + 5, LocalIndicatorSize, LocalIndicatorSize); - - DrawTimelineRectAroundIconRect(entryRect, localIndicatorRect); - - GUI.DrawTexture(localIndicatorRect, Styles.LocalCommitIcon); - - summaryRect.Set(localIndicatorRect.xMax, summaryRect.y, summaryRect.width - LocalIndicatorSize, summaryRect.height); - } - - if (state == LogEntryState.Normal && string.IsNullOrEmpty(entry.MergeA)) - { - const float NormalIndicatorWidth = 6f; - const float NormalIndicatorHeight = 6f; - - Rect normalIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), - summaryRect.y + 5, - NormalIndicatorWidth, - NormalIndicatorHeight); - - DrawTimelineRectAroundIconRect(entryRect, normalIndicatorRect); - - GUI.DrawTexture(normalIndicatorRect, Styles.DotIcon); - } - } - else if (Event.current.type == EventType.ContextClick && entryRect.Contains(Event.current.mousePosition)) + if (historyControl == null) { - GenericMenu menu = new GenericMenu(); - menu.AddItem(new GUIContent("Revert"), false, RevertCommit); - menu.ShowAsContext(); - - Event.current.Use(); - } - else if (Event.current.type == EventType.MouseDown && entryRect.Contains(Event.current.mousePosition)) - { - Event.current.Use(); - return true; + historyControl = new HistoryControl(); } - return false; - } - - private void HistoryDetailsEntry(GitLogEntry entry) - { - GUILayout.BeginVertical(Styles.HeaderBoxStyle); - GUILayout.Label(entry.Summary, Styles.HistoryDetailsTitleStyle, GUILayout.Width(Position.width)); - - GUILayout.Space(-5); - - GUILayout.BeginHorizontal(); - GUILayout.Label(entry.PrettyTimeString, Styles.HistoryDetailsMetaInfoStyle); - GUILayout.Label(entry.AuthorName, Styles.HistoryDetailsMetaInfoStyle); - GUILayout.FlexibleSpace(); - GUILayout.EndHorizontal(); - - GUILayout.Space(3); - GUILayout.EndVertical(); + historyControl.Load(statusAhead, logEntries); } private void Pull() @@ -632,51 +591,9 @@ private void Fetch() .Start(); } - private void DrawTimelineRectAroundIconRect(Rect parentRect, Rect iconRect) - { - Color timelineBarColor = new Color(0.51F, 0.51F, 0.51F, 0.2F); - - // Draw them lines - // - // First I need to figure out how large to make the top one: - // I'll subtract the entryRect.y from the mergeIndicatorRect.y to - // get the difference in length. then subtract a little more for padding - float topTimelineRectHeight = iconRect.y - parentRect.y - 2; - // Now let's create the rect - Rect topTimelineRect = new Rect( - parentRect.x + Styles.BaseSpacing, - parentRect.y, - 2, - topTimelineRectHeight); - - // And draw it - EditorGUI.DrawRect(topTimelineRect, timelineBarColor); - - // Let's do the same for the bottom - float bottomTimelineRectHeight = parentRect.yMax - iconRect.yMax - 2; - Rect bottomTimelineRect = new Rect( - parentRect.x + Styles.BaseSpacing, - parentRect.yMax - bottomTimelineRectHeight, - 2, - bottomTimelineRectHeight); - EditorGUI.DrawRect(bottomTimelineRect, timelineBarColor); - } - public override bool IsBusy { get { return false; } } - - private float EntryHeight - { - get { return Styles.HistoryEntryHeight + Styles.HistoryEntryPadding; } - } - - private enum LogEntryState - { - Normal, - Local, - Remote - } } } From 80627b92aac8e778658ee114044c661267113239 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Mon, 11 Dec 2017 20:27:01 -0500 Subject: [PATCH 2/6] Completing HistoryControl --- .../Editor/GitHub.Unity/UI/HistoryView.cs | 132 +++++++++--------- 1 file changed, 67 insertions(+), 65 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 253e0582f..633d95773 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -32,28 +32,32 @@ public HistoryControlRenderResult Render(Rect containingRect, Rect rect, Vector2 controlId = GUIUtility.GetControlID(FocusType.Keyboard); - var treeHasFocus = GUIUtility.keyboardControl == controlId; if (Event.current.type != EventType.Repaint) { if (rightClickNextRender != null) { rightClickNextRender.Invoke(rightClickNextRenderEntry); rightClickNextRender = null; - //TODO: Default GitLogEntry - rightClickNextRenderEntry = new GitLogEntry(); + rightClickNextRenderEntry = GitLogEntry.Default; } } + var startDisplay = scroll.y; + var endDisplay = scroll.y + containingRect.height; + rect = new Rect(rect.x, rect.y, rect.width, 0); for (var index = 0; index < entries.Count; index++) { var entry = entries[index]; - var isLocalCommit = index < statusAhead; var entryRect = new Rect(rect.x, rect.y, rect.width, Styles.HistoryEntryHeight); - RenderEntry(entryRect, entry, isLocalCommit, index == selectedIndex); + var shouldRenderEntry = !(entryRect.y > endDisplay || entryRect.yMax < startDisplay); + if (shouldRenderEntry && Event.current.type == EventType.Repaint) + { + RenderEntry(entryRect, entry, index); + } var entryRequiresRepaint = HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); requiresRepaint = requiresRepaint || entryRequiresRepaint; @@ -67,6 +71,64 @@ public HistoryControlRenderResult Render(Rect containingRect, Rect rect, Vector2 }; } + private void RenderEntry(Rect entryRect, GitLogEntry entry, int index) + { + var isLocalCommit = index < statusAhead; + var isSelected = index == selectedIndex; + var summaryRect = new Rect(entryRect.x, entryRect.y + Styles.BaseSpacing / 2, entryRect.width, Styles.HistorySummaryHeight + Styles.BaseSpacing); + var timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight - Styles.BaseSpacing / 2, entryRect.width, Styles.HistoryDetailsHeight); + + var hasKeyboardFocus = GUIUtility.keyboardControl == controlId; + + Styles.Label.Draw(entryRect, GUIContent.none, false, false, isSelected, hasKeyboardFocus); + Styles.HistoryEntrySummaryStyle.Draw(summaryRect, entry.Summary, false, false, isSelected, hasKeyboardFocus); + + var historyEntryDetail = string.Format(HistoryEntryDetailFormat, entry.PrettyTimeString, entry.AuthorName); + Styles.HistoryEntryDetailsStyle.Draw(timestampRect, historyEntryDetail, false, false, isSelected, hasKeyboardFocus); + + if (!string.IsNullOrEmpty(entry.MergeA)) + { + const float MergeIndicatorWidth = 10.28f; + const float MergeIndicatorHeight = 12f; + var mergeIndicatorRect = new Rect(entryRect.x + 7, summaryRect.y, MergeIndicatorWidth, MergeIndicatorHeight); + + GUI.DrawTexture(mergeIndicatorRect, Styles.MergeIcon); + + DrawTimelineRectAroundIconRect(entryRect, mergeIndicatorRect); + + summaryRect.Set(mergeIndicatorRect.xMax, summaryRect.y, summaryRect.width - MergeIndicatorWidth, + summaryRect.height); + } + else + { + if (isLocalCommit) + { + const float LocalIndicatorSize = 6f; + var localIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), summaryRect.y + 5, LocalIndicatorSize, + LocalIndicatorSize); + + DrawTimelineRectAroundIconRect(entryRect, localIndicatorRect); + + GUI.DrawTexture(localIndicatorRect, Styles.LocalCommitIcon); + + summaryRect.Set(localIndicatorRect.xMax, summaryRect.y, summaryRect.width - LocalIndicatorSize, + summaryRect.height); + } + else + { + const float NormalIndicatorWidth = 6f; + const float NormalIndicatorHeight = 6f; + + var normalIndicatorRect = new Rect(entryRect.x + (Styles.BaseSpacing - 2), summaryRect.y + 5, + NormalIndicatorWidth, NormalIndicatorHeight); + + DrawTimelineRectAroundIconRect(entryRect, normalIndicatorRect); + + GUI.DrawTexture(normalIndicatorRect, Styles.DotIcon); + } + } + } + private bool HandleInput(Rect rect, GitLogEntry entry, int index, Action singleClick = null, Action doubleClick = null, Action rightClick = null) { @@ -119,64 +181,6 @@ private bool HandleInput(Rect rect, GitLogEntry entry, int index, Action Date: Tue, 12 Dec 2017 09:45:41 -0500 Subject: [PATCH 3/6] Managing scroll and maintaining selected index in HistoryControl --- .../Editor/GitHub.Unity/UI/HistoryView.cs | 202 +++++++++++++----- 1 file changed, 144 insertions(+), 58 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 633d95773..cc01dcdec 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -6,75 +6,86 @@ namespace GitHub.Unity { - struct HistoryControlRenderResult - { - public Rect Rect; - public bool RequiresRepaint; - } - [Serializable] class HistoryControl { private const string HistoryEntryDetailFormat = "{0} {1}"; + [SerializeField] private Vector2 scroll; [SerializeField] private List entries = new List(); [SerializeField] private int statusAhead; - [SerializeField] private int selectedIndex; + [SerializeField] private int selectedIndex = -1; [NonSerialized] private Action rightClickNextRender; [NonSerialized] private GitLogEntry rightClickNextRenderEntry; [NonSerialized] private int controlId; - public HistoryControlRenderResult Render(Rect containingRect, Rect rect, Vector2 scroll, Action singleClick = null, + public int SelectedIndex + { + get { return selectedIndex; } + set { selectedIndex = value; } + } + + public GitLogEntry SelectedGitLogEntry + { + get { return SelectedIndex < 0 ? GitLogEntry.Default : entries[SelectedIndex]; } + } + + public bool Render(Rect containingRect, Action singleClick = null, Action doubleClick = null, Action rightClick = null) { var requiresRepaint = false; + var rect = Rect.zero; - controlId = GUIUtility.GetControlID(FocusType.Keyboard); - - if (Event.current.type != EventType.Repaint) + scroll = GUILayout.BeginScrollView(scroll); { - if (rightClickNextRender != null) + controlId = GUIUtility.GetControlID(FocusType.Keyboard); + + if (Event.current.type != EventType.Repaint) { - rightClickNextRender.Invoke(rightClickNextRenderEntry); - rightClickNextRender = null; - rightClickNextRenderEntry = GitLogEntry.Default; + if (rightClickNextRender != null) + { + rightClickNextRender.Invoke(rightClickNextRenderEntry); + rightClickNextRender = null; + rightClickNextRenderEntry = GitLogEntry.Default; + } } - } - var startDisplay = scroll.y; - var endDisplay = scroll.y + containingRect.height; + var startDisplay = scroll.y; + var endDisplay = scroll.y + containingRect.height; - rect = new Rect(rect.x, rect.y, rect.width, 0); + rect = new Rect(containingRect.x, containingRect.y, containingRect.width, 0); - for (var index = 0; index < entries.Count; index++) - { - var entry = entries[index]; + for (var index = 0; index < entries.Count; index++) + { + var entry = entries[index]; - var entryRect = new Rect(rect.x, rect.y, rect.width, Styles.HistoryEntryHeight); + var entryRect = new Rect(rect.x, rect.y, rect.width, Styles.HistoryEntryHeight); - var shouldRenderEntry = !(entryRect.y > endDisplay || entryRect.yMax < startDisplay); - if (shouldRenderEntry && Event.current.type == EventType.Repaint) - { - RenderEntry(entryRect, entry, index); - } + var shouldRenderEntry = !(entryRect.y > endDisplay || entryRect.yMax < startDisplay); + if (shouldRenderEntry && Event.current.type == EventType.Repaint) + { + RenderEntry(entryRect, entry, index); + } - var entryRequiresRepaint = HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); - requiresRepaint = requiresRepaint || entryRequiresRepaint; + var entryRequiresRepaint = HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); + requiresRepaint = requiresRepaint || entryRequiresRepaint; - rect.y += Styles.HistoryEntryHeight; + rect.y += Styles.HistoryEntryHeight; + } } - return new HistoryControlRenderResult { - Rect = rect, - RequiresRepaint = requiresRepaint - }; + GUILayout.Space(rect.y - containingRect.y); + + GUILayout.EndScrollView(); + + return requiresRepaint; } private void RenderEntry(Rect entryRect, GitLogEntry entry, int index) { var isLocalCommit = index < statusAhead; - var isSelected = index == selectedIndex; + var isSelected = index == SelectedIndex; var summaryRect = new Rect(entryRect.x, entryRect.y + Styles.BaseSpacing / 2, entryRect.width, Styles.HistorySummaryHeight + Styles.BaseSpacing); var timestampRect = new Rect(entryRect.x, entryRect.yMax - Styles.HistoryDetailsHeight - Styles.BaseSpacing / 2, entryRect.width, Styles.HistoryDetailsHeight); @@ -139,7 +150,7 @@ private bool HandleInput(Rect rect, GitLogEntry entry, int index, Action loadEntries) { + var selectedCommitId = SelectedGitLogEntry.CommitID; + statusAhead = loadAhead; entries = loadEntries; + + var changed = false; + for (var index = 0; index < entries.Count; index++) + { + var gitLogEntry = entries[index]; + if (gitLogEntry.CommitID.Equals(selectedCommitId)) + { + selectedIndex = index; + changed = true; + break; + } + } + + if (!changed) + { + selectedIndex = -1; + } } private int SelectNext(int index) @@ -223,7 +253,7 @@ private int SelectNext(int index) if (index < entries.Count) { - selectedIndex = index; + SelectedIndex = index; } else { @@ -239,15 +269,20 @@ private int SelectPrevious(int index) if (index >= 0) { - selectedIndex = index; + SelectedIndex = index; } else { - selectedIndex = -1; + SelectedIndex = -1; } return index; } + + public void ScrollTo(int index) + { + scroll.Set(scroll.x, Styles.HistoryEntryHeight * index); + } } enum LogEntryState @@ -286,8 +321,10 @@ class HistoryView : Subview [SerializeField] private bool hasRemote; [SerializeField] private string currentRemoteName; - [SerializeField] private Vector2 historyScroll; [SerializeField] private HistoryControl historyControl; + [SerializeField] private GitLogEntry selectedEntry = GitLogEntry.Default; + + [SerializeField] private Vector2 detailsScroll; [SerializeField] private List logEntries = new List(); @@ -393,30 +430,74 @@ public void OnEmbeddedGUI() GUILayout.EndHorizontal(); var rect = GUILayoutUtility.GetLastRect(); - historyScroll = GUILayout.BeginScrollView(historyScroll); - { - OnHistoryGUI(new Rect(0f, 0f, Position.width, Position.height - rect.height)); - } - GUILayout.EndScrollView(); - } - - private void OnHistoryGUI(Rect rect) - { - var initialRect = rect; if (historyControl != null) { - var renderResult = historyControl.Render(initialRect, rect, historyScroll, - entry => { }, + var historyControlRect = new Rect(0f, 0f, Position.width, Position.height - rect.height); + + var requiresRepaint = historyControl.Render(historyControlRect, + entry => { + selectedEntry = entry; + }, entry => { }, entry => { }); - rect = renderResult.Rect; - - if (renderResult.RequiresRepaint) + if (requiresRepaint) Redraw(); } - GUILayout.Space(rect.y - initialRect.y); + if (!selectedEntry.Equals(GitLogEntry.Default)) + { + // Top bar for scrolling to selection or clearing it + GUILayout.BeginHorizontal(EditorStyles.toolbar); + { + if (GUILayout.Button(CommitDetailsTitle, Styles.HistoryToolbarButtonStyle)) + { + historyControl.ScrollTo(historyControl.SelectedIndex); + } + if (GUILayout.Button(ClearSelectionButton, Styles.HistoryToolbarButtonStyle, GUILayout.ExpandWidth(false))) + { + selectedEntry = GitLogEntry.Default; + historyControl.SelectedIndex = -1; + } + } + GUILayout.EndHorizontal(); + + // Log entry details - including changeset tree (if any changes are found) + detailsScroll = GUILayout.BeginScrollView(detailsScroll, GUILayout.Height(250)); + { + HistoryDetailsEntry(selectedEntry); + + GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); + GUILayout.Label("Files changed", EditorStyles.boldLabel); + GUILayout.Space(-5); + + GUILayout.BeginHorizontal(Styles.HistoryFileTreeBoxStyle); + { + //changesetTree.OnGUI(); + } + GUILayout.EndHorizontal(); + + GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); + } + GUILayout.EndScrollView(); + } + } + + private void HistoryDetailsEntry(GitLogEntry entry) + { + GUILayout.BeginVertical(Styles.HeaderBoxStyle); + GUILayout.Label(entry.Summary, Styles.HistoryDetailsTitleStyle, GUILayout.Width(Position.width)); + + GUILayout.Space(-5); + + GUILayout.BeginHorizontal(); + GUILayout.Label(entry.PrettyTimeString, Styles.HistoryDetailsMetaInfoStyle); + GUILayout.Label(entry.AuthorName, Styles.HistoryDetailsMetaInfoStyle); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + GUILayout.Space(3); + GUILayout.EndVertical(); } private void RepositoryTrackingOnStatusChanged(CacheUpdateEvent cacheUpdateEvent) @@ -519,6 +600,11 @@ private void BuildHistoryControl() } historyControl.Load(statusAhead, logEntries); + if (!selectedEntry.Equals(GitLogEntry.Default) + && selectedEntry.CommitID != historyControl.SelectedGitLogEntry.CommitID) + { + selectedEntry = GitLogEntry.Default; + } } private void Pull() From 4f7ceac3e5fbc23629ef1c5758c44abac37e5b3c Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Tue, 12 Dec 2017 10:11:19 -0500 Subject: [PATCH 4/6] Maintaining scroll state when history items get added --- .../Editor/GitHub.Unity/UI/HistoryView.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index cc01dcdec..45fe2908e 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -225,26 +225,45 @@ private void DrawTimelineRectAroundIconRect(Rect parentRect, Rect iconRect) public void Load(int loadAhead, List loadEntries) { var selectedCommitId = SelectedGitLogEntry.CommitID; + var scrollValue = scroll.y; + + var previousCount = entries.Count; + + var scrollIndex = (int)(scrollValue / Styles.HistoryEntryHeight); statusAhead = loadAhead; entries = loadEntries; - var changed = false; + var selectionPresent = false; for (var index = 0; index < entries.Count; index++) { var gitLogEntry = entries[index]; if (gitLogEntry.CommitID.Equals(selectedCommitId)) { selectedIndex = index; - changed = true; + selectionPresent = true; break; } } - if (!changed) + if (!selectionPresent) { selectedIndex = -1; } + + if (scrollIndex > entries.Count) + { + ScrollTo(0); + } + else + { + var scrollOffset = scrollValue % Styles.HistoryEntryHeight; + + var scrollIndexFromBottom = previousCount - scrollIndex; + var newScrollIndex = entries.Count - scrollIndexFromBottom; + + ScrollTo(newScrollIndex, scrollOffset); + } } private int SelectNext(int index) @@ -279,9 +298,9 @@ private int SelectPrevious(int index) return index; } - public void ScrollTo(int index) + public void ScrollTo(int index, float offset = 0f) { - scroll.Set(scroll.x, Styles.HistoryEntryHeight * index); + scroll.Set(scroll.x, Styles.HistoryEntryHeight * index + offset); } } From 7fde8d118f481de4979febaef870a80881525ffd Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Tue, 12 Dec 2017 20:08:18 -0500 Subject: [PATCH 5/6] Removing unused enum --- .../Assets/Editor/GitHub.Unity/UI/HistoryView.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 45fe2908e..4c5a2adab 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -304,12 +304,6 @@ public void ScrollTo(int index, float offset = 0f) } } - enum LogEntryState - { - Normal, - Local - } - [Serializable] class HistoryView : Subview { From 6090cd8a5faaed13b7c05f8f503e5b950c208851 Mon Sep 17 00:00:00 2001 From: Stanley Goldman Date: Tue, 12 Dec 2017 20:13:48 -0500 Subject: [PATCH 6/6] Minor cleanup --- .../Assets/Editor/GitHub.Unity/UI/HistoryView.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs index 4c5a2adab..c18c9805f 100644 --- a/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs +++ b/src/UnityExtension/Assets/Editor/GitHub.Unity/UI/HistoryView.cs @@ -35,8 +35,6 @@ public bool Render(Rect containingRect, Action singleClick = null, Action doubleClick = null, Action rightClick = null) { var requiresRepaint = false; - var rect = Rect.zero; - scroll = GUILayout.BeginScrollView(scroll); { controlId = GUIUtility.GetControlID(FocusType.Keyboard); @@ -54,7 +52,7 @@ public bool Render(Rect containingRect, Action singleClick = null, var startDisplay = scroll.y; var endDisplay = scroll.y + containingRect.height; - rect = new Rect(containingRect.x, containingRect.y, containingRect.width, 0); + var rect = new Rect(containingRect.x, containingRect.y, containingRect.width, 0); for (var index = 0; index < entries.Count; index++) { @@ -68,15 +66,15 @@ public bool Render(Rect containingRect, Action singleClick = null, RenderEntry(entryRect, entry, index); } - var entryRequiresRepaint = HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); + var entryRequiresRepaint = + HandleInput(entryRect, entry, index, singleClick, doubleClick, rightClick); requiresRepaint = requiresRepaint || entryRequiresRepaint; rect.y += Styles.HistoryEntryHeight; } - } - - GUILayout.Space(rect.y - containingRect.y); + GUILayout.Space(rect.y - containingRect.y); + } GUILayout.EndScrollView(); return requiresRepaint;