Files
Test/Library/PackageCache/com.unity.collab-proxy@4628d2ba6ea4/Editor/Diff/Text/DiffSplitter.cs
T
2026-11-01 23:39:55 +02:00

483 lines
18 KiB
C#

using System;
using Codice.CM.Client.Differences.Graphic;
using Unity.CodeEditor.Rendering;
using Unity.PlasticSCM.Editor.UI;
using Unity.PlasticSCM.Editor.UI.UIElements;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using XDiffGui;
using XDiffGui.Drawing;
namespace Unity.PlasticSCM.Editor.Diff.Text
{
internal class DiffSplitter : VisualElement
{
internal DiffSplitter(
Unity.CodeEditor.TextEditor srcEditor,
Unity.CodeEditor.TextEditor dstEditor,
VisualElement leftPanel,
VisualElement rightPanel)
{
mLeftTextView = srcEditor;
mRightTextView = dstEditor;
mLeftPanel = leftPanel;
mRightPanel = rightPanel;
style.backgroundColor = UnityStyles.Colors.Diff.DiffSplitterBackgroundColor;
this.SetMouseCursor(MouseCursor.SplitResizeLeftRight);
mImguiContainer = new IMGUIContainer(OnGUI);
mImguiContainer.pickingMode = PickingMode.Ignore;
mImguiContainer.focusable = false;
mImguiContainer.style.position = Position.Absolute;
mImguiContainer.style.left = 0;
mImguiContainer.style.top = 0;
mImguiContainer.style.right = 0;
mImguiContainer.style.bottom = 0;
Add(mImguiContainer);
RegisterCallback<PointerDownEvent>(OnPointerDown);
RegisterCallback<PointerMoveEvent>(OnPointerMove);
RegisterCallback<PointerUpEvent>(OnPointerUp);
}
internal void Dispose()
{
UnregisterCallback<PointerDownEvent>(OnPointerDown);
UnregisterCallback<PointerMoveEvent>(OnPointerMove);
UnregisterCallback<PointerUpEvent>(OnPointerUp);
}
internal DiffSplitterDrawingInfo DrawingInfo
{
get { return mDiffDrawingInfo; }
}
internal void SetDrawingInfo(DiffSplitterDrawingInfo diffDrawingInfo)
{
mDiffDrawingInfo = diffDrawingInfo;
Redraw();
}
internal void Redraw()
{
mImguiContainer.MarkDirtyRepaint();
}
void OnGUI()
{
if (Event.current.type != EventType.Repaint)
return;
try
{
if (mLeftTextView == null || mRightTextView == null)
return;
if (mDiffDrawingInfo == null)
return;
GUI.BeginClip(new Rect(0, 0, resolvedStyle.width, resolvedStyle.height));
for (int i = 0; i < mDiffDrawingInfo.Left.Count; i++)
{
ColorTextRegion leftRegion = mDiffDrawingInfo.Left[i];
ColorTextRegion rightRegion = mDiffDrawingInfo.Right[i];
if (DiffSplitterDrawingInfo.AreMovedRegions(
mDiffDrawingInfo, leftRegion, rightRegion))
continue;
DrawRegion(
leftRegion, rightRegion,
ColorConfiguration.Value.GetSplitterFillColor(
leftRegion, mDiffDrawingInfo.IsMergeSplitter));
}
if (mDiffDrawingInfo.MovedRegions != null)
{
for (int i = 0; i < mDiffDrawingInfo.MovedRegions.Left.Count; i++)
{
DrawRegion(
mDiffDrawingInfo.MovedRegions.Left[i],
mDiffDrawingInfo.MovedRegions.Right[i],
ColorConfiguration.Value.DiffLinesMoveColor);
}
}
GUI.EndClip();
}
catch (Exception ex)
{
Debug.LogError(ex);
}
}
void DrawRegion(
ColorTextRegion leftRegion,
TextRegion rightRegion,
ArgbColor fillColor)
{
if (!IsDifferenceRegionVisible(leftRegion, rightRegion))
return;
if (mLeftTextView.TextArea.TextView.VisualLines.Count == 0 ||
mRightTextView.TextArea.TextView.VisualLines.Count == 0)
return;
bool isCurrentRegion = mDiffDrawingInfo.CurrentLines != null &&
mDiffDrawingInfo.CurrentLines.IsCurrentRegion(leftRegion);
float width = resolvedStyle.width;
float topLeftY = GetStartPosition(leftRegion.InitialLine, mLeftTextView);
float bottomLeftY = GetEndPosition(leftRegion.EndLine - 1, mLeftTextView);
float topRightY = GetStartPosition(rightRegion.InitialLine, mRightTextView);
float bottomRightY = GetEndPosition(rightRegion.EndLine - 1, mRightTextView);
Vector3 topLeft = new Vector3(0, topLeftY, 0);
Vector3 bottomLeft = new Vector3(0,
topLeftY == bottomLeftY ? topLeftY : bottomLeftY, 0);
Vector3 topRight = new Vector3(width, topRightY, 0);
Vector3 bottomRight = new Vector3(width,
topRightY == bottomRightY ? topRightY : bottomRightY, 0);
// CalculateTorsionPoints outputs (nearEnd, nearStart) tangents
// for a left-to-right bezier curve.
CalculateTorsionPoints(width, topLeft, topRight,
out Vector3 topNearEnd, out Vector3 topNearStart);
CalculateTorsionPoints(width, bottomLeft, bottomRight,
out Vector3 botNearEnd, out Vector3 botNearStart);
bool topIsStraight = Mathf.Approximately(topLeft.y, topRight.y);
bool bottomIsStraight = Mathf.Approximately(bottomLeft.y, bottomRight.y);
// 1) Fill first. Floor/Ceil on bezier edges guarantees the fill
// always reaches the border center line, so the 2px bezier
// border (1px each side of center) fully covers the seam.
DrawFill(
ToColor(fillColor), width,
topLeft, topRight, topNearStart, topNearEnd, topIsStraight,
bottomLeft, bottomRight, botNearStart, botNearEnd, bottomIsStraight);
// 2) Borders on top of fill.
// For non-bordered regions we still draw a fill-colored border
// to anti-alias the bezier edges, but we position the bottom
// edge so it ends AT the fill boundary instead of extending below.
bool isUnselected = AdornmentStyle.IsUnselectedRegion(
leftRegion, isCurrentRegion);
bool showBorder = AdornmentStyle.ShowRegionBorderLine(isUnselected);
Color borderColor = showBorder
? ToColor(AdornmentStyle.GetColorLine(leftRegion, isCurrentRegion))
: ToColor(fillColor);
float straightThickness = showBorder
? (float)AdornmentStyle.GetBorderThickness(isCurrentRegion)
: FILL_BORDER_THICKNESS;
// Non-bordered straight edges don't need a border at all -- the
// fill is already pixel-perfect. Drawing a fill-colored rect on
// top would double-blend the alpha, producing a visible darker band.
// We only draw the fill-colored border on bezier edges (for anti-aliasing).
//
// For bordered bezier borders, shift the path by straightThickness/2
// so the bezier center aligns with the center of the straight rect
// borders drawn by TextEditorDrawing (which start at Y and extend
// downward by straightThickness).
float bezierYOffset = showBorder ? straightThickness / 2f : 0f;
if (showBorder || !topIsStraight)
{
DrawEdgeBorder(
borderColor, straightThickness, width,
topLeft, topRight, topNearStart, topNearEnd,
topIsStraight, adjustBottomEdge: false,
bezierYOffset: bezierYOffset);
}
if (showBorder || !bottomIsStraight)
{
DrawEdgeBorder(
borderColor, straightThickness, width,
bottomLeft, bottomRight, botNearStart, botNearEnd,
bottomIsStraight, adjustBottomEdge: !showBorder,
bezierYOffset: bezierYOffset);
}
}
static void DrawEdgeBorder(
Color color, float straightThickness, float width,
Vector3 left, Vector3 right,
Vector3 nearStart, Vector3 nearEnd,
bool isStraight, bool adjustBottomEdge,
float bezierYOffset)
{
if (isStraight)
{
// adjustBottomEdge: pull the rect up so it ends at left.y
// instead of extending below (used for non-bordered regions).
float y = adjustBottomEdge
? left.y - straightThickness
: left.y;
EditorGUI.DrawRect(
new Rect(0, y, width, straightThickness), color);
}
else
{
// Shift the bezier path so its center aligns with the center
// of the rect borders drawn by TextEditorDrawing.
left.y += bezierYOffset;
right.y += bezierYOffset;
nearStart.y += bezierYOffset;
nearEnd.y += bezierYOffset;
Handles.DrawBezier(
left, right,
nearStart, nearEnd,
color, null, BEZIER_BORDER_THICKNESS);
}
}
static void DrawFill(
Color fillColor, float width,
Vector3 topLeft, Vector3 topRight,
Vector3 topNearStart, Vector3 topNearEnd, bool topIsStraight,
Vector3 bottomLeft, Vector3 bottomRight,
Vector3 botNearStart, Vector3 botNearEnd, bool bottomIsStraight)
{
if (topIsStraight && bottomIsStraight)
{
float top = topLeft.y;
float bottom = bottomLeft.y;
if (bottom > top)
EditorGUI.DrawRect(new Rect(0, top, width, bottom - top), fillColor);
return;
}
Vector3[] topSamples = topIsStraight
? null
: Handles.MakeBezierPoints(
topLeft, topRight, topNearStart, topNearEnd,
BEZIER_SAMPLES + 1);
Vector3[] bottomSamples = bottomIsStraight
? null
: Handles.MakeBezierPoints(
bottomLeft, bottomRight, botNearStart, botNearEnd,
BEZIER_SAMPLES + 1);
int columnCount = Mathf.Max(1, Mathf.CeilToInt(width));
for (int i = 0; i < columnCount; i++)
{
float x = i;
float topY = topIsStraight
? topLeft.y
: Mathf.Floor(InterpolateYAtX(topSamples, x));
float bottomY = bottomIsStraight
? bottomLeft.y
: Mathf.Ceil(InterpolateYAtX(bottomSamples, x));
if (bottomY > topY)
{
EditorGUI.DrawRect(
new Rect(x, topY, 1, bottomY - topY), fillColor);
}
}
}
static void CalculateTorsionPoints(
float boundsWidth,
Vector3 leftPoint, Vector3 rightPoint,
out Vector3 torsionLeftPoint, out Vector3 torsionRightPoint)
{
float differenceHeight = leftPoint.y - rightPoint.y;
float differenceWidth;
if (differenceHeight != 0)
{
differenceWidth = Mathf.Max(
Mathf.Min(
boundsWidth / 2f + (boundsWidth / 10f *
(boundsWidth / Mathf.Abs(differenceHeight))),
boundsWidth * 0.95f),
boundsWidth * 0.7f);
}
else
{
differenceWidth = boundsWidth * 0.35f;
}
differenceHeight = differenceHeight *
Mathf.Min(Mathf.Abs(differenceHeight) / boundsWidth, 0.3f);
torsionLeftPoint = new Vector3(
rightPoint.x - differenceWidth,
rightPoint.y + differenceHeight, 0);
torsionRightPoint = new Vector3(
leftPoint.x + differenceWidth,
leftPoint.y - differenceHeight, 0);
}
bool IsDifferenceRegionVisible(TextRegion leftRegion, TextRegion rightRegion)
{
TextView leftTextView = mLeftTextView.TextArea.TextView;
TextView rightTextView = mRightTextView.TextArea.TextView;
if (!leftTextView.VisualLinesValid || !rightTextView.VisualLinesValid)
return false;
if (leftTextView.VisualLines.Count == 0 || rightTextView.VisualLines.Count == 0)
return false;
int leftFirstVisibleLine =
leftTextView.VisualLines[0].FirstDocumentLine.LineNumber;
int leftLastVisibleLine =
leftTextView.VisualLines[leftTextView.VisualLines.Count - 1]
.LastDocumentLine.LineNumber;
int rightFirstVisibleLine =
rightTextView.VisualLines[0].FirstDocumentLine.LineNumber;
int rightLastVisibleLine =
rightTextView.VisualLines[rightTextView.VisualLines.Count - 1]
.LastDocumentLine.LineNumber;
return TextRegionChecker.IsDifferenceRegionVisible(
leftRegion, rightRegion,
leftFirstVisibleLine - 1, leftLastVisibleLine - 1,
rightFirstVisibleLine - 1, rightLastVisibleLine - 1);
}
float GetStartPosition(int line, Unity.CodeEditor.TextEditor editor)
{
if (line < 0)
return 0;
if (line >= editor.Document.LineCount)
return GetEndPosition(editor.Document.LineCount - 1, editor);
VisualLine visualLine = editor.TextArea.TextView.GetOrConstructVisualLine(
editor.Document.GetLineByNumber(line + 1));
return visualLine.VisualTop - (float)editor.TextArea.TextView.ScrollOffset.y;
}
float GetEndPosition(int line, Unity.CodeEditor.TextEditor editor)
{
if (line < 0)
return 0;
if (line >= editor.Document.LineCount)
line = editor.Document.LineCount - 1;
VisualLine visualLine = editor.TextArea.TextView.GetOrConstructVisualLine(
editor.Document.GetLineByNumber(line + 1));
return visualLine.VisualTop + visualLine.Height
- (float)editor.TextArea.TextView.ScrollOffset.y;
}
static float InterpolateYAtX(Vector3[] samples, float x)
{
if (x <= samples[0].x)
return samples[0].y;
for (int i = 0; i < samples.Length - 1; i++)
{
if (x <= samples[i + 1].x)
{
float dx = samples[i + 1].x - samples[i].x;
if (dx < 0.0001f)
return samples[i].y;
float t = (x - samples[i].x) / dx;
return Mathf.Lerp(samples[i].y, samples[i + 1].y, t);
}
}
return samples[samples.Length - 1].y;
}
static Color ToColor(ArgbColor color)
{
return new Color(
color.R / 255f,
color.G / 255f,
color.B / 255f,
color.A / 255f);
}
void OnPointerDown(PointerDownEvent evt)
{
if (evt.button != 0)
return;
mIsDragging = true;
mDragStartX = evt.position.x;
mStartLeftWidth = mLeftPanel.resolvedStyle.width;
mStartRightWidth = mRightPanel.resolvedStyle.width;
this.CapturePointer(evt.pointerId);
evt.StopPropagation();
}
void OnPointerMove(PointerMoveEvent evt)
{
if (!mIsDragging)
return;
float deltaX = evt.position.x - mDragStartX;
float newLeftWidth = mStartLeftWidth + deltaX;
float newRightWidth = mStartRightWidth - deltaX;
if (newLeftWidth < MIN_PANEL_WIDTH)
{
newLeftWidth = MIN_PANEL_WIDTH;
newRightWidth = mStartLeftWidth + mStartRightWidth - MIN_PANEL_WIDTH;
}
else if (newRightWidth < MIN_PANEL_WIDTH)
{
newRightWidth = MIN_PANEL_WIDTH;
newLeftWidth = mStartLeftWidth + mStartRightWidth - MIN_PANEL_WIDTH;
}
mLeftPanel.style.flexGrow = newLeftWidth;
mRightPanel.style.flexGrow = newRightWidth;
evt.StopPropagation();
}
void OnPointerUp(PointerUpEvent evt)
{
if (!mIsDragging)
return;
mIsDragging = false;
this.ReleasePointer(evt.pointerId);
evt.StopPropagation();
}
DiffSplitterDrawingInfo mDiffDrawingInfo;
readonly Unity.CodeEditor.TextEditor mLeftTextView;
readonly Unity.CodeEditor.TextEditor mRightTextView;
readonly VisualElement mLeftPanel;
readonly VisualElement mRightPanel;
readonly IMGUIContainer mImguiContainer;
bool mIsDragging;
float mDragStartX;
float mStartLeftWidth;
float mStartRightWidth;
const int BEZIER_SAMPLES = 50;
const float BEZIER_BORDER_THICKNESS = 2f;
const float FILL_BORDER_THICKNESS = 2f;
const float MIN_PANEL_WIDTH = 100f;
}
}