2311 lines
93 KiB
C#
2311 lines
93 KiB
C#
using UnityEngine;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.Profiling;
|
|
|
|
namespace UnityEditor.Performance.ProfileAnalyzer
|
|
{
|
|
[Serializable]
|
|
internal class FrameTimeGraphGlobalSettings
|
|
{
|
|
public bool showThreads = false;
|
|
public bool showSelectedMarker = true;
|
|
public bool showFrameLines = true;
|
|
public bool showFrameLineText = true;
|
|
public bool showOrderedByFrameDuration = false;
|
|
}
|
|
|
|
internal class FrameTimeGraph
|
|
{
|
|
static FrameTimeGraphGlobalSettings m_GlobalSettings = new FrameTimeGraphGlobalSettings();
|
|
|
|
static public void SetGlobalSettings(FrameTimeGraphGlobalSettings globalSettings)
|
|
{
|
|
m_GlobalSettings = globalSettings;
|
|
}
|
|
|
|
public struct Data
|
|
{
|
|
public readonly float ms;
|
|
public readonly int frameOffset;
|
|
|
|
public Data(float _ms, int _index)
|
|
{
|
|
ms = _ms;
|
|
frameOffset = _index;
|
|
}
|
|
};
|
|
|
|
public delegate void SetRange(List<int> selected, int clickCount, FrameTimeGraph.State inputStatus);
|
|
public delegate void SetActive(bool active);
|
|
|
|
public enum State
|
|
{
|
|
None,
|
|
Dragging,
|
|
DragComplete
|
|
};
|
|
|
|
enum DragDirection
|
|
{
|
|
Start,
|
|
Forward,
|
|
Backward,
|
|
None
|
|
};
|
|
|
|
enum AxisMode
|
|
{
|
|
One60HzFrame,
|
|
Two60HzFrames,
|
|
Four60HzFrames,
|
|
Max,
|
|
Custom
|
|
};
|
|
|
|
Draw2D m_2D;
|
|
int m_DragBeginFirstOffset;
|
|
int m_DragBeginLastOffset;
|
|
|
|
bool m_Dragging;
|
|
int m_DragFirstOffset;
|
|
int m_DragLastOffset;
|
|
bool m_Moving;
|
|
int m_MoveHandleOffset;
|
|
bool m_SingleControlAction;
|
|
|
|
int m_ClickCount;
|
|
double m_LastClickTime;
|
|
bool m_MouseReleased;
|
|
|
|
bool m_Zoomed;
|
|
int m_ZoomStartOffset;
|
|
int m_ZoomEndOffset;
|
|
|
|
Color m_ColorBarBackground;
|
|
Color m_ColorBarBackgroundSelected;
|
|
Color m_ColorBar;
|
|
Color m_ColorBarOutOfRange;
|
|
Color m_ColorBarSelected;
|
|
Color m_ColorBarThreads;
|
|
Color m_ColorBarThreadsOutOfRange;
|
|
Color m_ColorBarThreadsSelected;
|
|
Color m_ColorBarMarker;
|
|
Color m_ColorBarMarkerOutOfRange;
|
|
Color m_ColorBarMarkerSelected;
|
|
Color m_ColorGridLine;
|
|
|
|
FrameTimeGraph m_PairedWithFrameTimeGraph;
|
|
|
|
internal static class Styles
|
|
{
|
|
public static readonly GUIContent menuItemClearSelection = new GUIContent("Clear Selection");
|
|
public static readonly GUIContent menuItemSelectAll = new GUIContent("Select All");
|
|
public static readonly GUIContent menuItemInvertSelection = new GUIContent("Invert Selection");
|
|
public static readonly GUIContent menuItemZoomSelection = new GUIContent("Zoom Selection");
|
|
public static readonly GUIContent menuItemZoomAll = new GUIContent("Zoom All");
|
|
public static readonly GUIContent menuItemSelectMin = new GUIContent("Select Shortest Frame");
|
|
public static readonly GUIContent menuItemSelectMax = new GUIContent("Select Longest Frame");
|
|
public static readonly GUIContent menuItemSelectMedian = new GUIContent("Select Median Frame");
|
|
|
|
public static readonly GUIContent menuItemSelectPrevious = new GUIContent("Move selection left _LEFT");
|
|
public static readonly GUIContent menuItemSelectNext = new GUIContent("Move selection right _RIGHT");
|
|
|
|
public static readonly GUIContent menuItemSelectGrow = new GUIContent("Grow selection _=");
|
|
public static readonly GUIContent menuItemSelectShrink = new GUIContent("Shrink selection _-");
|
|
public static readonly GUIContent menuItemSelectGrowLeft = new GUIContent("Grow selection left _<");
|
|
public static readonly GUIContent menuItemSelectGrowRight = new GUIContent("Grow selection right _>");
|
|
public static readonly GUIContent menuItemSelectShrinkLeft = new GUIContent("Shrink selection left _&<");
|
|
public static readonly GUIContent menuItemSelectShrinkRight = new GUIContent("Shrink selection right _&>");
|
|
|
|
public static readonly GUIContent menuItemSelectGrowFast = new GUIContent("Grow selection (fast) _#=");
|
|
public static readonly GUIContent menuItemSelectShrinkFast = new GUIContent("Shrink selection (fast) _#-");
|
|
public static readonly GUIContent menuItemSelectGrowLeftFast = new GUIContent("Grow selection left (fast) _#<");
|
|
public static readonly GUIContent menuItemSelectGrowRightFast = new GUIContent("Grow selection right (fast) _#>");
|
|
public static readonly GUIContent menuItemSelectShrinkLeftFast = new GUIContent("Shrink selection left (fast) _#&<");
|
|
public static readonly GUIContent menuItemSelectShrinkRightFast = new GUIContent("Shrink selection right (fast) _#&>");
|
|
|
|
public static readonly GUIContent menuItemShowSelectedMarker = new GUIContent("Show Selected Marker");
|
|
public static readonly GUIContent menuItemShowThreads = new GUIContent("Show Filtered Threads");
|
|
// public static readonly GUIContent menuItemDetailedMode = new GUIContent("Detailed mode");
|
|
public static readonly GUIContent menuItemShowFrameLines = new GUIContent("Show Frame Lines");
|
|
public static readonly GUIContent menuItemShowFrameLineText = new GUIContent("Show Frame Line Text");
|
|
public static readonly GUIContent menuItemShowOrderedByFrameDuration = new GUIContent("Order by Frame Duration");
|
|
}
|
|
|
|
const int kXAxisWidth = 80;
|
|
const int kYAxisDetailThreshold = 40;
|
|
const int kOverrunHeight = 3;
|
|
|
|
static AxisMode s_YAxisMode;
|
|
static float m_YAxisMs;
|
|
|
|
bool m_IsOrderedByFrameDuration;
|
|
|
|
List<Data> m_Values = new List<Data> {};
|
|
List<int> m_LastSelectedFrameOffsets = new List<int> {};
|
|
int[] m_FrameOffsetToDataOffsetMapping = new int[] {};
|
|
SetRange m_SetRange;
|
|
SetActive m_SetActive;
|
|
|
|
List<int> m_CurrentSelection = new List<int>();
|
|
int m_CurrentSelectionFirstDataOffset;
|
|
int m_CurrentSelectionLastDataOffset;
|
|
|
|
int m_GraphId;
|
|
int m_ControlID;
|
|
static int s_LastSelectedGraphId = -1;
|
|
static int s_CurrentSelectedGraphId = -1;
|
|
|
|
bool m_Enabled;
|
|
|
|
struct BarData
|
|
{
|
|
public float x;
|
|
public float y;
|
|
public float w;
|
|
public float h;
|
|
|
|
public int startDataOffset;
|
|
public int endDataOffset;
|
|
public float yMin;
|
|
public float yMax;
|
|
|
|
public BarData(float _x, float _y, float _w, float _h, int _startDataOffset, int _endDataOffset, float _yMin, float _yMax)
|
|
{
|
|
x = _x;
|
|
y = _y;
|
|
w = _w;
|
|
h = _h;
|
|
startDataOffset = _startDataOffset;
|
|
endDataOffset = _endDataOffset;
|
|
yMin = _yMin;
|
|
yMax = _yMax;
|
|
}
|
|
}
|
|
|
|
List<BarData> m_Bars = new List<BarData>();
|
|
|
|
DisplayUnits m_Units;
|
|
Rect m_LastRect;
|
|
int m_MaxFrames;
|
|
|
|
string DisplayUnits()
|
|
{
|
|
return m_Units.Postfix();
|
|
}
|
|
|
|
string ToDisplayUnits(float ms, bool showUnits = false, int limitToNDigits = 5)
|
|
{
|
|
return m_Units.ToString(ms, showUnits, limitToNDigits);
|
|
}
|
|
|
|
public void SetUnits(Units units)
|
|
{
|
|
m_Units = new DisplayUnits(units);
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
m_Zoomed = false;
|
|
m_Dragging = false;
|
|
ClearDragSelection();
|
|
|
|
m_Moving = false;
|
|
m_ClickCount = 0;
|
|
m_MouseReleased = false;
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
Reset();
|
|
|
|
m_PairedWithFrameTimeGraph = null;
|
|
m_YAxisMs = 100f;
|
|
s_YAxisMode = AxisMode.Max;
|
|
m_IsOrderedByFrameDuration = false;
|
|
|
|
m_Enabled = true;
|
|
|
|
m_LastRect = new Rect(0, 0, 0, 0);
|
|
m_MaxFrames = -1;
|
|
}
|
|
|
|
public void MakeGraphActive(bool activate)
|
|
{
|
|
if (activate)
|
|
{
|
|
if (s_CurrentSelectedGraphId != m_GraphId)
|
|
{
|
|
s_LastSelectedGraphId = m_GraphId;
|
|
s_CurrentSelectedGraphId = m_GraphId;
|
|
|
|
// Make sure we are not still selecting another graph
|
|
GUIUtility.hotControl = 0;
|
|
|
|
m_SetActive(true);
|
|
}
|
|
|
|
if (GUI.GetNameOfFocusedControl() != "FrameTimeGraph")
|
|
{
|
|
// Take focus away from any other control
|
|
// Doesn't really matter what the name is here
|
|
GUI.FocusControl("FrameTimeGraph");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (s_CurrentSelectedGraphId == m_GraphId)
|
|
{
|
|
s_CurrentSelectedGraphId = -1;
|
|
|
|
// Remember this was the active control
|
|
// Before this point one of the inner labels would have been active
|
|
// GUIUtility.hotControl = m_ControlID;
|
|
|
|
m_SetActive(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsGraphActive()
|
|
{
|
|
if (s_CurrentSelectedGraphId == m_GraphId)
|
|
return true;
|
|
|
|
if (s_LastSelectedGraphId == m_GraphId && GUIUtility.hotControl == m_ControlID)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
public FrameTimeGraph(int graphID, Draw2D draw2D, Units units, Color background, Color backgroundSelected, Color barColor, Color barSelected, Color barMarker, Color barMarkerSelected, Color barThreads, Color barThreadsSelected, Color colorGridlines)
|
|
{
|
|
m_GraphId = graphID;
|
|
m_ControlID = 0;
|
|
|
|
m_2D = draw2D;
|
|
SetUnits(units);
|
|
Init();
|
|
|
|
float ratio = 0.75f;
|
|
m_ColorBarBackground = background;
|
|
m_ColorBarBackgroundSelected = backgroundSelected;
|
|
|
|
m_ColorBar = barColor;
|
|
m_ColorBarOutOfRange = new Color(barColor.r * ratio, barColor.g * ratio, barColor.b * ratio);
|
|
m_ColorBarSelected = barSelected;
|
|
|
|
m_ColorBarMarker = barMarker;
|
|
m_ColorBarMarkerOutOfRange = new Color(barMarker.r * ratio, barMarker.g * ratio, barMarker.b * ratio);
|
|
m_ColorBarMarkerSelected = barMarkerSelected;
|
|
|
|
m_ColorBarThreads = barThreads;
|
|
m_ColorBarThreadsOutOfRange = new Color(barThreads.r * ratio, barThreads.g * ratio, barThreads.b * ratio);
|
|
m_ColorBarThreadsSelected = barThreadsSelected;
|
|
|
|
m_ColorGridLine = colorGridlines;
|
|
}
|
|
|
|
int ClampToRange(int value, int min, int max)
|
|
{
|
|
if (value < min)
|
|
value = min;
|
|
if (value > max)
|
|
value = max;
|
|
|
|
return value;
|
|
}
|
|
|
|
int GetDataOffsetForXUnclamped(int xPosition, int width, int totalDataSize)
|
|
{
|
|
int visibleDataSize;
|
|
if (m_Zoomed)
|
|
visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
|
|
else
|
|
visibleDataSize = totalDataSize;
|
|
|
|
int dataOffset = (int)(xPosition * visibleDataSize / width);
|
|
|
|
if (m_Zoomed)
|
|
dataOffset += m_ZoomStartOffset;
|
|
|
|
return dataOffset;
|
|
}
|
|
|
|
int GetDataOffsetForX(int xPosition, int width, int totalDataSize)
|
|
{
|
|
//xPosition = ClampToRange(xPosition, 0, width-1);
|
|
int dataOffset = GetDataOffsetForXUnclamped(xPosition, width, totalDataSize);
|
|
return ClampToRange(dataOffset, 0, totalDataSize - 1);
|
|
}
|
|
|
|
int GetXForDataOffset(int dataOffset, int width, int totalDataSize)
|
|
{
|
|
//frameOffset = ClampToRange(frameOffset, 0, frames-1);
|
|
|
|
int visibleDataSize;
|
|
if (m_Zoomed)
|
|
{
|
|
dataOffset = ClampToRange(dataOffset, m_ZoomStartOffset, m_ZoomEndOffset + 1);
|
|
dataOffset -= m_ZoomStartOffset;
|
|
visibleDataSize = (m_ZoomEndOffset - m_ZoomStartOffset) + 1;
|
|
}
|
|
else
|
|
visibleDataSize = totalDataSize;
|
|
|
|
int x = (int)(dataOffset * width / visibleDataSize);
|
|
|
|
x = ClampToRange(x, 0, width - 1);
|
|
return x;
|
|
}
|
|
|
|
void SetDragMovement(int startOffset, int endOffset, int currentSelectionFirstDataOffset, int currentSelectionLastDataOffset)
|
|
{
|
|
// Maintain length but clamp to range
|
|
int frames = m_Values.Count;
|
|
|
|
int currentSelectionRange = currentSelectionLastDataOffset - currentSelectionFirstDataOffset;
|
|
endOffset = startOffset + currentSelectionRange;
|
|
|
|
startOffset = ClampToRange(startOffset, 0, frames - (currentSelectionRange + 1));
|
|
endOffset = ClampToRange(endOffset, 0, frames - 1);
|
|
|
|
SetDragSelection(startOffset, endOffset);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
|
|
{
|
|
m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
|
|
}
|
|
}
|
|
|
|
void SetDragSelection(int startOffset, int endOffset, DragDirection dragDirection)
|
|
{
|
|
// No need to clamp these as input is clamped.
|
|
switch (dragDirection)
|
|
{
|
|
case DragDirection.Forward:
|
|
SetDragSelection(m_DragBeginFirstOffset, endOffset);
|
|
break;
|
|
|
|
case DragDirection.Backward:
|
|
SetDragSelection(startOffset, m_DragBeginLastOffset);
|
|
break;
|
|
|
|
case DragDirection.Start:
|
|
SetDragSelection(startOffset, endOffset);
|
|
|
|
// Record first selected bar range
|
|
m_DragBeginFirstOffset = m_DragFirstOffset;
|
|
m_DragBeginLastOffset = m_DragLastOffset;
|
|
break;
|
|
}
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
|
|
{
|
|
m_PairedWithFrameTimeGraph.SetDragSelection(m_DragFirstOffset, m_DragLastOffset);
|
|
}
|
|
}
|
|
|
|
public void SetDragSelection(int startOffset, int endOffset)
|
|
{
|
|
m_DragFirstOffset = startOffset;
|
|
m_DragLastOffset = endOffset;
|
|
}
|
|
|
|
public void ClearDragSelection()
|
|
{
|
|
m_DragFirstOffset = -1;
|
|
m_DragLastOffset = -1;
|
|
}
|
|
|
|
public bool HasDragRegion()
|
|
{
|
|
return (m_DragFirstOffset != -1);
|
|
}
|
|
|
|
public void GetSelectedRange(List<int> frameOffsets, out int firstDataOffset, out int lastDataOffset, out int firstFrameOffset, out int lastFrameOffset)
|
|
{
|
|
int frames = m_Values != null ? m_Values.Count : 0;
|
|
|
|
firstDataOffset = 0;
|
|
lastDataOffset = frames - 1;
|
|
firstFrameOffset = 0;
|
|
lastFrameOffset = frames - 1;
|
|
|
|
if (m_FrameOffsetToDataOffsetMapping.Length > 0)
|
|
{
|
|
// By default data is ordered by index so first/last will be the selected visible range
|
|
|
|
if (frameOffsets.Count >= 1)
|
|
{
|
|
firstFrameOffset = frameOffsets[0];
|
|
lastFrameOffset = firstFrameOffset;
|
|
|
|
firstDataOffset = GetDataOffset(firstFrameOffset);
|
|
lastDataOffset = firstDataOffset;
|
|
}
|
|
if (frameOffsets.Count >= 2)
|
|
{
|
|
lastFrameOffset = frameOffsets[frameOffsets.Count - 1];
|
|
|
|
lastDataOffset = GetDataOffset(lastFrameOffset);
|
|
}
|
|
|
|
if (m_GlobalSettings.showOrderedByFrameDuration)
|
|
{
|
|
// Need to find the selected items with lowest and highest ms values
|
|
if (frameOffsets.Count > 0)
|
|
{
|
|
int dataOffset = GetDataOffset(firstFrameOffset);
|
|
|
|
firstDataOffset = dataOffset;
|
|
lastDataOffset = dataOffset;
|
|
float firstDataMS = m_Values[dataOffset].ms;
|
|
float lastDataMS = m_Values[dataOffset].ms;
|
|
|
|
foreach (int frameOffset in frameOffsets)
|
|
{
|
|
dataOffset = GetDataOffset(frameOffset);
|
|
|
|
float ms = m_Values[dataOffset].ms;
|
|
if (ms <= firstDataMS && dataOffset < firstDataOffset)
|
|
{
|
|
firstDataMS = ms;
|
|
firstDataOffset = dataOffset;
|
|
}
|
|
if (ms >= lastDataMS && dataOffset > lastDataOffset)
|
|
{
|
|
lastDataMS = ms;
|
|
lastDataOffset = dataOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsMultiSelectControlHeld()
|
|
{
|
|
#if UNITY_EDITOR_OSX
|
|
return Event.current.command;
|
|
#else
|
|
return Event.current.control;
|
|
#endif
|
|
}
|
|
|
|
public State ProcessInput()
|
|
{
|
|
if (!IsEnabled())
|
|
return State.None;
|
|
|
|
if (m_Values == null)
|
|
return State.None;
|
|
|
|
if (m_LastRect.width == 0 || m_MaxFrames < 0)
|
|
return State.None;
|
|
|
|
Rect rect = m_LastRect;
|
|
int maxFrames = m_MaxFrames;
|
|
|
|
int dataLength = m_Values.Count;
|
|
if (dataLength <= 0)
|
|
return State.None;
|
|
|
|
if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
|
|
{
|
|
// Reorder if necessary
|
|
SetData(m_Values);
|
|
}
|
|
|
|
int currentSelectionFirstDataOffset;
|
|
int currentSelectionLastDataOffset;
|
|
int currentSelectionFirstFrameOffset;
|
|
int currentSelectionLastFrameOffset;
|
|
|
|
GetSelectedRange(m_LastSelectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
|
|
|
|
m_CurrentSelection.Clear();
|
|
m_CurrentSelection.AddRange(m_LastSelectedFrameOffsets);
|
|
m_CurrentSelectionFirstDataOffset = currentSelectionFirstDataOffset;
|
|
m_CurrentSelectionLastDataOffset = currentSelectionLastDataOffset;
|
|
|
|
if (Event.current.isKey && Event.current.type == EventType.KeyDown && !m_Dragging && !m_MouseReleased)
|
|
{
|
|
if (IsGraphActive())
|
|
{
|
|
int step = Event.current.shift ? 10 : 1;
|
|
var eventUsed = false;
|
|
switch (Event.current.keyCode)
|
|
{
|
|
case KeyCode.LeftArrow:
|
|
SelectPrevious(step);
|
|
eventUsed = true;
|
|
break;
|
|
case KeyCode.RightArrow:
|
|
SelectNext(step);
|
|
eventUsed = true;
|
|
break;
|
|
case KeyCode.Less:
|
|
case KeyCode.Comma:
|
|
if (Event.current.alt)
|
|
SelectShrinkLeft(step);
|
|
else
|
|
SelectGrowLeft(step);
|
|
eventUsed = true;
|
|
break;
|
|
case KeyCode.Greater:
|
|
case KeyCode.Period:
|
|
if (Event.current.alt)
|
|
SelectShrinkRight(step);
|
|
else
|
|
SelectGrowRight(step);
|
|
eventUsed = true;
|
|
break;
|
|
case KeyCode.Plus:
|
|
case KeyCode.Equals:
|
|
case KeyCode.KeypadPlus:
|
|
if (Event.current.alt)
|
|
SelectShrink(step);
|
|
else
|
|
SelectGrow(step);
|
|
eventUsed = true;
|
|
break;
|
|
case KeyCode.Underscore:
|
|
case KeyCode.Minus:
|
|
case KeyCode.KeypadMinus:
|
|
if (Event.current.alt)
|
|
SelectGrow(step);
|
|
else
|
|
SelectShrink(step);
|
|
eventUsed = true;
|
|
break;
|
|
}
|
|
|
|
if (eventUsed)
|
|
Event.current.Use();
|
|
|
|
}
|
|
}
|
|
|
|
float doubleClickTimeout = 0.25f;
|
|
if (m_MouseReleased)
|
|
{
|
|
if ((EditorApplication.timeSinceStartup - m_LastClickTime) > doubleClickTimeout)
|
|
{
|
|
// By this point we will know if its a single or double click
|
|
bool append = IsMultiSelectControlHeld();
|
|
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.DragComplete, append);
|
|
|
|
ClearDragSelection();
|
|
if (m_PairedWithFrameTimeGraph != null && !m_SingleControlAction)
|
|
m_PairedWithFrameTimeGraph.ClearDragSelection();
|
|
|
|
m_MouseReleased = false;
|
|
}
|
|
}
|
|
|
|
int width = (int)rect.width;
|
|
int height = (int)rect.height;
|
|
float xStart = rect.xMin;
|
|
if (height > kYAxisDetailThreshold)
|
|
{
|
|
float h = GUI.skin.label.lineHeight;
|
|
xStart += kXAxisWidth;
|
|
width -= kXAxisWidth;
|
|
}
|
|
if (maxFrames > 0)
|
|
{
|
|
if (!m_Zoomed)
|
|
width = width * dataLength / maxFrames;
|
|
}
|
|
|
|
// Process input
|
|
Event e = Event.current;
|
|
if (e.isMouse)
|
|
{
|
|
if (m_Dragging)
|
|
{
|
|
if (e.type == EventType.MouseUp)
|
|
{
|
|
m_Dragging = false;
|
|
m_Moving = false;
|
|
|
|
// Delay the action as we are checking for double click
|
|
m_MouseReleased = true;
|
|
return State.Dragging;
|
|
}
|
|
}
|
|
|
|
int x = (int)(e.mousePosition.x - xStart);
|
|
|
|
int dataOffset = GetDataOffsetForXUnclamped(x, width, dataLength);
|
|
if (m_Moving)
|
|
dataOffset -= m_MoveHandleOffset;
|
|
dataOffset = ClampToRange(dataOffset, 0, dataLength - 1);
|
|
int frameOffsetBeforeNext = Math.Max(dataOffset, GetDataOffsetForX(x + 1, width, dataLength) - 1);
|
|
|
|
if (m_Dragging)
|
|
{
|
|
if (e.button == 0)
|
|
{
|
|
// Still dragging (doesn't have to be within the y bounds)
|
|
if (m_Moving)
|
|
{
|
|
// Forward drag from start point
|
|
SetDragMovement(dataOffset, frameOffsetBeforeNext, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
|
|
}
|
|
else
|
|
{
|
|
DragDirection dragDirection = (dataOffset < m_DragBeginFirstOffset) ? DragDirection.Backward : DragDirection.Forward;
|
|
SetDragSelection(dataOffset, frameOffsetBeforeNext, dragDirection);
|
|
}
|
|
|
|
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
|
|
return State.Dragging;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (e.mousePosition.x >= rect.x && e.mousePosition.x <= rect.xMax &&
|
|
e.mousePosition.y >= rect.y && e.mousePosition.y <= rect.yMax)
|
|
{
|
|
if (e.mousePosition.x >= xStart && e.mousePosition.x <= (xStart + width) &&
|
|
e.mousePosition.y >= rect.y && e.mousePosition.y < rect.yMax)
|
|
{
|
|
MakeGraphActive(true);
|
|
|
|
if (e.type == EventType.MouseDown && e.button == 0)
|
|
{
|
|
// Drag start (must be within the bounds of the control)
|
|
// Might be single or double click
|
|
m_LastClickTime = EditorApplication.timeSinceStartup;
|
|
m_ClickCount = e.clickCount;
|
|
|
|
m_Dragging = true;
|
|
m_Moving = false;
|
|
|
|
if (currentSelectionFirstDataOffset != 0 || currentSelectionLastDataOffset != dataLength - 1)
|
|
{
|
|
// Selection is valid
|
|
if (e.shift && dataOffset >= currentSelectionFirstDataOffset && frameOffsetBeforeNext <= currentSelectionLastDataOffset)
|
|
{
|
|
// Moving if shift held and we are inside the current selection range
|
|
m_Moving = true;
|
|
}
|
|
}
|
|
|
|
if (m_PairedWithFrameTimeGraph != null)
|
|
m_SingleControlAction = e.alt; // Record if we are acting only on this control rather than the paired one too
|
|
else
|
|
m_SingleControlAction = true;
|
|
|
|
if (m_Moving)
|
|
{
|
|
m_MoveHandleOffset = dataOffset - currentSelectionFirstDataOffset;
|
|
|
|
SetDragMovement(currentSelectionFirstDataOffset, currentSelectionLastDataOffset, currentSelectionFirstDataOffset, currentSelectionLastDataOffset);
|
|
}
|
|
else
|
|
{
|
|
//SetDragSelection(dataOffset, frameOffsetBeforeNext, DragDirection.Start);
|
|
|
|
// Select just 1 frame
|
|
SetDragSelection(dataOffset, dataOffset, DragDirection.Start);
|
|
}
|
|
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
|
|
return State.Dragging;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Left this graph area
|
|
MakeGraphActive(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_MouseReleased)
|
|
{
|
|
// Not finished drag fully yet
|
|
CallSetRange(m_DragFirstOffset, m_DragLastOffset, m_ClickCount, m_SingleControlAction, FrameTimeGraph.State.Dragging);
|
|
return State.Dragging;
|
|
}
|
|
|
|
return State.None;
|
|
}
|
|
|
|
public float GetDataRange()
|
|
{
|
|
if (m_Values == null)
|
|
return 0f;
|
|
|
|
int frames = m_Values.Count;
|
|
|
|
float min = 0f;
|
|
float max = 0f;
|
|
for (int frameOffset = 0; frameOffset < frames; frameOffset++)
|
|
{
|
|
float ms = m_Values[frameOffset].ms;
|
|
if (ms > max)
|
|
max = ms;
|
|
}
|
|
float hRange = max - min;
|
|
|
|
return hRange;
|
|
}
|
|
|
|
public void PairWith(FrameTimeGraph otherFrameTimeGraph)
|
|
{
|
|
if (m_PairedWithFrameTimeGraph != null)
|
|
{
|
|
// Clear existing pairing
|
|
m_PairedWithFrameTimeGraph.m_PairedWithFrameTimeGraph = null;
|
|
}
|
|
|
|
m_PairedWithFrameTimeGraph = otherFrameTimeGraph;
|
|
if (otherFrameTimeGraph != null)
|
|
otherFrameTimeGraph.m_PairedWithFrameTimeGraph = this;
|
|
}
|
|
|
|
public FrameTimeGraph GetPairedWith()
|
|
{
|
|
return m_PairedWithFrameTimeGraph;
|
|
}
|
|
|
|
public float GetYAxisRange(float yMax)
|
|
{
|
|
switch (s_YAxisMode)
|
|
{
|
|
case AxisMode.One60HzFrame:
|
|
return 1000f / 60f;
|
|
case AxisMode.Two60HzFrames:
|
|
return 2000f / 60f;
|
|
case AxisMode.Four60HzFrames:
|
|
return 4000f / 60f;
|
|
case AxisMode.Max:
|
|
return yMax;
|
|
case AxisMode.Custom:
|
|
return m_YAxisMs;
|
|
}
|
|
|
|
return yMax;
|
|
}
|
|
|
|
public void SetData(List<Data> values)
|
|
{
|
|
if (values == null)
|
|
return;
|
|
|
|
m_Values = values;
|
|
|
|
if (m_GlobalSettings.showOrderedByFrameDuration)
|
|
m_Values.Sort((a, b) => { return a.ms.CompareTo(b.ms); });
|
|
else
|
|
m_Values.Sort((a, b) => { return a.frameOffset.CompareTo(b.frameOffset); });
|
|
|
|
m_FrameOffsetToDataOffsetMapping = new int[m_Values.Count];
|
|
for (int dataOffset = 0; dataOffset < m_Values.Count; dataOffset++)
|
|
m_FrameOffsetToDataOffsetMapping[m_Values[dataOffset].frameOffset] = dataOffset;
|
|
|
|
m_CurrentSelection.Clear();
|
|
for (int frameIndex = 0; frameIndex < m_Values.Count; frameIndex++)
|
|
{
|
|
m_CurrentSelection.Add(frameIndex);
|
|
}
|
|
m_CurrentSelectionFirstDataOffset = 0;
|
|
m_CurrentSelectionLastDataOffset = m_Values.Count - 1;
|
|
|
|
m_IsOrderedByFrameDuration = m_GlobalSettings.showOrderedByFrameDuration;
|
|
}
|
|
|
|
int GetDataOffset(int frameOffset)
|
|
{
|
|
if (frameOffset < 0 || frameOffset >= m_FrameOffsetToDataOffsetMapping.Length)
|
|
{
|
|
Debug.Log(string.Format("{0} out of range of frame offset to data offset mapping {1}", frameOffset, m_FrameOffsetToDataOffsetMapping.Length));
|
|
return 0;
|
|
}
|
|
|
|
return m_FrameOffsetToDataOffsetMapping[frameOffset];
|
|
}
|
|
|
|
public void ClearData()
|
|
{
|
|
m_Values = null;
|
|
}
|
|
|
|
public bool HasData()
|
|
{
|
|
if (m_Values == null)
|
|
return false;
|
|
if (m_Values.Count == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void SetActiveCallback(SetActive setActive)
|
|
{
|
|
m_SetActive = setActive;
|
|
}
|
|
|
|
public void SetRangeCallback(SetRange setRange)
|
|
{
|
|
m_SetRange = setRange;
|
|
}
|
|
|
|
void CallSetRange(int startDataOffset, int endDataOffset, int clickCount, bool singleControlAction, FrameTimeGraph.State inputStatus, bool append = false, bool effectPaired = true)
|
|
{
|
|
if (m_SetRange == null)
|
|
return;
|
|
|
|
if (startDataOffset < 0 && endDataOffset < 0)
|
|
{
|
|
// Clear
|
|
m_SetRange(new List<int>(), clickCount, inputStatus);
|
|
return;
|
|
}
|
|
|
|
startDataOffset = Math.Max(0, startDataOffset);
|
|
endDataOffset = Math.Min(endDataOffset, m_Values.Count - 1);
|
|
|
|
List<int> selected = new List<int>();
|
|
if (append && m_LastSelectedFrameOffsets.Count != m_Values.Count)
|
|
{
|
|
foreach (int frameOffset in m_LastSelectedFrameOffsets)
|
|
{
|
|
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
|
|
if (dataOffset >= 0 && dataOffset < m_Values.Count)
|
|
{
|
|
selected.Add(frameOffset);
|
|
}
|
|
}
|
|
}
|
|
for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
|
|
{
|
|
if (dataOffset >= 0 && dataOffset < m_Values.Count)
|
|
{
|
|
int frameOffset = m_Values[dataOffset].frameOffset;
|
|
if (append == false || !selected.Contains(frameOffset))
|
|
{
|
|
selected.Add(frameOffset);
|
|
}
|
|
}
|
|
}
|
|
// Sort selection in frame index order so start is lowest and end is highest
|
|
selected.Sort();
|
|
|
|
if (selected.Count == 0)
|
|
return;
|
|
|
|
m_SetRange(selected, clickCount, inputStatus);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && m_PairedWithFrameTimeGraph.m_Values.Count > 1 && effectPaired && !singleControlAction)
|
|
{
|
|
// Update selection on the other frame time graph
|
|
int mainMaxFrame = m_Values.Count - 1;
|
|
int otherMaxFrame = m_PairedWithFrameTimeGraph.m_Values.Count - 1;
|
|
|
|
int startOffset = startDataOffset;
|
|
int endOffset = endDataOffset;
|
|
|
|
if (startOffset > otherMaxFrame)
|
|
{
|
|
if (append)
|
|
{
|
|
// Nothing more to do
|
|
return;
|
|
}
|
|
|
|
// Select all, if the main selection is outsize the range of the other
|
|
startOffset = 0;
|
|
endOffset = otherMaxFrame;
|
|
}
|
|
else
|
|
{
|
|
if (startOffset == 0 && endOffset == mainMaxFrame)
|
|
{
|
|
// If clearing main selection then clear the other section fully too
|
|
endOffset = otherMaxFrame;
|
|
}
|
|
|
|
startOffset = ClampToRange(startOffset, 0, otherMaxFrame);
|
|
endOffset = ClampToRange(endOffset, 0, otherMaxFrame);
|
|
}
|
|
|
|
m_PairedWithFrameTimeGraph.CallSetRange(startOffset, endOffset, clickCount, singleControlAction, inputStatus, append, false);
|
|
}
|
|
}
|
|
|
|
bool HasNoSelection()
|
|
{
|
|
if (m_Values == null)
|
|
return false;
|
|
|
|
int frames = m_Values.Count;
|
|
|
|
return ((m_CurrentSelectionFirstDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames) &&
|
|
(m_CurrentSelectionLastDataOffset < 0 || m_CurrentSelectionLastDataOffset >= frames));
|
|
}
|
|
|
|
bool HasSelectedAll()
|
|
{
|
|
if (m_Values == null)
|
|
return false;
|
|
|
|
int frames = m_Values.Count;
|
|
|
|
return (m_CurrentSelectionFirstDataOffset == 0 && m_CurrentSelectionLastDataOffset == (frames - 1));
|
|
}
|
|
|
|
bool HasSubsetSelected()
|
|
{
|
|
if (m_Values == null)
|
|
return false;
|
|
|
|
return !(HasSelectedAll() || HasNoSelection());
|
|
}
|
|
|
|
void RegenerateBars(float x, float y, float width, float height, float yRange)
|
|
{
|
|
int frames = m_Values.Count;
|
|
if (frames <= 0)
|
|
return;
|
|
|
|
m_Bars.Clear();
|
|
|
|
int nextDataOffset = GetDataOffsetForX(0, (int)width, frames);
|
|
for (int barX = 0; barX < width; barX++)
|
|
{
|
|
int startDataOffset = nextDataOffset;
|
|
nextDataOffset = GetDataOffsetForX(barX + 1, (int)width, frames);
|
|
int endDataOffset = Math.Max(startDataOffset, nextDataOffset - 1);
|
|
|
|
float min = m_Values[startDataOffset].ms;
|
|
float max = min;
|
|
for (int dataOffset = startDataOffset + 1; dataOffset <= endDataOffset; dataOffset++)
|
|
{
|
|
float ms = m_Values[dataOffset].ms;
|
|
if (ms < min)
|
|
min = ms;
|
|
if (ms > max)
|
|
max = ms;
|
|
}
|
|
float maxClamped = Math.Min(max, yRange);
|
|
float h = height * maxClamped / yRange;
|
|
|
|
m_Bars.Add(new BarData(x + barX, y, 1, h, startDataOffset, endDataOffset, min, max));
|
|
}
|
|
}
|
|
|
|
public void SetEnabled(bool enabled)
|
|
{
|
|
m_Enabled = enabled;
|
|
}
|
|
|
|
public bool IsEnabled()
|
|
{
|
|
return m_Enabled;
|
|
}
|
|
|
|
float GetTotalSelectionTime(List<int> selectedFrameOffsets)
|
|
{
|
|
float totalMs = 0;
|
|
for (int i = 0; i < selectedFrameOffsets.Count; i++)
|
|
{
|
|
int frameOffset = selectedFrameOffsets[i];
|
|
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
|
|
totalMs += m_Values[dataOffset].ms;
|
|
}
|
|
|
|
return totalMs;
|
|
}
|
|
|
|
float GetTotalSelectionTime(int firstOffset, int lastOffset)
|
|
{
|
|
float totalMs = 0;
|
|
for (int frameOffset = firstOffset; frameOffset <= lastOffset; frameOffset++)
|
|
{
|
|
if (frameOffset < m_FrameOffsetToDataOffsetMapping.Length)
|
|
{
|
|
int dataOffset = m_FrameOffsetToDataOffsetMapping[frameOffset];
|
|
totalMs += m_Values[dataOffset].ms;
|
|
}
|
|
}
|
|
|
|
return totalMs;
|
|
}
|
|
|
|
void ShowFrameLines(float x, float y, float yRange, float width, float height)
|
|
{
|
|
float msSegment = 1000f / 60f;
|
|
int lines = (int)(yRange / msSegment);
|
|
int step = 1;
|
|
for (int line = 1; line <= lines; line += step, step *= 2)
|
|
{
|
|
float ms = line * msSegment;
|
|
float h = height * ms / yRange;
|
|
m_2D.DrawLine(x, y + h, x + width - 1, y + h, m_ColorGridLine);
|
|
}
|
|
}
|
|
|
|
bool InSelectedRegion(int startDataOffset, int endDataOffset, int selectedFirstOffset, int selectedLastOffset, Dictionary<int, int> frameOffsetToSelectionIndex, bool subsetSelected)
|
|
{
|
|
bool inSelectionRegion = false;
|
|
|
|
bool showCurrentSelection = false;
|
|
if (HasDragRegion())
|
|
{
|
|
if (endDataOffset >= selectedFirstOffset && startDataOffset <= selectedLastOffset)
|
|
{
|
|
inSelectionRegion = true;
|
|
}
|
|
if (IsMultiSelectControlHeld() && m_LastSelectedFrameOffsets.Count != m_Values.Count)
|
|
{
|
|
// Show current selection too
|
|
showCurrentSelection = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
showCurrentSelection = true;
|
|
}
|
|
|
|
if (showCurrentSelection)
|
|
{
|
|
//if (subsetSelected)
|
|
{
|
|
for (int dataOffset = startDataOffset; dataOffset <= endDataOffset; dataOffset++)
|
|
{
|
|
int frameOffset = m_Values[dataOffset].frameOffset;
|
|
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
|
|
{
|
|
inSelectionRegion = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return inSelectionRegion;
|
|
}
|
|
|
|
public void Draw(Rect rect, ProfileAnalysis analysis, List<int> selectedFrameOffsets, float yMax, int offsetToDisplayMapping, int offsetToIndexMapping, string selectedMarkerName, int maxFrames = 0, ProfileAnalysis fullAnalysis = null)
|
|
{
|
|
Profiler.BeginSample("FrameTimeGraph.Draw");
|
|
|
|
// Must be outside repaint to make sure next controls work correctly (specifically marker name filter)
|
|
int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
|
|
|
|
if (Event.current.type == EventType.Repaint)
|
|
{
|
|
m_LastRect = rect;
|
|
|
|
// Control id would change during non repaint phase if tooltips displayed if we update this outside the repaint
|
|
m_ControlID = controlID;
|
|
}
|
|
|
|
m_MaxFrames = maxFrames;
|
|
|
|
if (m_Values == null)
|
|
return;
|
|
|
|
m_LastSelectedFrameOffsets = selectedFrameOffsets;
|
|
|
|
int totalDataSize = m_Values.Count;
|
|
if (totalDataSize <= 0)
|
|
return;
|
|
|
|
if (m_IsOrderedByFrameDuration != m_GlobalSettings.showOrderedByFrameDuration)
|
|
{
|
|
// Reorder if necessary
|
|
SetData(m_Values);
|
|
}
|
|
|
|
// Get start and end selection span
|
|
int currentSelectionFirstDataOffset;
|
|
int currentSelectionLastDataOffset;
|
|
int currentSelectionFirstFrameOffset;
|
|
int currentSelectionLastFrameOffset;
|
|
|
|
GetSelectedRange(selectedFrameOffsets, out currentSelectionFirstDataOffset, out currentSelectionLastDataOffset, out currentSelectionFirstFrameOffset, out currentSelectionLastFrameOffset);
|
|
|
|
|
|
// Create mapping from offset to selection for faster selection detection
|
|
Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
|
|
for (int i = 0; i < selectedFrameOffsets.Count; i++)
|
|
{
|
|
int frameOffset = selectedFrameOffsets[i];
|
|
frameOffsetToSelectionIndex[frameOffset] = i;
|
|
}
|
|
|
|
Event current = Event.current;
|
|
|
|
int selectedFirstOffset;
|
|
int selectedLastOffset;
|
|
int selectedCount;
|
|
bool subsetSelected = false;
|
|
if (HasDragRegion())
|
|
{
|
|
selectedFirstOffset = m_DragFirstOffset;
|
|
selectedLastOffset = m_DragLastOffset;
|
|
|
|
if (selectedFirstOffset > m_Values.Count - 1)
|
|
{
|
|
// Selection off the end
|
|
selectedFirstOffset = m_Values.Count;
|
|
selectedLastOffset = m_Values.Count;
|
|
selectedCount = 0;
|
|
}
|
|
else
|
|
{
|
|
selectedFirstOffset = ClampToRange(selectedFirstOffset, 0, m_Values.Count - 1);
|
|
selectedLastOffset = ClampToRange(selectedLastOffset, 0, m_Values.Count - 1);
|
|
|
|
selectedCount = 1 + (selectedLastOffset - selectedFirstOffset);
|
|
subsetSelected = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selectedFirstOffset = currentSelectionFirstDataOffset;
|
|
selectedLastOffset = currentSelectionLastDataOffset;
|
|
selectedCount = selectedFrameOffsets.Count;
|
|
subsetSelected = (selectedCount > 0 && selectedCount != totalDataSize);
|
|
}
|
|
|
|
// Draw frames and selection
|
|
float width = rect.width;
|
|
float height = rect.height;
|
|
|
|
bool showAxis = false;
|
|
float xStart = 0f;
|
|
float yStart = 0f;
|
|
if (height > kYAxisDetailThreshold)
|
|
{
|
|
showAxis = true;
|
|
|
|
float h = GUI.skin.label.lineHeight;
|
|
xStart += kXAxisWidth;
|
|
width -= kXAxisWidth;
|
|
|
|
yStart += h;
|
|
height -= h;
|
|
}
|
|
if (maxFrames > 0)
|
|
{
|
|
if (!m_Zoomed)
|
|
width = width * totalDataSize / maxFrames;
|
|
}
|
|
|
|
// Start / End
|
|
int startOffset = m_Zoomed ? m_ZoomStartOffset : 0;
|
|
int endOffset = m_Zoomed ? m_ZoomEndOffset : totalDataSize - 1;
|
|
|
|
// Get try index values
|
|
int startIndex = offsetToDisplayMapping + startOffset;
|
|
int endIndex = offsetToDisplayMapping + endOffset;
|
|
int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
|
|
int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
|
|
|
|
string detailsString = "";
|
|
|
|
if (!showAxis)
|
|
{
|
|
string frameRangeString;
|
|
if (startIndex == endIndex)
|
|
frameRangeString = string.Format("Total Range {0}", startIndex);
|
|
else
|
|
frameRangeString = string.Format("Total Range {0} - {1} [{2}]", startIndex, endIndex, 1 + (endIndex - startIndex));
|
|
|
|
// Selection range
|
|
string selectedTooltip = "";
|
|
if (subsetSelected)
|
|
{
|
|
if (selectedFirstIndex == selectedLastIndex)
|
|
selectedTooltip = string.Format("\nSelected {0}\n", selectedFirstIndex);
|
|
else
|
|
selectedTooltip = string.Format("\nSelected {0} - {1} [{2}]", selectedFirstIndex, selectedLastIndex, selectedCount);
|
|
}
|
|
|
|
detailsString = string.Format("\n\n{0}{1}", frameRangeString, selectedTooltip);
|
|
}
|
|
|
|
float yRange = GetYAxisRange(yMax);
|
|
|
|
bool lastEnabled = GUI.enabled;
|
|
bool enabled = IsEnabled();
|
|
GUI.enabled = enabled;
|
|
|
|
if (m_2D.DrawStart(rect, Draw2D.Origin.BottomLeft))
|
|
{
|
|
float totalMs;
|
|
if (HasDragRegion())
|
|
{
|
|
totalMs = GetTotalSelectionTime(selectedFirstOffset, selectedLastOffset);
|
|
}
|
|
else
|
|
{
|
|
totalMs = GetTotalSelectionTime(selectedFrameOffsets);
|
|
}
|
|
|
|
string timeForSelectedFrames = ToDisplayUnits(totalMs, true, 0);
|
|
string timeForSelectedFramesClamped = ToDisplayUnits(totalMs, true, 1);
|
|
string selectionAreaString = string.Format("\n\nTotal time for {0} selected frames\n{1} ({2})", selectedCount, timeForSelectedFrames, timeForSelectedFramesClamped);
|
|
|
|
Color selectedControl = GUI.skin.settings.selectionColor;
|
|
|
|
if (IsGraphActive())
|
|
{
|
|
m_2D.DrawBox(xStart, yStart, width, height, selectedControl);
|
|
}
|
|
|
|
//xStart -= 1f;
|
|
yStart += 1f;
|
|
width -= 1f;
|
|
height -= 1f;
|
|
|
|
m_2D.DrawFilledBox(xStart, yStart, width, height, m_ColorBarBackground);
|
|
|
|
RegenerateBars(xStart, yStart, width, height, yRange);
|
|
|
|
foreach (BarData bar in m_Bars)
|
|
{
|
|
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
|
|
if (inSelectionRegion)
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, height, m_ColorBarBackgroundSelected);
|
|
}
|
|
}
|
|
|
|
if (m_GlobalSettings.showFrameLines)
|
|
{
|
|
ShowFrameLines(xStart, yStart, yRange, width, height);
|
|
}
|
|
|
|
ProfileAnalysis analysisData = analysis;
|
|
bool full = false;
|
|
if (fullAnalysis != null)
|
|
{
|
|
analysisData = fullAnalysis;
|
|
full = true;
|
|
}
|
|
|
|
MarkerData selectedMarker = (m_GlobalSettings.showSelectedMarker && analysisData != null) ? analysisData.GetMarkerByName(selectedMarkerName) : null;
|
|
foreach (BarData bar in m_Bars)
|
|
{
|
|
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
|
|
if (inSelectionRegion)
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBarSelected);
|
|
}
|
|
else
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, bar.h, m_ColorBar);
|
|
}
|
|
|
|
// Show where its been clamped
|
|
if (bar.yMax > yRange)
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y + height, 1, kOverrunHeight, m_ColorBarOutOfRange);
|
|
}
|
|
|
|
|
|
if (analysisData != null && (full || !m_Dragging))
|
|
{
|
|
// Analysis is just on the subset
|
|
if (m_GlobalSettings.showThreads)
|
|
{
|
|
Profiler.BeginSample("FrameTimeGraph.ShowThreads");
|
|
ShowThreads(height, yRange, bar, full,
|
|
analysisData.GetThreads(), subsetSelected, selectedFirstOffset, selectedLastOffset,
|
|
offsetToIndexMapping, frameOffsetToSelectionIndex);
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
if (m_GlobalSettings.showSelectedMarker)
|
|
{
|
|
// Analysis is just on the subset (unless we have full analysis data)
|
|
ShowSelectedMarker(height, yRange, bar, full, selectedMarker, subsetSelected, selectedFirstOffset, selectedLastOffset,
|
|
offsetToIndexMapping, frameOffsetToSelectionIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_2D.DrawEnd();
|
|
|
|
if (m_GlobalSettings.showFrameLines && m_GlobalSettings.showFrameLineText)
|
|
{
|
|
ShowFrameLinesText(rect, xStart, yStart, yRange, width, height, subsetSelected, selectedFirstOffset, selectedLastOffset);
|
|
}
|
|
|
|
foreach (BarData bar in m_Bars)
|
|
{
|
|
bool inSelectionRegion = InSelectedRegion(bar.startDataOffset, bar.endDataOffset, selectedFirstOffset, selectedLastOffset, frameOffsetToSelectionIndex, subsetSelected);
|
|
|
|
// Draw tooltip for bar (or 1 pixel segment of bar)
|
|
{
|
|
int barStartIndex = offsetToDisplayMapping + m_Values[bar.startDataOffset].frameOffset;
|
|
int barEndIndex = offsetToDisplayMapping + m_Values[bar.endDataOffset].frameOffset;
|
|
string tooltip;
|
|
if (barStartIndex == barEndIndex)
|
|
tooltip = string.Format("Frame {0}\n{1}{2}", barStartIndex, ToDisplayUnits(bar.yMax, true), detailsString);
|
|
else
|
|
tooltip = string.Format("Frame {0}-{1}\n{2} max\n{3} min{4}", barStartIndex, barEndIndex, ToDisplayUnits(bar.yMax, true), ToDisplayUnits(bar.yMin, true), detailsString);
|
|
|
|
if (inSelectionRegion)
|
|
tooltip += selectionAreaString;
|
|
GUI.Label(new Rect(rect.x + bar.x, rect.y + 5, bar.w, height), new GUIContent("", tooltip));
|
|
}
|
|
}
|
|
}
|
|
|
|
GUI.enabled = lastEnabled;
|
|
|
|
if (showAxis)
|
|
{
|
|
int zoomedSelectedFirstOffset = selectedFirstOffset;
|
|
int zoomedSelectedLastOffset = selectedLastOffset;
|
|
int zoomedSelectedCount = selectedCount;
|
|
if (m_Zoomed)
|
|
{
|
|
if (selectedFirstOffset > endOffset || selectedLastOffset < startOffset)
|
|
{
|
|
zoomedSelectedCount = 0;
|
|
}
|
|
else
|
|
{
|
|
// Clamp selection range to zoom range
|
|
zoomedSelectedFirstOffset = ClampToRange(selectedFirstOffset, startOffset, endOffset);
|
|
zoomedSelectedLastOffset = ClampToRange(selectedLastOffset, startOffset, endOffset);
|
|
if (HasDragRegion())
|
|
{
|
|
zoomedSelectedCount = 1 + (zoomedSelectedLastOffset - zoomedSelectedFirstOffset);
|
|
}
|
|
else
|
|
{
|
|
zoomedSelectedCount = 0;
|
|
foreach (var offset in selectedFrameOffsets)
|
|
{
|
|
if (offset >= startOffset && offset <= endOffset)
|
|
{
|
|
zoomedSelectedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ShowAxis(rect, xStart, width, startOffset, endOffset, zoomedSelectedFirstOffset, zoomedSelectedLastOffset, zoomedSelectedCount, selectedCount, yMax, totalDataSize, offsetToDisplayMapping);
|
|
}
|
|
|
|
GUI.enabled = enabled;
|
|
if (rect.Contains(current.mousePosition) && current.type == EventType.ContextClick)
|
|
{
|
|
var analytic = ProfileAnalyzerAnalytics.BeginAnalytic();
|
|
|
|
ShowContextMenu(subsetSelected, selectedCount);
|
|
|
|
current.Use();
|
|
|
|
ProfileAnalyzerAnalytics.SendUIVisibilityEvent(ProfileAnalyzerAnalytics.UIVisibility.FrameTimeContextMenu, analytic.GetDurationInSeconds(), true);
|
|
}
|
|
GUI.enabled = lastEnabled;
|
|
|
|
Profiler.EndSample();
|
|
}
|
|
|
|
void ShowThreads(float height, float yRange, BarData bar, bool full,
|
|
List<ThreadData> threads, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
|
|
int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
|
|
{
|
|
float max = float.MinValue;
|
|
bool selected = false;
|
|
for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
|
|
{
|
|
int frameOffset = m_Values[dataOffset].frameOffset;
|
|
if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
|
|
continue;
|
|
|
|
float threadMs = 0f;
|
|
foreach (var thread in threads)
|
|
{
|
|
int frameIndex = offsetToIndexMapping + frameOffset;
|
|
var frame = thread.GetFrame(frameIndex);
|
|
if (frame == null)
|
|
continue;
|
|
|
|
float ms = frame.Value.ms;
|
|
if (ms > threadMs)
|
|
threadMs = ms;
|
|
}
|
|
|
|
if (threadMs > max)
|
|
max = threadMs;
|
|
|
|
if (m_Dragging)
|
|
{
|
|
if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
|
|
selected = true;
|
|
}
|
|
else if (subsetSelected)
|
|
{
|
|
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
|
|
selected = true;
|
|
}
|
|
}
|
|
|
|
if (full || selected)
|
|
{
|
|
// Clamp to frame time (these values can be time summed over multiple threads)
|
|
if (max > bar.yMax)
|
|
max = bar.yMax;
|
|
|
|
float maxClamped = Math.Min(max, yRange);
|
|
float h = height * maxClamped / yRange;
|
|
|
|
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarThreadsSelected : m_ColorBarThreads);
|
|
|
|
// Show where its been clamped
|
|
if (max > yRange)
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarThreadsOutOfRange);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShowSelectedMarker(float height, float yRange, BarData bar, bool full,
|
|
MarkerData selectedMarker, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset,
|
|
int offsetToIndexMapping, Dictionary<int, int> frameOffsetToSelectionIndex)
|
|
{
|
|
float max = 0f;
|
|
bool selected = false;
|
|
if (selectedMarker != null)
|
|
{
|
|
for (int dataOffset = bar.startDataOffset; dataOffset <= bar.endDataOffset; dataOffset++)
|
|
{
|
|
int frameOffset = m_Values[dataOffset].frameOffset;
|
|
if (!full && !frameOffsetToSelectionIndex.ContainsKey(frameOffset))
|
|
continue;
|
|
|
|
float ms = selectedMarker.GetFrameMs(offsetToIndexMapping + frameOffset);
|
|
|
|
if (ms > max)
|
|
max = ms;
|
|
|
|
if (m_Dragging)
|
|
{
|
|
if (frameOffset >= selectedFirstOffset && frameOffset <= selectedLastOffset)
|
|
selected = true;
|
|
}
|
|
else if (subsetSelected)
|
|
{
|
|
if (frameOffsetToSelectionIndex.ContainsKey(frameOffset))
|
|
selected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (full || selected)
|
|
{
|
|
// Clamp to frame time (these values can be tiem summed over multiple threads)
|
|
if (max > bar.yMax)
|
|
max = bar.yMax;
|
|
|
|
float maxClamped = Math.Min(max, yRange);
|
|
float h = height * maxClamped / yRange;
|
|
|
|
m_2D.DrawFilledBox(bar.x, bar.y, bar.w, h, selected ? m_ColorBarMarkerSelected : m_ColorBarMarker);
|
|
|
|
if (max > 0f)
|
|
{
|
|
// we start the bar lower so that very small markers still show up.
|
|
m_2D.DrawFilledBox(bar.x, bar.y - kOverrunHeight, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
|
|
}
|
|
|
|
// Show where its been clamped
|
|
if (max > yRange)
|
|
{
|
|
m_2D.DrawFilledBox(bar.x, bar.y + height, bar.w, kOverrunHeight, m_ColorBarMarkerOutOfRange);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShowFrameLinesText(Rect rect, float xStart, float yStart, float yRange, float width, float height, bool subsetSelected, int selectedFirstOffset, int selectedLastOffset)
|
|
{
|
|
int totalDataSize = m_Values.Count;
|
|
float y = yStart;
|
|
|
|
float msSegment = 1000f / 60f;
|
|
|
|
int lines = (int)(yRange / msSegment);
|
|
int step = 1;
|
|
for (int line = 1; line <= lines; line += step, step *= 2)
|
|
{
|
|
float ms = line * msSegment;
|
|
float h = height * ms / yRange;
|
|
int edgePad = 3;
|
|
if (h >= (height / 4) && h < (height - GUI.skin.label.lineHeight))
|
|
{
|
|
GUIContent content = new GUIContent(ToDisplayUnits((float)Math.Floor(ms), true, 0));
|
|
Vector2 size = EditorStyles.miniTextField.CalcSize(content);
|
|
|
|
bool left = true;
|
|
if (subsetSelected)
|
|
{
|
|
float x = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
|
|
float x2 = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize);
|
|
|
|
// text would overlap selection so move it if that prevents overlap
|
|
if (left)
|
|
{
|
|
if (x < (size.x + edgePad) && x2 < (width - (size.x + edgePad)))
|
|
left = false;
|
|
}
|
|
else
|
|
{
|
|
if (x > (size.x + edgePad) && x2 > (width - (size.x + edgePad)))
|
|
left = true;
|
|
}
|
|
}
|
|
|
|
Rect r;
|
|
|
|
if (left)
|
|
r = new Rect(rect.x + (xStart + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
|
|
else
|
|
r = new Rect(rect.x + (xStart + width) - (size.x + edgePad), (rect.y - y) + (height - h), size.x, EditorStyles.miniTextField.lineHeight);
|
|
GUI.Label(r, content, EditorStyles.miniTextField);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShowSelectionMenuItem(bool subsetSelected, GenericMenu menu, GUIContent style, bool state, GenericMenu.MenuFunction func)
|
|
{
|
|
if (subsetSelected)
|
|
menu.AddItem(style, state, func);
|
|
else
|
|
menu.AddDisabledItem(style);
|
|
}
|
|
|
|
void ShowContextMenu(bool subsetSelected, int selectionCount)
|
|
{
|
|
GenericMenu menu = new GenericMenu();
|
|
bool showselectionOptions = subsetSelected || ((m_PairedWithFrameTimeGraph != null) && m_PairedWithFrameTimeGraph.HasSubsetSelected());
|
|
ShowSelectionMenuItem(showselectionOptions || selectionCount == 0, menu, Styles.menuItemSelectAll, false, () => SelectAll());
|
|
ShowSelectionMenuItem(showselectionOptions || selectionCount == m_Values.Count, menu, Styles.menuItemClearSelection, false, () => ClearSelection());
|
|
menu.AddItem(Styles.menuItemInvertSelection, false, () => InvertSelection());
|
|
menu.AddItem(Styles.menuItemSelectMin, false, () => SelectMin());
|
|
menu.AddItem(Styles.menuItemSelectMax, false, () => SelectMax());
|
|
menu.AddItem(Styles.menuItemSelectMedian, false, () => SelectMedian());
|
|
menu.AddSeparator("");
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectPrevious, false, () => SelectPrevious(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectNext, false, () => SelectNext(1));
|
|
menu.AddSeparator("");
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrow, false, () => SelectGrow(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrink, false, () => SelectShrink(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeft, false, () => SelectGrowLeft(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRight, false, () => SelectGrowRight(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeft, false, () => SelectShrinkLeft(1));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRight, false, () => SelectShrinkRight(1));
|
|
menu.AddSeparator("");
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowFast, false, () => SelectGrow(10));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkFast, false, () => SelectShrink(10));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowLeftFast, false, () => SelectGrowLeft(10));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectGrowRightFast, false, () => SelectGrowRight(10));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkLeftFast, false, () => SelectShrinkLeft(10));
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemSelectShrinkRightFast, false, () => SelectShrinkRight(10));
|
|
menu.AddSeparator("");
|
|
ShowSelectionMenuItem(showselectionOptions, menu, Styles.menuItemZoomSelection, false, () => ZoomSelection());
|
|
ShowSelectionMenuItem(m_Zoomed, menu, Styles.menuItemZoomAll, false, () => ZoomAll());
|
|
menu.AddSeparator("");
|
|
menu.AddItem(Styles.menuItemShowSelectedMarker, m_GlobalSettings.showSelectedMarker, () => ToggleShowSelectedMarker());
|
|
menu.AddItem(Styles.menuItemShowThreads, m_GlobalSettings.showThreads, () => ToggleShowThreads());
|
|
menu.AddItem(Styles.menuItemShowFrameLines, m_GlobalSettings.showFrameLines, () => ToggleShowFrameLines());
|
|
//menu.AddItem(Styles.menuItemShowFrameLineText, m_GlobalSettings.showFrameLineText, () => ToggleShowFrameLinesText());
|
|
menu.AddSeparator("");
|
|
menu.AddItem(Styles.menuItemShowOrderedByFrameDuration, m_GlobalSettings.showOrderedByFrameDuration, () => ToggleShowOrderedByFrameDuration());
|
|
|
|
menu.ShowAsContext();
|
|
}
|
|
|
|
string GetYMaxText(float value)
|
|
{
|
|
return ToDisplayUnits(value, true, 0);
|
|
}
|
|
|
|
void DrawYAxisRangeSelector(Rect rect, float yMax)
|
|
{
|
|
string yMaxText = GetYMaxText(yMax);
|
|
|
|
List<GUIContent> yAxisOptions = new List<GUIContent>();
|
|
var graphScaleUnits = ToDisplayUnits(1000f / 60f, true, 0);
|
|
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 60Hz or 60FPS.", graphScaleUnits)));
|
|
graphScaleUnits = ToDisplayUnits(1000f / 30f, true, 0);
|
|
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 30Hz or 30FPS.", graphScaleUnits)));
|
|
graphScaleUnits = ToDisplayUnits(1000f / 15f, true, 0);
|
|
yAxisOptions.Add(new GUIContent(graphScaleUnits, string.Format("Graph Scale : {0} is equivalent to 15Hz or 15FPS.", graphScaleUnits)));
|
|
yAxisOptions.Add(new GUIContent(yMaxText, "Graph Scale : Max frame time from data"));
|
|
|
|
float width = 0;
|
|
foreach (var content in yAxisOptions)
|
|
{
|
|
Vector2 size = EditorStyles.popup.CalcSize(content);
|
|
if (size.x > width)
|
|
width = size.x;
|
|
}
|
|
|
|
// Use smaller width if text is shorter
|
|
int margin = 2;
|
|
width = Math.Min(width + margin, rect.width);
|
|
// Shift right to right align
|
|
rect.x += (rect.width - width);
|
|
rect.x -= margin;
|
|
rect.width = width;
|
|
s_YAxisMode = (AxisMode)EditorGUI.Popup(rect, (int)s_YAxisMode, yAxisOptions.ToArray());
|
|
}
|
|
|
|
void ShowAxis(Rect rect, float xStart, float width, int startOffset, int endOffset, int selectedFirstOffset, int selectedLastOffset, int selectedCount, int totalSelectedCount, float yMax, int totalDataSize, int offsetToDisplayMapping)
|
|
{
|
|
GUIStyle leftAlignStyle = new GUIStyle(GUI.skin.label);
|
|
leftAlignStyle.padding = new RectOffset(leftAlignStyle.padding.left, leftAlignStyle.padding.right, 0, 0);
|
|
leftAlignStyle.alignment = TextAnchor.MiddleLeft;
|
|
GUIStyle rightAlignStyle = new GUIStyle(GUI.skin.label);
|
|
rightAlignStyle.padding = new RectOffset(rightAlignStyle.padding.left, rightAlignStyle.padding.right, 0, 0);
|
|
rightAlignStyle.alignment = TextAnchor.MiddleRight;
|
|
|
|
// y axis
|
|
float h = GUI.skin.label.lineHeight;
|
|
float y = rect.y + ((rect.height - 1) - h);
|
|
|
|
|
|
DrawYAxisRangeSelector(new Rect(rect.x, rect.y, kXAxisWidth, h), yMax);
|
|
|
|
string yMinText = ToDisplayUnits(0, true);
|
|
GUI.Label(new Rect(rect.x, y - h, kXAxisWidth, h), yMinText, rightAlignStyle);
|
|
|
|
|
|
// x axis
|
|
rect.x += xStart;
|
|
|
|
leftAlignStyle.padding = new RectOffset(0, 0, leftAlignStyle.padding.top, leftAlignStyle.padding.bottom);
|
|
rightAlignStyle.padding = new RectOffset(0, 0, rightAlignStyle.padding.top, rightAlignStyle.padding.bottom);
|
|
|
|
int startIndex = offsetToDisplayMapping + startOffset;
|
|
string startIndexText = string.Format("{0}", startIndex);
|
|
GUIContent startIndexContent = new GUIContent(startIndexText);
|
|
Vector2 startIndexSize = GUI.skin.label.CalcSize(startIndexContent);
|
|
bool drawStart = !m_GlobalSettings.showOrderedByFrameDuration;
|
|
|
|
int endIndex = offsetToDisplayMapping + endOffset;
|
|
string endIndexText = string.Format("{0}", endIndex);
|
|
GUIContent endIndexContent = new GUIContent(endIndexText);
|
|
Vector2 endIndexSize = GUI.skin.label.CalcSize(endIndexContent);
|
|
bool drawEnd = !m_GlobalSettings.showOrderedByFrameDuration;
|
|
|
|
|
|
// Show selection frame values (if space for them)
|
|
if (totalSelectedCount > 0)
|
|
{
|
|
if (selectedCount == 0)
|
|
{
|
|
// If we have no selection then adjust 'selection start/end to span whole view so the count display is centred)
|
|
selectedFirstOffset = startOffset;
|
|
selectedLastOffset = endOffset;
|
|
}
|
|
|
|
int selectedFirstX = GetXForDataOffset(selectedFirstOffset, (int)width, totalDataSize);
|
|
int selectedLastX = GetXForDataOffset(selectedLastOffset + 1, (int)width, totalDataSize); // last + 1 so right hand side of the bbar
|
|
int selectedRangeWidth = 1 + (selectedLastX - selectedFirstX);
|
|
|
|
int selectedFirstIndex = offsetToDisplayMapping + selectedFirstOffset;
|
|
int selectedLastIndex = offsetToDisplayMapping + selectedLastOffset;
|
|
|
|
string selectionCountText;
|
|
if (totalSelectedCount != selectedCount)
|
|
selectionCountText = string.Format("[{0} of {1}]", selectedCount, totalSelectedCount);
|
|
else
|
|
selectionCountText = string.Format("[{0}]", selectedCount);
|
|
|
|
string selectionRangeText;
|
|
if (selectedCount > 1)
|
|
{
|
|
if (m_GlobalSettings.showOrderedByFrameDuration)
|
|
selectionRangeText = selectionCountText;
|
|
else
|
|
selectionRangeText = string.Format("{0} {1} {2}", selectedFirstIndex, selectionCountText, selectedLastIndex);
|
|
}
|
|
else
|
|
selectionRangeText = string.Format("{0} {1}", selectedFirstIndex, selectionCountText);
|
|
|
|
string tooltip = string.Format("{0} frames in selection", selectedCount);
|
|
if (totalSelectedCount != selectedCount)
|
|
{
|
|
tooltip = string.Format("{0} frames in zoomed selection\n{1} frames in overall selection", selectedCount, totalSelectedCount);
|
|
}
|
|
|
|
GUIContent selectionRangeTextContent = new GUIContent(selectionRangeText, tooltip);
|
|
Vector2 selectionRangeTextSize = GUI.skin.label.CalcSize(selectionRangeTextContent);
|
|
if ((selectedRangeWidth > selectionRangeTextSize.x && selectedCount > 1) || selectedCount == 0)
|
|
{
|
|
// Selection width is larger than the text so we can split the text
|
|
string selectedFirstIndexText = string.Format("{0}", selectedFirstIndex);
|
|
GUIContent selectedFirstIndexContent = new GUIContent(selectedFirstIndexText);
|
|
Vector2 selectedFirstIndexSize = GUI.skin.label.CalcSize(selectedFirstIndexContent);
|
|
if (m_GlobalSettings.showOrderedByFrameDuration)
|
|
selectedFirstIndexSize.x = 0;
|
|
|
|
string selectedLastIndexText = string.Format("{0}", selectedLastIndex);
|
|
GUIContent selectedLastIndexContent = new GUIContent(selectedLastIndexText);
|
|
Vector2 selectedLastIndexSize = GUI.skin.label.CalcSize(selectedLastIndexContent);
|
|
if (m_GlobalSettings.showOrderedByFrameDuration)
|
|
selectedLastIndexSize.x = 0;
|
|
|
|
GUIContent selectedCountContent = new GUIContent(selectionCountText, tooltip);
|
|
Vector2 selectedCountSize = GUI.skin.label.CalcSize(selectedCountContent);
|
|
|
|
Rect rFirst = new Rect(rect.x + selectedFirstX, y, selectedFirstIndexSize.x, selectedFirstIndexSize.y);
|
|
GUI.Label(rFirst, selectedFirstIndexContent);
|
|
|
|
Rect rLast = new Rect(rect.x + selectedLastX - selectedLastIndexSize.x, y, selectedLastIndexSize.x, selectedLastIndexSize.y);
|
|
GUI.Label(rLast, selectedLastIndexContent);
|
|
|
|
float mid = selectedFirstX + ((selectedLastX - selectedFirstX) / 2);
|
|
Rect rCount = new Rect(rect.x + mid - (selectedCountSize.x / 2), y, selectedCountSize.x, selectedCountSize.y);
|
|
GUI.Label(rCount, selectedCountContent);
|
|
|
|
if (selectedFirstX < startIndexSize.x)
|
|
{
|
|
// would overlap with start text
|
|
drawStart = false;
|
|
}
|
|
if (selectedLastX > ((width - 1) - endIndexSize.x))
|
|
{
|
|
// would overlap with end text
|
|
drawEnd = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int mid = (selectedFirstX + (selectedRangeWidth / 2));
|
|
int selectionTextX = mid - (int)(selectionRangeTextSize.x / 2);
|
|
selectionTextX = ClampToRange(selectionTextX, 0, (int)((width - 1) - selectionRangeTextSize.x));
|
|
|
|
Rect rangeRect = new Rect(rect.x + selectionTextX, y, selectionRangeTextSize.x, selectionRangeTextSize.y);
|
|
GUI.Label(rangeRect, selectionRangeTextContent);
|
|
|
|
if (selectionTextX < startIndexSize.x)
|
|
{
|
|
// would overlap with start text
|
|
drawStart = false;
|
|
}
|
|
if ((selectionTextX + selectionRangeTextSize.x) > ((width - 1) - endIndexSize.x))
|
|
{
|
|
// would overlap with end text
|
|
drawEnd = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Show start and end values
|
|
if (drawStart)
|
|
{
|
|
Rect leftRect = new Rect(rect.x, y, startIndexSize.x, startIndexSize.y);
|
|
GUI.Label(leftRect, startIndexContent, leftAlignStyle);
|
|
}
|
|
|
|
if (drawEnd)
|
|
{
|
|
Rect rightRect = new Rect(rect.x + ((width - 1) - endIndexSize.x), y, endIndexSize.x, endIndexSize.y);
|
|
GUI.Label(rightRect, endIndexContent, rightAlignStyle);
|
|
}
|
|
}
|
|
|
|
void ClearSelection(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
|
|
bool singleControlAction = true; // As we need the frame range to be unique to each data set
|
|
CallSetRange(-1, -1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
|
|
|
|
// Disable zoom
|
|
m_Zoomed = false;
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.ClearSelection(false);
|
|
}
|
|
|
|
void SelectAll(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
|
|
bool singleControlAction = true; // As we need the frame range to be unique to each data set
|
|
CallSetRange(0, dataLength - 1, 0, singleControlAction, FrameTimeGraph.State.DragComplete);
|
|
|
|
// Disable zoom
|
|
m_Zoomed = false;
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectAll(false);
|
|
}
|
|
|
|
void InvertSelection(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
|
|
Dictionary<int, int> frameOffsetToSelectionIndex = new Dictionary<int, int>();
|
|
for (int i = 0; i < m_CurrentSelection.Count; i++)
|
|
{
|
|
int frameOffset = m_CurrentSelection[i];
|
|
frameOffsetToSelectionIndex[frameOffset] = i;
|
|
}
|
|
|
|
m_CurrentSelection.Clear();
|
|
for (int frameIndex = 0; frameIndex < dataLength; frameIndex++)
|
|
{
|
|
if (!frameOffsetToSelectionIndex.ContainsKey(frameIndex))
|
|
m_CurrentSelection.Add(frameIndex);
|
|
}
|
|
|
|
m_SetRange(m_CurrentSelection, 1, FrameTimeGraph.State.DragComplete);
|
|
|
|
// Disable zoom
|
|
m_Zoomed = false;
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.InvertSelection(false);
|
|
}
|
|
|
|
void ZoomSelection(bool effectPaired = true)
|
|
{
|
|
m_Zoomed = true;
|
|
m_ZoomStartOffset = m_CurrentSelectionFirstDataOffset;
|
|
m_ZoomEndOffset = m_CurrentSelectionLastDataOffset;
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.ZoomSelection(false);
|
|
}
|
|
|
|
void ZoomAll(bool effectPaired = true)
|
|
{
|
|
m_Zoomed = false;
|
|
int frames = m_Values.Count;
|
|
|
|
m_ZoomStartOffset = 0;
|
|
m_ZoomEndOffset = frames - 1;
|
|
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.ZoomAll(false);
|
|
}
|
|
|
|
void SelectMin(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
if (dataLength <= 0)
|
|
return;
|
|
|
|
|
|
int minDataOffset = 0;
|
|
float msMin = m_Values[0].ms;
|
|
for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
|
|
{
|
|
float ms = m_Values[dataOffset].ms;
|
|
if (ms < msMin)
|
|
{
|
|
msMin = ms;
|
|
minDataOffset = dataOffset;
|
|
}
|
|
}
|
|
|
|
bool singleControlAction = true;
|
|
CallSetRange(minDataOffset, minDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
|
|
//CallSetRange(minDataOffset, minDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
|
|
m_Zoomed = false;
|
|
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectMin(false);
|
|
}
|
|
|
|
void SelectMax(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
if (dataLength <= 0)
|
|
return;
|
|
|
|
|
|
int maxDataOffset = 0;
|
|
float msMax = m_Values[0].ms;
|
|
for (int dataOffset = 0; dataOffset < dataLength; dataOffset++)
|
|
{
|
|
float ms = m_Values[dataOffset].ms;
|
|
if (ms > msMax)
|
|
{
|
|
msMax = ms;
|
|
maxDataOffset = dataOffset;
|
|
}
|
|
}
|
|
|
|
bool singleControlAction = true;
|
|
CallSetRange(maxDataOffset, maxDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
|
|
//CallSetRange(maxDataOffset, maxDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
|
|
m_Zoomed = false;
|
|
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectMax(false);
|
|
}
|
|
|
|
float GetPercentageOffset(List<Data> data, float percent, out int outputFrameOffset)
|
|
{
|
|
int index = (int)((data.Count - 1) * percent / 100);
|
|
outputFrameOffset = data[index].frameOffset;
|
|
|
|
// True median is half of the sum of the middle 2 frames for an even count. However this would be a value never recorded so we avoid that.
|
|
return data[index].ms;
|
|
}
|
|
|
|
void SelectMedian(bool effectPaired = true)
|
|
{
|
|
int dataLength = m_Values.Count;
|
|
if (dataLength <= 0)
|
|
return;
|
|
|
|
List<Data> sortedValues = new List<Data>();
|
|
foreach (var value in m_Values)
|
|
{
|
|
Data data = new Data(value.ms, value.frameOffset);
|
|
sortedValues.Add(data);
|
|
}
|
|
// If ms value is the same then order by frame offset
|
|
sortedValues.Sort((a, b) => { int compare = a.ms.CompareTo(b.ms); return compare == 0 ? a.frameOffset.CompareTo(b.frameOffset) : compare; });
|
|
int medianFrameOffset = 0;
|
|
GetPercentageOffset(sortedValues, 50, out medianFrameOffset);
|
|
int medianDataOffset = m_FrameOffsetToDataOffsetMapping[medianFrameOffset];
|
|
|
|
|
|
bool singleControlAction = true;
|
|
CallSetRange(medianDataOffset, medianDataOffset, 1, singleControlAction, State.DragComplete); // act like single click, so we select frame
|
|
//CallSetRange(medianDataOffset, medianDataOffset, 2, singleControlAction, State.DragComplete); // act like double click, so we jump to the frame
|
|
m_Zoomed = false;
|
|
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectMedian(false);
|
|
}
|
|
|
|
public void SelectPrevious(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
MoveSelectedRange(-step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectPrevious(step, false);
|
|
}
|
|
|
|
public void SelectNext(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
MoveSelectedRange(step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectNext(step, false);
|
|
}
|
|
|
|
public void SelectGrow(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(-step, step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectGrow(step, false);
|
|
}
|
|
|
|
public void SelectGrowLeft(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(-step, 0, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectGrowLeft(step, false);
|
|
}
|
|
|
|
public void SelectGrowRight(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(0, step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectGrowRight(step, false);
|
|
}
|
|
|
|
public void SelectShrink(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(step, -step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectShrink(step, false);
|
|
}
|
|
|
|
public void SelectShrinkLeft(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(step, 0, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectShrinkLeft(step, false);
|
|
}
|
|
|
|
public void SelectShrinkRight(int step, bool effectPaired = true)
|
|
{
|
|
int clicks = 1;
|
|
bool singleClickAction = true;
|
|
|
|
ResizeSelectedRange(0, -step, clicks, singleClickAction, State.DragComplete);
|
|
|
|
if (m_PairedWithFrameTimeGraph != null && effectPaired)
|
|
m_PairedWithFrameTimeGraph.SelectShrinkRight(step, false);
|
|
}
|
|
|
|
public void ToggleShowThreads()
|
|
{
|
|
m_GlobalSettings.showThreads ^= true;
|
|
}
|
|
|
|
public void ToggleShowSelectedMarker()
|
|
{
|
|
m_GlobalSettings.showSelectedMarker ^= true;
|
|
}
|
|
|
|
public void ToggleShowFrameLines()
|
|
{
|
|
m_GlobalSettings.showFrameLines ^= true;
|
|
}
|
|
|
|
public void ToggleShowFrameLinesText()
|
|
{
|
|
m_GlobalSettings.showFrameLineText ^= true;
|
|
}
|
|
|
|
public void ToggleShowOrderedByFrameDuration()
|
|
{
|
|
m_GlobalSettings.showOrderedByFrameDuration ^= true;
|
|
SetData(m_Values); // Update order
|
|
|
|
if (m_PairedWithFrameTimeGraph != null)
|
|
{
|
|
m_PairedWithFrameTimeGraph.SetData(m_PairedWithFrameTimeGraph.m_Values); // Update order
|
|
}
|
|
}
|
|
|
|
internal struct SelectedRangeState
|
|
{
|
|
public int currentSelectionFirstDataOffset;
|
|
public int currentSelectionLastDataOffset;
|
|
public List<int> lastSelectedFrameOffsets;
|
|
}
|
|
|
|
void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus)
|
|
{
|
|
MoveSelectedRange(offset, clickCount, singleControlAction, inputStatus, new SelectedRangeState()
|
|
{
|
|
currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
|
|
currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
|
|
lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
|
|
});
|
|
}
|
|
|
|
internal void MoveSelectedRange(int offset, int clickCount, bool singleControlAction, State inputStatus, SelectedRangeState selectedRange)
|
|
{
|
|
var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
|
|
var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
|
|
var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
|
|
|
|
if (offset < 0)
|
|
{
|
|
// Clamp offset to graph lower bound.
|
|
if (currentSelectionFirstDataOffset + offset < 0)
|
|
{
|
|
offset = -currentSelectionFirstDataOffset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Clamp offset to graph upper bound.
|
|
if (currentSelectionLastDataOffset + offset >= m_Values.Count)
|
|
{
|
|
offset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
|
|
}
|
|
}
|
|
|
|
// Offset selection.
|
|
List<int> selected = new List<int>();
|
|
foreach (int selectedFrameOffset in lastSelectedFrameOffsets)
|
|
{
|
|
var selectedDataOffset = m_FrameOffsetToDataOffsetMapping[selectedFrameOffset];
|
|
var newDataOffset = selectedDataOffset + offset;
|
|
if (newDataOffset >= 0 && newDataOffset < m_Values.Count)
|
|
{
|
|
var newFrameOffset = m_Values[newDataOffset].frameOffset;
|
|
if (!selected.Contains(newFrameOffset))
|
|
{
|
|
selected.Add(newFrameOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort selection in frame index order.
|
|
selected.Sort();
|
|
|
|
m_SetRange(selected, clickCount, inputStatus);
|
|
}
|
|
|
|
void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState)
|
|
{
|
|
ResizeSelectedRange(leftOffset, rightOffset, clickCount, singleControlAction, inputState, new SelectedRangeState()
|
|
{
|
|
currentSelectionFirstDataOffset = m_CurrentSelectionFirstDataOffset,
|
|
currentSelectionLastDataOffset = m_CurrentSelectionLastDataOffset,
|
|
lastSelectedFrameOffsets = m_LastSelectedFrameOffsets,
|
|
});
|
|
}
|
|
|
|
internal void ResizeSelectedRange(int leftOffset, int rightOffset, int clickCount, bool singleControlAction, State inputState, SelectedRangeState selectedRange)
|
|
{
|
|
const int k_InvalidDataOffset = -1;
|
|
var currentSelectionFirstDataOffset = selectedRange.currentSelectionFirstDataOffset;
|
|
var currentSelectionLastDataOffset = selectedRange.currentSelectionLastDataOffset;
|
|
var lastSelectedFrameOffsets = selectedRange.lastSelectedFrameOffsets;
|
|
|
|
// Clamp left offset to lower graph bound.
|
|
bool isGrowingLeft = leftOffset < 0;
|
|
if (isGrowingLeft && currentSelectionFirstDataOffset + leftOffset < 0)
|
|
{
|
|
leftOffset = -currentSelectionFirstDataOffset;
|
|
}
|
|
|
|
// Clamp right offset to upper graph bound.
|
|
bool isGrowingRight = rightOffset > 0;
|
|
if (isGrowingRight && currentSelectionLastDataOffset + rightOffset > (m_Values.Count - 1))
|
|
{
|
|
rightOffset = (m_Values.Count - 1) - currentSelectionLastDataOffset;
|
|
}
|
|
|
|
int selectionStartDataOffset = k_InvalidDataOffset;
|
|
List<int> selected = new List<int>(lastSelectedFrameOffsets);
|
|
for (int dataOffset = 0; dataOffset < m_Values.Count; ++dataOffset)
|
|
{
|
|
var frameOffset = m_Values[dataOffset].frameOffset;
|
|
if (selectionStartDataOffset == k_InvalidDataOffset)
|
|
{
|
|
// Find selection start.
|
|
if (lastSelectedFrameOffsets.Contains(frameOffset))
|
|
{
|
|
selectionStartDataOffset = dataOffset;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Find selection end.
|
|
bool isSelected = lastSelectedFrameOffsets.Contains(frameOffset);
|
|
if (!isSelected || dataOffset == (m_Values.Count - 1))
|
|
{
|
|
int selectionEndDataOffset;
|
|
|
|
// If we reached the last index and it is selected, this index is the selection end. Otherwise, the previous index is the last selected.
|
|
bool isLastIndex = dataOffset == (m_Values.Count - 1);
|
|
if (isLastIndex && isSelected)
|
|
{
|
|
selectionEndDataOffset = dataOffset;
|
|
}
|
|
else
|
|
{
|
|
selectionEndDataOffset = dataOffset - 1;
|
|
}
|
|
|
|
int newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + leftOffset, 0, m_Values.Count - 1);
|
|
int newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + rightOffset, 0, m_Values.Count - 1);
|
|
|
|
// Enforce a minimum selection width.
|
|
if (newSelectionEndDataOffset < newSelectionStartDataOffset)
|
|
{
|
|
var maximumOffset = (selectionEndDataOffset - selectionStartDataOffset) * 0.5f;
|
|
var adjustedLeftOffset = Mathf.CeilToInt(maximumOffset);
|
|
newSelectionStartDataOffset = Mathf.Clamp(selectionStartDataOffset + adjustedLeftOffset, 0, m_Values.Count - 1);
|
|
var adjustedRightOffset = -Mathf.FloorToInt(maximumOffset);
|
|
newSelectionEndDataOffset = Mathf.Clamp(selectionEndDataOffset + adjustedRightOffset, 0, m_Values.Count - 1);
|
|
}
|
|
|
|
if (selectionStartDataOffset != newSelectionStartDataOffset)
|
|
{
|
|
// Resize from left edge.
|
|
int startDataOffset = Mathf.Min(selectionStartDataOffset, newSelectionStartDataOffset);
|
|
int endDataOffset = Mathf.Max(selectionStartDataOffset, newSelectionStartDataOffset);
|
|
MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingLeft, ref selected);
|
|
}
|
|
|
|
if (selectionEndDataOffset != newSelectionEndDataOffset)
|
|
{
|
|
// Resize from right edge (iterate backwards).
|
|
int startDataOffset = Mathf.Max(selectionEndDataOffset, newSelectionEndDataOffset);
|
|
int endDataOffset = Mathf.Min(selectionEndDataOffset, newSelectionEndDataOffset);
|
|
MoveSelectionEdge(startDataOffset, endDataOffset, isGrowingRight, ref selected);
|
|
}
|
|
|
|
// Reset to find next selection.
|
|
selectionStartDataOffset = k_InvalidDataOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort selection in frame index order.
|
|
selected.Sort();
|
|
|
|
m_SetRange(selected, clickCount, inputState);
|
|
}
|
|
|
|
void MoveSelectionEdge(int startDataOffset, int endDataOffset, bool isGrowingFromEdge, ref List<int> selection)
|
|
{
|
|
var direction = (startDataOffset >= endDataOffset) ? -1 : 1;
|
|
for (int dataOffset = startDataOffset; dataOffset != endDataOffset; dataOffset += direction)
|
|
{
|
|
var frameOffset = m_Values[dataOffset].frameOffset;
|
|
var indexInSelection = selection.IndexOf(frameOffset);
|
|
if (isGrowingFromEdge)
|
|
{
|
|
if (indexInSelection == -1)
|
|
{
|
|
selection.Add(frameOffset);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (indexInSelection != -1)
|
|
{
|
|
selection.RemoveAt(indexInSelection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|