using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Text; using UnityEngine; using UnityEditor; [assembly: InternalsVisibleTo("Unity.Burst.Editor.Tests")] namespace Unity.Burst.Editor { internal class LongTextArea { private const float naturalEnhancedPad = 20f; private const int kMaxFragment = 2048; private struct Fragment { public int lineCount; public string text; } internal float fontHeight = 0.0f; internal float fontWidth = 0.0f; private string m_Text = ""; private int _mTextLines = 0; private List m_Fragments = null; private bool invalidated = true; internal Vector2 finalAreaSize; private static readonly Texture2D backgroundTexture = Texture2D.whiteTexture; private static readonly GUIStyle textureStyle = new GUIStyle { normal = new GUIStyleState { background = backgroundTexture } }; internal float horizontalPad = 50.0f; private int[] _lineDepth = null; private bool[] _folded = null; internal int[] _blockLine = null; private List[] _blocksFragments = null; private readonly string _foldedString = "...\n"; private BurstDisassembler _disassembler; private int _selectBlockStart = -1; private float _selectStartY = -1f; private int _selectBlockEnd = -1; private float _selectEndY = -1f; private float _renderStartY = 0.0f; private int _renderBlockStart = -1; private int _renderBlockEnd = -1; private int _initialLineCount = -1; private bool _mouseDown = false; private bool _mouseOutsideBounds = true; internal Vector2 _selectPos = Vector2.zero; internal Vector2 _selectDragPos = Vector2.zero; private Color _selectionColor; private Color _selectionColorDarkmode = new Color(0f, .6f, .9f, .5f); private Color _selectionColorLightmode = new Color(0f, 0f, .9f, .2f); private readonly Color[] _colourWheel = new Color[] {Color.blue, Color.cyan, Color.green, Color.magenta, Color.red, Color.yellow, Color.white, Color.black}; internal (int idx, int length) _textSelectionIdx = (0, 0); private bool _textSelectionIdxValid = true; internal (int blockIdx, int textIdx) _enhancedTextSelectionIdxStart = (0, 0); internal (int blockIdx, int textIdx) _enhancedTextSelectionIdxEnd = (0, 0); private bool _oldShowBranchMarkers = false; internal const int regLineThickness = 2; private const int highlightLineThickness = 3; private float _verticalPad = 0; internal Branch _hoveredBranch; private BurstDisassembler.AsmEdge _prevHoveredEdge; private float _hitBoxAdjust = 2f; internal bool IsTextSet(string textToRender) { return textToRender == m_Text; } public void SetText(string textToRender, bool isDarkMode, bool isAssemblyKindJustChanged, BurstDisassembler disassembler, bool useDisassembler) { StopSelection(); // we just switched view m_Text = textToRender; if (!useDisassembler) { _disassembler = null; m_Fragments = RecomputeFragments(m_Text); horizontalPad = 0.0f; } else { m_Fragments = null; SetDisassembler(disassembler); _selectionColor = isDarkMode ? _selectionColorDarkmode : _selectionColorLightmode; } invalidated = true; } public void ExpandAllBlocks() { StopSelection(); int blockIdx = 0; foreach (var block in _disassembler.Blocks) { var changed = _folded[blockIdx]; _folded[blockIdx] = false; if (changed) { finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; } blockIdx++; } } public void FocusCodeBlocks() { StopSelection(); var blockIdx = 0; foreach (var block in _disassembler.Blocks) { bool changed = false; switch (block.Kind) { case BurstDisassembler.AsmBlockKind.None: case BurstDisassembler.AsmBlockKind.Directive: case BurstDisassembler.AsmBlockKind.Block: case BurstDisassembler.AsmBlockKind.Data: if (!_folded[blockIdx]) { changed = true; } _folded[blockIdx] = true; break; case BurstDisassembler.AsmBlockKind.Code: if (_folded[blockIdx]) { changed = true; } _folded[blockIdx] = false; break; } if (changed) { if (_folded[blockIdx]) { finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; } else { finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; } } blockIdx++; } } private void ComputeInitialLineCount() { var blockIdx = 0; _initialLineCount = 0; foreach (var block in _disassembler.Blocks) { switch (block.Kind) { case BurstDisassembler.AsmBlockKind.None: case BurstDisassembler.AsmBlockKind.Directive: case BurstDisassembler.AsmBlockKind.Block: case BurstDisassembler.AsmBlockKind.Data: _folded[blockIdx] = true; break; case BurstDisassembler.AsmBlockKind.Code: _folded[blockIdx] = false; break; } _initialLineCount += _folded[blockIdx] ? 1 : block.Length; blockIdx++; } } public void SetDisassembler(BurstDisassembler disassembler) { _disassembler = disassembler; if (disassembler == null) { return; } var numBlocks = _disassembler.Blocks.Count; var numLinesFromBlock = new int[numBlocks]; _lineDepth = new int[numBlocks]; _folded = new bool[numBlocks]; _blockLine = new int[numBlocks]; _blocksFragments = new List[numBlocks]; ComputeInitialLineCount(); // Count edges var edgeCount = 0; foreach (var block in _disassembler.Blocks) { if (block.Edges != null) { foreach (var edge in block.Edges) { if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) { edgeCount++; } } } } var edgeArray = new BurstDisassembler.AsmEdge[edgeCount]; var edgeIndex = 0; foreach (var block in _disassembler.Blocks) { if (block.Edges != null) { foreach (var edge in block.Edges) { if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) { edgeArray[edgeIndex++] = edge; } } } } Array.Sort(edgeArray, (a, b) => { var src1BlockIdx = a.OriginRef.BlockIndex; var src1Line = _disassembler.Blocks[src1BlockIdx].LineIndex; src1Line += a.OriginRef.LineIndex; var dst1BlockIdx = a.LineRef.BlockIndex; var dst1Line = _disassembler.Blocks[dst1BlockIdx].LineIndex; dst1Line += a.LineRef.LineIndex; var Len1 = Math.Abs(src1Line - dst1Line); var src2BlockIdx = b.OriginRef.BlockIndex; var src2Line = _disassembler.Blocks[src2BlockIdx].LineIndex; src2Line += b.OriginRef.LineIndex; var dst2BlockIdx = b.LineRef.BlockIndex; var dst2Line = _disassembler.Blocks[dst2BlockIdx].LineIndex; dst2Line += b.LineRef.LineIndex; var Len2 = Math.Abs(src2Line - dst2Line); return Len1 - Len2; }); // Iterate through the blocks to pre-compute the widths for branches int maxLine = 0; foreach (var edge in edgeArray) { if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) { var s = edge.OriginRef.BlockIndex; var e = edge.LineRef.BlockIndex; if (e == s + 1) { continue; // don't render if its pointing to next line } int m = 0; int l = s; int le = e; if (e < s) { l = e; le = s; } for (; l <= le; l++) { numLinesFromBlock[l]++; if (m < numLinesFromBlock[l]) { m = numLinesFromBlock[l]; } if (maxLine < m) { maxLine = m; } } _lineDepth[s] = m; } } horizontalPad = naturalEnhancedPad + maxLine * 10; } // Changing the font size doesn't update the text field, so added this to force a recalculation public void Invalidate() { invalidated = true; } private struct HoverBox { public Vector2 position; public string info; public bool valid; } private HoverBox hover; public void Interact(Vector2 pos) { if (_disassembler == null) { return; } // lineNumber (absolute) int lineNumber = Mathf.FloorToInt(pos.y / fontHeight); int blockNumber = 0; for (int idx = 0; idx < _disassembler.Blocks.Count; idx++) { var block = _disassembler.Blocks[idx]; if (lineNumber < block.LineIndex) { break; } if (_folded[idx]) { lineNumber += block.Length - 1; } } int column = Mathf.FloorToInt((pos.x - horizontalPad) / fontWidth); hover.valid = false; if (column < 0 || lineNumber < 0 || lineNumber >= _disassembler.Lines.Count) { return; } int tokIdx; try { tokIdx = _disassembler.GetTokenIndexFromColumn(blockNumber, lineNumber, column, out _); } catch (ArgumentOutOfRangeException) { return; } if (tokIdx < 0) { return; } var tok = _disassembler.GetToken(tokIdx); // Found match hover.valid = true; hover.position = pos; hover.info = $"Token Hit : {tok.Kind} - {tok.Position} - {tok.Length}"; } // Not clipped, so use only if you know the line will be within the ui element private void DrawLine(Vector2 start, Vector2 end, int width) { var matrix = GUI.matrix; Vector2 distance = end - start; float angle = Mathf.Rad2Deg * Mathf.Atan(distance.y / distance.x); if (distance.x < 0) { angle += 180; } int width2 = (int)Mathf.Ceil(width / 2); Rect pos = new Rect(start.x, start.y - width2, distance.magnitude, width); GUIUtility.RotateAroundPivot(angle, start); GUI.DrawTexture(pos, backgroundTexture); GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) } private void DrawLine(Rect line, float angle) { var matrix = GUI.matrix; GUIUtility.RotateAroundPivot(angle, /*new Vector2(line.xMin, line.center.y)*/ /*start*/ new Vector2(line.x, line.center.y)); GUI.DrawTexture(line, backgroundTexture); GUI.matrix = matrix; // restore initial matrix to avoid floating point inaccuracies with RotateAroundPivot(...) } private void DrawHover(GUIStyle style) { if (hover.valid) { GUI.Box(new Rect(horizontalPad + hover.position.x, hover.position.y, (hover.info.Length + 1) * fontWidth, 2 * fontHeight), "", textureStyle); GUI.color = Color.black; GUI.Label(new Rect(horizontalPad + hover.position.x + fontWidth * 0.5f, hover.position.y + fontHeight * 0.5f, hover.info.Length * fontWidth, fontHeight), hover.info, style); } } private Vector2 AnglePoint(float angle, Vector2 point, Vector2 pivotPoint) { // https://matthew-brett.github.io/teaching/rotation_2d.html // Problem Angle is calculates as angle clockwise, and here we use it as it was counterclockwise! var s = Mathf.Sin(angle); var c = Mathf.Cos(angle); point -= pivotPoint; // This is wrong, as we need not rotate around origo, but the rect! return new Vector2(c * point.x - s * point.y, s * point.x + c * point.y) + pivotPoint; } private float CalculateAngle(Vector2 start, Vector2 end) { Vector2 distance = end - start; float angle = Mathf.Rad2Deg * Mathf.Atan(distance.y / distance.x); if (distance.x < 0) { angle += 180; } return angle; } internal struct Branch { public BurstDisassembler.AsmEdge Edge; public Rect StartHorizontal; public Rect VerticalLine; public Rect EndHorizontal; public Rect UpperLine; public Rect LowerLine; public float UpperAngle; public float LowerAngle; public Branch(BurstDisassembler.AsmEdge edge, Rect startHorizontal, Rect verticalLine, Rect endHorizontal, Rect upperLine, Rect lowerLine, float angle1, float angle2) { Edge = edge; StartHorizontal = startHorizontal; VerticalLine = verticalLine; EndHorizontal = endHorizontal; UpperLine = upperLine; LowerLine = lowerLine; UpperAngle = angle1; LowerAngle = angle2; } } private void MakeBranchThin(ref Branch branch) { int lineThickness = regLineThickness; branch.StartHorizontal.height = lineThickness; branch.VerticalLine.width = lineThickness; branch.EndHorizontal.height = lineThickness; branch.UpperLine.height = lineThickness; branch.LowerLine.height = lineThickness; // Adjusting position for arrowtip for thicker lines. branch.UpperLine.y += (highlightLineThickness - regLineThickness); branch.UpperLine.position -= new Vector2(.5f, .5f); branch.LowerLine.position -= new Vector2(.5f, -.5f); // Make end part of arrow expand upwards. branch.EndHorizontal.y += (highlightLineThickness - regLineThickness); } /// /// Use this for hover, as there is a slight visual misbalance /// between cursor position and visual cursor. /// private Vector2 GetMousePosForHover() { Vector2 mousePos = Event.current.mousePosition; mousePos.y -= 0.5f; mousePos.x += 0.5f; return mousePos; } /// /// Calculate the position and size of an edge, and return it as a branch. /// /// The edge to base branch on. /// Start x position of branch. /// Start y position of branch. /// End y position of branch. /// Depth of branch. private Branch CalculateBranch(BurstDisassembler.AsmEdge edge, float x, float y1, float y2, int w) { bool isEdgeHovered = edge.Equals(_prevHoveredEdge); int lineThickness = isEdgeHovered ? highlightLineThickness : regLineThickness; // Calculate rectangles for branch arrows: var start = new Vector2(x, y1 + _verticalPad); var end = start + new Vector2(-(w * 10), 0); var branchStartPos = new Rect(end.x - lineThickness / 2, start.y - 1, start.x - end.x + lineThickness / 2, lineThickness); start = end; end = start + new Vector2(0, y2 - y1); var branchVerticalPartPos = end.y < start.y ? new Rect(start.x - 1, end.y, lineThickness, start.y - end.y) : new Rect(start.x - 1, start.y, lineThickness, end.y - start.y); start = end; end = start + new Vector2(w * 10, 0); var branchEndPos = new Rect(start.x - lineThickness / 2, start.y - 1, end.x - start.x, lineThickness); // Calculate rectangels for arrowtip. Vector2 lowerArrowTipStart = end; Vector2 upperArrowtipStart = end; // Moving the arrowtips closer together. upperArrowtipStart += new Vector2(0, 0.5f); lowerArrowTipStart -= new Vector2(0, 0.5f); // Upper arrowtip. var upperArrowTipEnd = upperArrowtipStart + new Vector2(-5, -5); var upperLine = new Rect(upperArrowtipStart.x, upperArrowtipStart.y - (int)Mathf.Ceil(lineThickness / 2), (upperArrowTipEnd - upperArrowtipStart).magnitude, lineThickness); // Lower arrowtip. var lowerArrowtipEnd = lowerArrowTipStart + new Vector2(-5, 5); var lowerLine = new Rect(lowerArrowTipStart.x, lowerArrowTipStart.y - (int)Mathf.Ceil(lineThickness / 2), (lowerArrowtipEnd - lowerArrowTipStart).magnitude, lineThickness); if (isEdgeHovered) { // Adjusting position for arrowtip for thicker lines. upperArrowtipStart.y -= (highlightLineThickness - regLineThickness); upperArrowTipEnd.y -= (highlightLineThickness - regLineThickness); upperLine.y -= (highlightLineThickness - regLineThickness); upperLine.position += new Vector2(.5f, .5f); lowerLine.position += new Vector2(.5f, -.5f); // Make end part of arrow expand upwards. branchEndPos.y -= (highlightLineThickness - regLineThickness); } var branch = new Branch(edge, branchStartPos, branchVerticalPartPos, branchEndPos, upperLine, lowerLine, CalculateAngle(upperArrowtipStart, upperArrowTipEnd), CalculateAngle(lowerArrowTipStart, lowerArrowtipEnd)); // Handling wether mouse is hovering over edge. Vector2 mousePos = GetMousePosForHover(); // Rotate mousePos so it seems like lower arrow tip is not rotatet. Vector2 lowerArrowTipPivot = lowerArrowTipStart; lowerArrowTipPivot.y -= (int)Mathf.Ceil(lineThickness / 2); Vector2 angledMouseLower = AnglePoint(CalculateAngle(lowerArrowTipPivot, lowerArrowtipEnd), mousePos, new Vector2(lowerLine.x, lowerLine.center.y)); angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); Vector2 upperArrowTipPivot = upperArrowtipStart; upperArrowTipPivot.y += (int)Mathf.Ceil(lineThickness / 2); Vector2 angleMouseUpper = AnglePoint(CalculateAngle(upperArrowTipPivot, upperArrowTipEnd) - 360, mousePos, new Vector2(upperLine.x, upperLine.center.y)); angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); if (AdjustedContains(branchStartPos, mousePos) || AdjustedContains(branchVerticalPartPos, mousePos) || AdjustedContains(branchEndPos, mousePos) || AdjustedContains(lowerLine, angledMouseLower) || AdjustedContains(upperLine, angleMouseUpper)) { // Handling whether another branch was already made thick is done in DrawBranch(...). if (!_hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge)) && _hoveredBranch.Edge.Equals(_prevHoveredEdge)) { return branch; } _hoveredBranch = branch; } return branch; } private bool AdjustedContains(Rect rect, Vector2 point) { return rect.yMax + _hitBoxAdjust >= point.y && rect.yMin - _hitBoxAdjust <= point.y && rect.xMax + _hitBoxAdjust >= point.x && rect.xMin - _hitBoxAdjust <= point.x; } /// /// Draws a branch as a jumpable set of boxes. /// /// The branch to draw. /// Depth of the branch. /// Array of possible colours for branches. /// Current view in inspector. private void DrawBranch(Branch branch, int w, Rect workingArea) { bool isBranchHovered = branch.Edge.Equals(_hoveredBranch.Edge); Vector2 scrollPos = workingArea.position; int lineThickness = isBranchHovered ? highlightLineThickness : regLineThickness; GUI.color = _colourWheel[w % _colourWheel.Length]; // Check if hovered but not made thick yet: if (isBranchHovered && !branch.Edge.Equals(_prevHoveredEdge)) { // alter thickness as edge is hovered over. branch.StartHorizontal.height = highlightLineThickness; branch.VerticalLine.width = highlightLineThickness; branch.EndHorizontal.height = highlightLineThickness; branch.UpperLine.height = highlightLineThickness; branch.LowerLine.height = highlightLineThickness; // Adjusting position for arrowtip for thicker lines. branch.UpperLine.y -= (highlightLineThickness - regLineThickness); branch.UpperLine.position += new Vector2(.5f, .5f); branch.LowerLine.position += new Vector2(.5f, -.5f); // Make end part of arrow expand upwards. branch.EndHorizontal.y -= (highlightLineThickness - regLineThickness); } else if (branch.EndHorizontal.height == highlightLineThickness && !isBranchHovered) { // the branch was previousy hovered, but is now hidden behind // another branch, that is the one being visually hovered. MakeBranchThin(ref branch); } // Render actual arrows: GUI.Box(branch.StartHorizontal, "", textureStyle); GUI.Box(branch.VerticalLine, "", textureStyle); float yy = branch.EndHorizontal.y - scrollPos.y; if (yy >= 0 && yy < workingArea.height) { GUI.Box(branch.EndHorizontal, "", textureStyle); DrawLine(branch.UpperLine, branch.UpperAngle); DrawLine(branch.LowerLine, branch.LowerAngle); } // Use below instead of buttons, to make the currently hovered edge the jumpable, // and not the edge rendered first i.e. when two edges overlap. if (Event.current.type == EventType.MouseDown && isBranchHovered) { Vector2 mousePos = GetMousePosForHover(); // Rotate mousePos so it seems like lower arrow tip is not rotatet. Vector2 lowerLineEnd = branch.LowerLine.position; lowerLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); lowerLineEnd += new Vector2(-5, 5); Vector2 angledMouseLower = AnglePoint(CalculateAngle(branch.LowerLine.position, lowerLineEnd), mousePos, new Vector2(branch.LowerLine.x, branch.LowerLine.center.y)); angledMouseLower.y -= (int)Mathf.Ceil(lineThickness / 2); // Rotate mousePos so it seems like upper arrow tip id not rotatet. Vector2 upperArrowTipPivot = branch.UpperLine.position; upperArrowTipPivot.y += 2 * (int)Mathf.Ceil(lineThickness / 2); Vector2 upperLineEnd = branch.UpperLine.position; upperLineEnd.y += (int)Mathf.Ceil(lineThickness / 2); upperLineEnd += new Vector2(-5, -5); Vector2 angleMouseUpper = AnglePoint(CalculateAngle(upperArrowTipPivot, upperLineEnd) - 360, Event.current.mousePosition, new Vector2(branch.UpperLine.x, branch.UpperLine.center.y)); angleMouseUpper.y += (int)Mathf.Ceil(lineThickness / 2); // Se if a jump should be made and jump. if (AdjustedContains(branch.StartHorizontal, mousePos)) { // make endarrow be at mouse cursor. var target = branch.EndHorizontal; target.y += branch.StartHorizontal.y < branch.EndHorizontal.y ? (workingArea.yMax - mousePos.y) : (workingArea.yMin - mousePos.y + highlightLineThickness / 2f); GUI.ScrollTo(target); Event.current.Use(); } else if (AdjustedContains(branch.EndHorizontal, mousePos) || AdjustedContains(branch.LowerLine, angledMouseLower) || AdjustedContains(branch.UpperLine, angleMouseUpper) || AdjustedContains(branch.VerticalLine, mousePos)) { var target = branch.StartHorizontal; target.y += branch.StartHorizontal.y < branch.EndHorizontal.y ? workingArea.yMin - mousePos.y + highlightLineThickness / 2 : workingArea.yMax - mousePos.y; GUI.ScrollTo(target); Event.current.Use(); } } } private bool DrawFold(float x, float y, bool state, BurstDisassembler.AsmBlockKind kind) { var currentBg = GUI.backgroundColor; switch (kind) { case BurstDisassembler.AsmBlockKind.None: case BurstDisassembler.AsmBlockKind.Directive: case BurstDisassembler.AsmBlockKind.Block: GUI.backgroundColor = Color.grey; break; case BurstDisassembler.AsmBlockKind.Code: GUI.backgroundColor = Color.green; break; case BurstDisassembler.AsmBlockKind.Data: GUI.backgroundColor = Color.magenta; break; } var pressed = false; if (state) { pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "+"); } else { pressed = GUI.Button(new Rect(x - fontWidth, y, fontWidth, fontHeight), "-"); } GUI.backgroundColor = currentBg; return pressed; } private void Layout(GUIStyle style, float hPad) { var cacheSize0 = style.CalcSize(new GUIContent("W\n")); var cacheSize1 = style.CalcSize(new GUIContent("WW\n\n")); var oldFontHeight = fontHeight; var oldFontWidth = fontWidth; fontHeight = cacheSize1.y - cacheSize0.y; fontWidth = cacheSize1.x - cacheSize0.x; _verticalPad = fontHeight * 0.5f; // oldFontWidth == 0 means we picked the first target after opening inspector. var diffX = oldFontWidth != 0 ? fontWidth / oldFontWidth : 0.0f; if (HasSelection() && (oldFontWidth != fontWidth || oldFontHeight != fontHeight)) { float diffY = fontHeight / oldFontHeight; // We only have to take padding into account for x-axis, as it's the only one with it. _selectPos = new Vector2(((_selectPos.x - hPad) * diffX) + hPad, diffY * _selectPos.y); _selectDragPos = new Vector2(((_selectDragPos.x - hPad) * diffX) + hPad, diffY * _selectDragPos.y); } invalidated = false; var oldFinalAreaSizeX = finalAreaSize.x; finalAreaSize = new Vector2(0.0f, 0.0f); if (_disassembler == null) { LayoutPlain(style); } else { finalAreaSize.y = _initialLineCount * fontHeight; finalAreaSize.x = oldFinalAreaSizeX * diffX; } } private void LayoutPlain(GUIStyle style) { // Using plain old text foreach (var frag in m_Fragments) { // Calculate the size as we have hidden control codes in the string var size = style.CalcSize(new GUIContent(frag.text)); finalAreaSize.x = Math.Max(finalAreaSize.x, size.x); finalAreaSize.y += frag.lineCount * fontHeight; } } private void LayoutEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers) { Vector2 scrollPos = workingArea.position; if (HasSelection() && showBranchMarkers != _oldShowBranchMarkers) { // need to alter selection according to padding on x-axis. if (showBranchMarkers) { _selectPos.x += (horizontalPad - naturalEnhancedPad); _selectDragPos.x += (horizontalPad - naturalEnhancedPad); } else { _selectPos.x -= (horizontalPad - naturalEnhancedPad); _selectDragPos.x -= (horizontalPad - naturalEnhancedPad); } } _oldShowBranchMarkers = showBranchMarkers; // Also computes the first and last blocks to render this time and ensures float positionY = 0.0f; int lNum = 0; _renderBlockStart = -1; _renderBlockEnd = -1; _selectBlockStart = -1; _selectStartY = -1f; _selectBlockEnd = -1; _selectEndY = -1f; for (int idx = 0; idx<_disassembler.Blocks.Count; idx++) { var block = _disassembler.Blocks[idx]; var blockHeight = block.Length * fontHeight; var lHeight = block.Length; if (_folded[idx]) { blockHeight = fontHeight; lHeight = 1; } _blockLine[idx] = lNum; if (_selectBlockStart == -1 && _selectPos.y - blockHeight <= positionY) { _selectBlockStart = idx; _selectStartY = positionY; } if (_selectBlockEnd == -1 && (_selectDragPos.y - blockHeight <= positionY || idx == _disassembler.Blocks.Count - 1)) { _selectBlockEnd = idx; _selectEndY = positionY; } // Whole block is above view, or block starts below view. // If at last block and _renderBlockStart == -1, we must have had all block above our scrollPos. // Happens when Scroll view is at bottom and fontsize is decreased. As this forces the scrollview upwards, // we simply set the last block as the one being rendered (avoids an indexOutOfBounds exception). if (((positionY - scrollPos.y + blockHeight) < 0 || (positionY - scrollPos.y) > workingArea.size.y) && !(idx == _disassembler.Blocks.Count - 1 && _renderBlockStart == -1)) { positionY += blockHeight; lNum += lHeight; continue; } if (_renderBlockStart == -1) { _renderBlockStart = idx; _renderStartY = positionY; } _renderBlockEnd = idx; if (_blocksFragments[idx] == null) { _blocksFragments[idx] = RecomputeFragmentsFromBlock(idx); foreach (var fragment in _blocksFragments[idx]) { style.CalcMinMaxWidth(new GUIContent(fragment.text), out _, out var maxWidth); if (finalAreaSize.x < maxWidth) { finalAreaSize.x = maxWidth; } } } positionY += blockHeight; lNum += lHeight; } } internal string GetStartingColorTag(string text) { if (_disassembler?.IsColored ?? false) { const int colorTagLength = 15; int colorTagIdx = text.LastIndexOf("", colorTagIdx, _enhancedTextSelectionIdxStart.textIdx - colorTagIdx) == -1 ? text.Substring(colorTagIdx, colorTagLength) : ""; } } else { return ""; } } internal string GetEndingColorTag(string text) { if (_disassembler?.IsColored ?? false) { int endColorTagIdx = text.IndexOf("", _enhancedTextSelectionIdxEnd.textIdx); // Check that tag actually belongs for this idx. if (endColorTagIdx == -1) { return ""; } else { return text.IndexOf("" : ""; } } else { return ""; } } internal void DoSelectionCopy() { if (HasSelection()) { if (_disassembler != null && _enhancedTextSelectionIdxEnd != _enhancedTextSelectionIdxStart) { var str = new StringBuilder(); if (_enhancedTextSelectionIdxStart.blockIdx < _enhancedTextSelectionIdxEnd.blockIdx) { // Multiple blocks to copy from. int blockIdx = _enhancedTextSelectionIdxStart.blockIdx; if (_folded[blockIdx]) { str.Append(_foldedString.Substring(_enhancedTextSelectionIdxStart.textIdx)); } else { string text = _disassembler.GetOrRenderBlockToText(blockIdx); str.Append(GetStartingColorTag(text) + text.Substring(_enhancedTextSelectionIdxStart.textIdx)); } blockIdx++; for (; blockIdx < _enhancedTextSelectionIdxEnd.blockIdx; blockIdx++) { if (_folded[blockIdx]) { str.Append(_foldedString); } else { str.Append(_disassembler.GetOrRenderBlockToText(blockIdx)); } } if (_folded[blockIdx]) { str.Append(_foldedString.Substring(0, _enhancedTextSelectionIdxEnd.textIdx)); } else { string text = _disassembler.GetOrRenderBlockToText(blockIdx); str.Append(text.Substring(0, _enhancedTextSelectionIdxEnd.textIdx) + GetEndingColorTag(text)); } } else { // Single block to copy from. if (_folded[_enhancedTextSelectionIdxStart.blockIdx]) { str.Append(_foldedString.Substring(_enhancedTextSelectionIdxStart.textIdx, _enhancedTextSelectionIdxEnd.textIdx - _enhancedTextSelectionIdxStart.textIdx)); } else { string text = _disassembler.GetOrRenderBlockToText(_enhancedTextSelectionIdxStart.blockIdx); str.Append(GetStartingColorTag(text) + text.Substring(_enhancedTextSelectionIdxStart.textIdx, _enhancedTextSelectionIdxEnd.textIdx - _enhancedTextSelectionIdxStart.textIdx) + GetEndingColorTag(text)); } } EditorGUIUtility.systemCopyBuffer = str.ToString(); } else if (_textSelectionIdx.length > 0) { EditorGUIUtility.systemCopyBuffer = m_Text.Substring(_textSelectionIdx.idx, _textSelectionIdx.length); } } } internal void SelectAll() { _selectPos = Vector2.zero; _selectDragPos = finalAreaSize; if (_disassembler != null) { _enhancedTextSelectionIdxStart = (0, 0); int blockIdx = _disassembler.Blocks.Count - 1; _enhancedTextSelectionIdxEnd = (blockIdx, !_folded[blockIdx] ? _disassembler.GetOrRenderBlockToText(blockIdx).Length - 1 : _foldedString.Length - 1); } else { _textSelectionIdx = (0, m_Text.Length - 1); } _textSelectionIdxValid = true; } private void ScrollDownToSelection(Rect workingArea) { if (!workingArea.Contains(_selectDragPos + new Vector2(0, fontHeight / 2))) { GUI.ScrollTo(new Rect(_selectDragPos + new Vector2(0, fontHeight), Vector2.zero)); } _textSelectionIdxValid = false; } private void ScrollUpToSelection(Rect workingArea) { if (!workingArea.Contains(_selectDragPos - new Vector2(0, fontHeight / 2))) { GUI.ScrollTo(new Rect(_selectDragPos - new Vector2(0, fontHeight), Vector2.zero)); } _textSelectionIdxValid = false; } internal void MoveSelectionLeft(Rect workingArea, bool showBranchMarkers) { if (_disassembler != null) { float hPad = showBranchMarkers ? horizontalPad : naturalEnhancedPad; string text; int prevLineIdx = Mathf.FloorToInt((_selectDragPos.y - _selectEndY) / fontHeight) - 1; if (_selectDragPos.x <= hPad + fontWidth) { if (prevLineIdx < 0 && _selectBlockEnd == 0) { // we are at the beginning of the text. return; } if (prevLineIdx < 0 && _selectBlockEnd > 0) { // get text from previous block, and do calculations for that. _selectBlockEnd--; (text, prevLineIdx) = !_folded[_selectBlockEnd] ? (_disassembler.GetOrRenderBlockToText(_selectBlockEnd), _disassembler.Blocks[_selectBlockEnd].Length - 1) : (_foldedString, 0); } else { text = _disassembler.GetOrRenderBlockToText(_selectBlockEnd); } int charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx).relative; _selectDragPos.x = charsInLine * fontWidth + hPad + fontWidth / 2; _selectDragPos.y -= fontHeight; } else { // simply move selection left. text = _folded[_selectBlockEnd] ? _foldedString : _disassembler.GetOrRenderBlockToText(_selectBlockEnd); int charsInLine = GetEndIndexOfColoredLine(text, prevLineIdx + 1).relative; if (_selectDragPos.x > charsInLine * fontWidth + hPad) _selectDragPos.x = hPad + charsInLine * fontWidth + fontWidth / 2; _selectDragPos.x -= fontWidth; } } else { int prevLineIdx = Mathf.FloorToInt((_selectDragPos.y) / fontHeight) - 1; if (_selectDragPos.x <= fontWidth) { if (prevLineIdx < 0) { // we are at beginning of the text return; } int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx).relative; _selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; _selectDragPos.y -= fontHeight; } else { // simply move selection left. int charsInLine = GetEndIndexOfPlainLine(m_Text, prevLineIdx+1).relative; if (_selectDragPos.x > charsInLine * fontWidth) { _selectDragPos.x = charsInLine * fontWidth + fontWidth / 2; } _selectDragPos.x -= fontWidth; } } // check if we moved outside of view and scroll if true. ScrollUpToSelection(workingArea); } internal void MoveSelectionRight(Rect workingArea, bool showBranchMarkers) { if (_disassembler != null) { float hPad = showBranchMarkers ? horizontalPad : 20f; string text = _disassembler.GetOrRenderBlockToText(_selectBlockEnd); int thisLine = Mathf.FloorToInt((_selectDragPos.y - _selectEndY) / fontHeight); int charsInLine = GetEndIndexOfColoredLine(text, thisLine).relative; if (_selectDragPos.x >= hPad + charsInLine * fontWidth) { // move down a line: thisLine++; int lineCount = _folded[_selectBlockEnd] ? 1 : _disassembler.Blocks[_selectBlockEnd].Length; if (thisLine > lineCount && _selectBlockEnd == _disassembler.Blocks.Count) { // We are at the end of the text return; } if (thisLine > lineCount) { // selected into next block. _selectBlockEnd++; } _selectDragPos.x = hPad + fontWidth/2; _selectDragPos.y += fontHeight; } else { // simply move selection right. if (_selectDragPos.x < hPad) { _selectDragPos.x = hPad + fontWidth / 2; } _selectDragPos.x += fontWidth; } } else { int thisLine = Mathf.FloorToInt((_selectDragPos.y) / fontHeight); int charsInLine = GetEndIndexOfColoredLine(m_Text, thisLine).relative; if (_selectDragPos.x >= charsInLine*fontWidth) { thisLine++; if (thisLine >= _mTextLines) { // we are at end of the text return; } _selectDragPos.x = 0f; _selectDragPos.y += fontHeight; } else { // simply move selection right. _selectDragPos.x += fontWidth; } } // check if we moved outside of view and scroll if true. ScrollDownToSelection(workingArea); } internal void MoveSelectionUp(Rect workingArea) { if (_selectDragPos.y > fontHeight) { _selectDragPos.y -= fontHeight; } // check if we moved outside of view and scroll if true. ScrollUpToSelection(workingArea); } internal void MoveSelectionDown(Rect workingArea) { if (_selectDragPos.y < finalAreaSize.y - fontHeight) { _selectDragPos.y += fontHeight; } // check if we moved outside of view and scroll if true. ScrollDownToSelection(workingArea); } internal bool MouseOutsideView(Rect workingArea, Vector2 mousePos, int controlID) { if (_mouseDown && !workingArea.Contains(mousePos)) { // Mouse was dragged outside of the view. if (GUIUtility.hotControl == controlID && Event.current.rawType == EventType.MouseUp) { _mouseDown = false; } _mouseOutsideBounds = true; return true; } _mouseOutsideBounds = false; return false; } internal void MouseClicked(bool withShift, Vector2 mousePos, int controlID) { if (_mouseOutsideBounds) { return; } GUIUtility.hotControl = controlID; // FocusControl is to take keyboard focus away from the TreeView. GUI.FocusControl("long text"); if (withShift) { _selectDragPos = mousePos; _textSelectionIdxValid = false; } else { _selectPos = mousePos; StopSelection(); } _mouseDown = true; } internal void DragMouse(Vector2 mousePos) { if (_mouseDown) { _selectDragPos = mousePos; _textSelectionIdxValid = false; } } internal void MouseReleased() { GUIUtility.hotControl = 0; _mouseDown = false; } internal void DoScroll(float mouseRelMoveY) { if (_mouseDown) { _selectDragPos.y += mouseRelMoveY * naturalEnhancedPad; // naturalEnhancedPad magic number taken from unity engine (GUI.cs under EndScrollView). _textSelectionIdxValid = false; } } private void StopSelection() { _selectDragPos = _selectPos; _textSelectionIdx = (0, 0); _textSelectionIdxValid = true; } private bool HasSelection() { return _selectPos != _selectDragPos; } /// (idx in regards to whole str, where colour tags are removed from this line, idx from this line with colour tags removed) internal (int total, int relative) GetEndIndexOfColoredLine(string str, int line) { int lastIdx = -1; int newIdx = -1; for (int i = 0; i <= line; i++) { lastIdx = newIdx; newIdx = str.IndexOf('\n', lastIdx+1); } // Remove color tag filler of line: lastIdx++; int endIdx = newIdx != -1 ? newIdx : str.Length - 1; int colorTagFiller = 0; bool lastWasStart = true; int colorTagStart = str.IndexOf("', colorTagStart+1); // +1 as the index calculation is zero based. colorTagFiller += colorTagEnd - colorTagStart + 1; if (lastWasStart) { colorTagStart = str.IndexOf("", colorTagEnd + 1); lastWasStart = false; } else { colorTagStart = str.IndexOf(" /// Checks if num is within the closed interval defined by endPoint1 and endPoint2. /// internal bool WithinRange(float endPoint1, float endPoint2, float num) { float start, end; if (endPoint1 < endPoint2) { start = endPoint1; end = endPoint2; } else { start = endPoint2; end = endPoint1; } return start <= num && num <= end; } /// /// Renders a blue box relative to text at (positionX, positionY) from start idx to end idx. /// private void RenderLineSelection(float positionX, float positionY, int start, int end) { const int alignmentPad = 2; var oldColor = GUI.color; GUI.color = _selectionColor; GUI.Box(new Rect(positionX + alignmentPad + start*fontWidth, positionY, (end - start)*fontWidth, fontHeight), "", textureStyle); GUI.color = oldColor; } /// /// https://www.programmingnotes.org/7601/cs-how-to-round-a-number-to-the-nearest-x-using-cs/ /// private float RoundDownToNearest(float number, float to) { float inverse = 1 / to; float dividend = Mathf.Floor(number * inverse); return dividend / inverse; } private void SelectText(float positionX, float positionY, float fragHeight, string text, Func GetEndIndexOfLine) { if (HasSelection() && (WithinRange(_selectPos.y, _selectDragPos.y, positionY) || WithinRange(_selectPos.y, _selectDragPos.y, positionY + fragHeight) || WithinRange(positionY, positionY + fragHeight, _selectPos.y))) { Vector2 top = _selectPos; Vector2 bot = _selectDragPos; if (_selectPos.y > _selectDragPos.y) { top = _selectDragPos; bot = _selectPos; } // fixing so we only look at things within this current fragment. Vector2 start = top.y < positionY ? new Vector2(positionX, positionY) : top; Vector2 last = bot.y < positionY + fragHeight ? bot : new Vector2(finalAreaSize.x, positionY + fragHeight - fontHeight / 2); int startLine = Mathf.FloorToInt((start.y - positionY) / fontHeight); int lastLine = Mathf.FloorToInt((last.y - positionY) / fontHeight); if (startLine == lastLine && start.x > last.x) (start, last) = (last, start); // Used for making sure charsIn and charsInDrag does not exceed line length. var (_, startLineEndIdxRel) = GetEndIndexOfLine(text, startLine); var (_, lastLineEndIdxRel) = GetEndIndexOfLine(text, lastLine); int charsIn = Math.Min(Mathf.FloorToInt((start.x - positionX) / fontWidth), startLineEndIdxRel); int charsInDrag = Math.Min(Mathf.FloorToInt((last.x - positionX) / fontWidth), lastLineEndIdxRel); charsIn = charsIn < 0 ? 0 : charsIn; charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; if (RoundDownToNearest(last.y, fontHeight) > RoundDownToNearest(start.y, fontHeight)) { // Multiline selection in this text. int lineEndIdx = startLineEndIdxRel; if (start.y != positionY) { // Selection started in this fragment. RenderLineSelection(positionX, positionY + (startLine * fontHeight), charsIn, lineEndIdx + 1); startLine++; } for (; startLine < lastLine; startLine++) { lineEndIdx = GetEndIndexOfLine(text, startLine).relative; RenderLineSelection(positionX, positionY + (startLine * fontHeight), 0, lineEndIdx + 1); } if (positionY + fragHeight < bot.y) { // select going into next fragment lineEndIdx = GetEndIndexOfLine(text, startLine).relative; charsInDrag = lineEndIdx + 1; } RenderLineSelection(positionX, positionY + (startLine * fontHeight), 0, charsInDrag); } else { // Single line selection in this text segment. int startIdx = charsIn; int endIdx = charsInDrag; if (start.y == positionY) { // Selection started in text segment above. startIdx = 0; } if (positionY + fragHeight < bot.y) { // Selection going into next text segment. endIdx = startLineEndIdxRel + 1; } RenderLineSelection(positionX, positionY + (startLine * fontHeight), startIdx, endIdx); } } } /// /// Updates _textSelectionIdx based on the position of _selectPos and _selectDragPos. /// /// either GetEndIndexOfPlainLine or GetEndIndexOfColoredLine private void UpdateSelectTextIdx(Func GetEndIndexOfLine) { if (!_textSelectionIdxValid && HasSelection()) { var start = _selectPos; var last = _selectDragPos; if (last.y < start.y) { start = _selectDragPos; last = _selectPos; } int startLine = Mathf.FloorToInt(start.y / fontHeight); int lastLine = Mathf.FloorToInt(last.y / fontHeight); if (startLine == lastLine && start.x > last.x) { (start, last) = (last, start); } var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfLine(m_Text, startLine); var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfLine(m_Text, lastLine); int charsIn = Math.Min(Mathf.FloorToInt(start.x / fontWidth), startLineEndIdxRel); int charsInDrag = Math.Min(Mathf.FloorToInt(last.x / fontWidth), lastLineEndIdxRel); charsIn = charsIn < 0 ? 0 : charsIn; charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; int selectStartIdx = startLineEndIdxTotal - (startLineEndIdxRel - charsIn); _textSelectionIdx = (selectStartIdx, lastLineEndIdxTotal - (lastLineEndIdxRel - charsInDrag) - selectStartIdx); _textSelectionIdxValid = true; } } private void RenderPlainOldFragments(GUIStyle style, Rect workingArea) { Vector2 scrollPos = workingArea.position; float positionY = 0.0f; foreach (var fragment in m_Fragments) { float fragHeight = fragment.lineCount * fontHeight; if ((positionY - scrollPos.y + fragHeight) < 0) { positionY += fragHeight; continue; } if ((positionY - scrollPos.y) > workingArea.size.y) break; // we append \n here, as we want it for the selection but not the rendering. SelectText(horizontalPad, positionY, fragHeight, fragment.text + '\n', GetEndIndexOfPlainLine); GUI.Label(new Rect(horizontalPad, positionY, finalAreaSize.x, fragHeight), fragment.text, style); positionY += fragHeight; } UpdateSelectTextIdx(GetEndIndexOfPlainLine); } public void Render(GUIStyle style, Rect workingArea, bool showBranchMarkers) { var hPad = showBranchMarkers ? horizontalPad : 20.0f; style.richText = true; if (invalidated) { Layout(style, hPad); } if (_disassembler != null) { // We always need to call this as its sets up the correct horizontal bar and block rendering LayoutEnhanced(style, workingArea, showBranchMarkers); } // Make sure finalAreaSize.x is correct prior to here GUILayoutUtility.GetRect(finalAreaSize.x + (showBranchMarkers?horizontalPad:20.0f),finalAreaSize.y); if (Event.current.type == EventType.Layout) { // working area will be valid only during repaint, for the layout event we don't draw the labels return; } if (_disassembler == null) { RenderPlainOldFragments(style, workingArea); } else { RenderEnhanced(style, workingArea, showBranchMarkers, hPad); } //DrawHover(style); } private void TestSelUnderscore(GUIStyle style, Vector2 scrollPos, Rect workingArea) { // TODO selection/underline items - for now still hard wired var current = GUI.color; // Selection GUI.color = Color.blue; GUI.Box( new Rect(horizontalPad + style.padding.left + fontWidth * 8, style.padding.top + fontHeight * 19, 3 * fontWidth, 1 * fontHeight), "", textureStyle); // Underscored Vector2 start = new Vector2(horizontalPad + style.padding.left + fontWidth * 8, style.padding.top + fontHeight * 20 - 2); Vector2 end = start + new Vector2((3 + 22) * fontWidth, 0 * fontHeight); GUI.color = Color.red; DrawLine(start, end, 2); GUI.color = current; } private void RenderBranches(Rect workingArea) { var color = GUI.color; List branches = new List(); _hoveredBranch = default; for (int idx = 0;idx<_disassembler.Blocks.Count;idx++) { var block = _disassembler.Blocks[idx]; if (block.Edges != null) { foreach (var edge in block.Edges) { if (edge.Kind == BurstDisassembler.AsmEdgeKind.OutBound) { var srcLine = _blockLine[idx]; if (!_folded[idx]) { srcLine += edge.OriginRef.LineIndex; } var dstBlockIdx = edge.LineRef.BlockIndex; var dstLine = _blockLine[dstBlockIdx]; if (!_folded[dstBlockIdx]) { dstLine += edge.LineRef.LineIndex; } int arrowMinY = srcLine; int arrowMaxY = dstLine; if (srcLine > dstLine) { (arrowMinY, arrowMaxY) = (dstLine, srcLine); } if ((dstBlockIdx == idx + 1 && edge.LineRef.LineIndex == 0) // pointing to next line || !(workingArea.yMin <= arrowMaxY * fontHeight && // Arrow not inside view. workingArea.yMax >= arrowMinY * fontHeight)) { continue; } branches.Add(CalculateBranch(edge, horizontalPad - (4 + fontWidth), srcLine * fontHeight, dstLine * fontHeight, _lineDepth[idx])); } } } } foreach (var branch in branches) { if (!branch.Edge.Equals(_hoveredBranch.Edge)) { DrawBranch(branch, _lineDepth[branch.Edge.OriginRef.BlockIndex], workingArea); } } if (!_hoveredBranch.Edge.Equals(default(BurstDisassembler.AsmEdge))) { DrawBranch(_hoveredBranch, _lineDepth[_hoveredBranch.Edge.OriginRef.BlockIndex], workingArea); } _prevHoveredEdge = _hoveredBranch.Edge; GUI.color = color; } internal int BumpSelectionXByColorTag(string text, int lineIdxTotal, int charsIn) { bool lastWasStart = true; int colorTagStart = text.IndexOf("', colorTagStart + 1); // +1 as the index calculation is zero based. charsIn += colorTagEnd - colorTagStart + 1; if (lastWasStart) { colorTagStart = text.IndexOf("", colorTagEnd + 1); lastWasStart = false; } else { colorTagStart = text.IndexOf(" last.x) { // _selectDragPos was above and behind _selectPos on same line. (start, last) = (last, start); } string text = _folded[blockIdxStart] ? _foldedString : _disassembler.GetOrRenderBlockToText(blockIdxStart); var (startLineEndIdxTotal, startLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockStartline); int startOfLineIdx = startLineEndIdxTotal - startLineEndIdxRel; int charsIn = Math.Min(Mathf.FloorToInt((start.x - hPad) / fontWidth), startLineEndIdxRel); charsIn = charsIn < 0 ? 0 : charsIn; // Adjust charsIn so it takes color tags into considerations. charsIn = BumpSelectionXByColorTag(text, startOfLineIdx, charsIn + 1) - 1; // +1 -1 to not bump charsIn when selecting char just after color tag. _enhancedTextSelectionIdxStart = (blockIdxStart, startOfLineIdx + charsIn); if (blockIdxStart < blockIdxEnd) { text = _folded[blockIdxEnd] ? _foldedString : _disassembler.GetOrRenderBlockToText(blockIdxEnd); } var (lastLineEndIdxTotal, lastLineEndIdxRel) = GetEndIndexOfColoredLine(text, blockEndLine); startOfLineIdx = lastLineEndIdxTotal - lastLineEndIdxRel; int charsInDrag = Math.Min(Mathf.FloorToInt((last.x - hPad) / fontWidth), lastLineEndIdxRel); charsInDrag = charsInDrag < 0 ? 0 : charsInDrag; // Adjust charsInDrag so it takes color tags into considerations. charsInDrag = BumpSelectionXByColorTag(text, startOfLineIdx, charsInDrag); _enhancedTextSelectionIdxEnd = (blockIdxEnd, startOfLineIdx + charsInDrag); _textSelectionIdxValid = true; } } private void RenderEnhanced(GUIStyle style, Rect workingArea, bool showBranchMarkers, float hPad) { //TestSelUnderscore(); if (showBranchMarkers) { RenderBranches(workingArea); } float positionY = _renderStartY; for (int b = _renderBlockStart; b <= _renderBlockEnd; b++) { var block = _disassembler.Blocks[b]; var pressed = DrawFold(hPad - 2, positionY, _folded[b], block.Kind); if (pressed) { _folded[b] = !_folded[b]; StopSelection(); if (_folded[b]) { finalAreaSize.y -= Math.Max(block.Length - 1, 1) * fontHeight; } else { finalAreaSize.y += Math.Max(block.Length - 1, 1) * fontHeight; } } if (!_folded[b]) { foreach (var fragment in _blocksFragments[b]) { var fragLineCount = fragment.lineCount; var fragHeight = fragLineCount * fontHeight; // we append \n here, as we want it for the selection but not the rendering. if (_disassembler.IsColored) { SelectText(hPad, positionY, fragHeight, fragment.text + '\n', GetEndIndexOfColoredLine); } else { SelectText(hPad, positionY, fragHeight, fragment.text + '\n', GetEndIndexOfPlainLine); } GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fragHeight), fragment.text, style); positionY += fragHeight; } } else { SelectText(hPad, positionY, fontHeight, _foldedString, GetEndIndexOfPlainLine); GUI.Label(new Rect(hPad, positionY, finalAreaSize.x, fontHeight), _foldedString, style); positionY += fontHeight; } } UpdateEnhancedSelectTextIdx(hPad); } private List RecomputeFragments(string text) { List result = new List(); string[] pieces = text.Split('\n'); _mTextLines = pieces.Length; StringBuilder b = new StringBuilder(); int lineCount = 0; for (int a=0;a= kMaxFragment) { b.Remove(b.Length - 1, 1); AddFragment(b, lineCount, result); lineCount = 0; } b.Append(pieces[a]); b.Append('\n'); lineCount++; } if (b.Length>0) { b.Remove(b.Length - 1, 1); AddFragment(b, lineCount, result); } return result; } private List RecomputeFragmentsFromBlock(int blockIdx) { var text = _disassembler.GetOrRenderBlockToText(blockIdx).TrimEnd('\n'); return RecomputeFragments(text); } private static void AddFragment(StringBuilder b, int lineCount, List result) { result.Add(new Fragment() { text = b.ToString(), lineCount = lineCount }); b.Length = 0; } } }