Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,23 @@
using System;
using Codice.Client.BaseCommands.BranchExplorer;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal static class BrExConnectionRectangle
{
internal static BrExRectangle CreateConnectionRectangle(
BrExRectangle sourceBounds,
BrExRectangle destinationBounds)
{
int left = Math.Min(sourceBounds.Left, destinationBounds.Left);
int top = Math.Min(sourceBounds.Top, destinationBounds.Top);
int maxRight = Math.Max(sourceBounds.Right, destinationBounds.Right);
int maxBottom = Math.Max(sourceBounds.Bottom, destinationBounds.Bottom);
int width = Math.Max(maxRight - left, 0);
int height = Math.Max(maxBottom - top, 0);
return new BrExRectangle(left, top, width, height);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b5c816a19cf4a868feca49d8cc1023a
timeCreated: 1768387458
@@ -0,0 +1,207 @@
using System;
using Unity.PlasticSCM.Editor.UI;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal class CanvasScrollView : VisualElement
{
internal event Action<Vector2> OnScrollChanged;
internal VisualElement Viewport { get; }
internal VisualElement ContentContainer { get; }
internal Vector2 ScrollOffset
{
get => mScrollOffset;
set => SetScrollOffset(value);
}
internal Vector2 ContentSize
{
get => mContentSize;
set
{
mContentSize = value;
// If viewport has valid geometry, update immediately
// Otherwise, schedule an update for when layout is ready
if (Viewport.layout.width > 0 && Viewport.layout.height > 0)
{
UpdateScrollers();
}
else
{
schedule.Execute(UpdateScrollers);
}
}
}
internal CanvasScrollView()
{
style.flexGrow = 1;
style.flexDirection = FlexDirection.Column;
var mainContainer = new VisualElement();
mainContainer.style.flexGrow = 1;
mainContainer.style.flexDirection = FlexDirection.Row;
Add(mainContainer);
Viewport = new VisualElement();
Viewport.style.backgroundColor =
UnityStyles.Colors.BranchExplorer.ControlBackgroundColor;
Viewport.name = "viewport";
Viewport.style.flexGrow = 1;
Viewport.style.overflow = Overflow.Hidden;
mainContainer.Add(Viewport);
mVerticalScroller = new Scroller(0, 100, OnVerticalScrollChanged);
mVerticalScroller.viewDataKey = "VerticalScroller";
mainContainer.Add(mVerticalScroller);
mBottomContainer = new VisualElement();
mBottomContainer.style.flexDirection = FlexDirection.Row;
Add(mBottomContainer);
mHorizontalScroller = new Scroller(0, 100, OnHorizontalScrollChanged, SliderDirection.Horizontal);
mHorizontalScroller.viewDataKey = "HorizontalScroller";
mHorizontalScroller.style.flexGrow = 1;
mBottomContainer.Add(mHorizontalScroller);
mCorner = new VisualElement();
mBottomContainer.Add(mCorner);
mVerticalScroller.RegisterCallback<GeometryChangedEvent>(OnVerticalScrollerGeometryChanged);
mHorizontalScroller.RegisterCallback<GeometryChangedEvent>(OnHorizontalScrollerGeometryChanged);
ContentContainer = new VisualElement();
ContentContainer.name = "content-container";
ContentContainer.style.position = Position.Absolute;
Viewport.Add(ContentContainer);
Viewport.RegisterCallback<WheelEvent>(OnWheelEvent);
Viewport.RegisterCallback<GeometryChangedEvent>(OnViewportGeometryChanged);
}
internal void Dispose()
{
mVerticalScroller.UnregisterCallback<GeometryChangedEvent>(OnVerticalScrollerGeometryChanged);
mHorizontalScroller.UnregisterCallback<GeometryChangedEvent>(OnHorizontalScrollerGeometryChanged);
Viewport.UnregisterCallback<WheelEvent>(OnWheelEvent);
Viewport.UnregisterCallback<GeometryChangedEvent>(OnViewportGeometryChanged);
}
void OnVerticalScrollerGeometryChanged(GeometryChangedEvent evt)
{
mCorner.style.width = evt.newRect.width;
}
void OnHorizontalScrollerGeometryChanged(GeometryChangedEvent evt)
{
mCorner.style.height = evt.newRect.height;
}
void OnViewportGeometryChanged(GeometryChangedEvent evt)
{
UpdateScrollers();
SetScrollOffset(mScrollOffset);
}
void OnWheelEvent(WheelEvent evt)
{
if (evt.ctrlKey)
return;
if (evt.pressedButtons != 0)
return;
Vector2 delta = evt.delta * MOUSE_WHEEL_SCROLL_SIZE;
ScrollOffset = new Vector2(
ScrollOffset.x + delta.x,
ScrollOffset.y + delta.y);
evt.StopPropagation();
}
void OnHorizontalScrollChanged(float value)
{
mScrollOffset.x = value;
ApplyScrollPosition();
}
void OnVerticalScrollChanged(float value)
{
mScrollOffset.y = value;
ApplyScrollPosition();
}
void SetScrollOffset(Vector2 newOffset)
{
float viewportWidth = Viewport.layout.width;
float viewportHeight = Viewport.layout.height;
float maxScrollX = Mathf.Max(0, mContentSize.x - viewportWidth);
float maxScrollY = Mathf.Max(0, mContentSize.y - viewportHeight);
mScrollOffset.x = Mathf.Clamp(newOffset.x, 0, maxScrollX);
mScrollOffset.y = Mathf.Clamp(newOffset.y, 0, maxScrollY);
// Update scrollers without triggering callbacks
mHorizontalScroller.slider.SetValueWithoutNotify(mScrollOffset.x);
mVerticalScroller.slider.SetValueWithoutNotify(mScrollOffset.y);
ApplyScrollPosition();
}
void UpdateScrollers()
{
if (Viewport.layout.width <= 0 || Viewport.layout.height <= 0)
return;
float viewportWidth = Viewport.layout.width;
float viewportHeight = Viewport.layout.height;
float maxScrollX = Mathf.Max(0, mContentSize.x - viewportWidth);
mHorizontalScroller.lowValue = 0;
mHorizontalScroller.highValue = maxScrollX;
float horizontalFactor = mContentSize.x > 0 ? viewportWidth / mContentSize.x : 1f;
mHorizontalScroller.Adjust(horizontalFactor);
float maxScrollY = Mathf.Max(0, mContentSize.y - viewportHeight);
mVerticalScroller.lowValue = 0;
mVerticalScroller.highValue = maxScrollY;
float verticalFactor = mContentSize.y > 0 ? viewportHeight / mContentSize.y : 1f;
mVerticalScroller.Adjust(verticalFactor);
bool showHorizontalScroller = maxScrollX > 0;
bool showVerticalScroller = maxScrollY > 0;
mBottomContainer.style.display = showHorizontalScroller ? DisplayStyle.Flex : DisplayStyle.None;
mVerticalScroller.style.display = showVerticalScroller ? DisplayStyle.Flex : DisplayStyle.None;
mCorner.style.display = showHorizontalScroller && showVerticalScroller ? DisplayStyle.Flex : DisplayStyle.None;
}
void ApplyScrollPosition()
{
// Position the content container using transform (negative because we're moving content opposite to scroll)
ContentContainer.style.translate = new Translate(-mScrollOffset.x, -mScrollOffset.y);
OnScrollChanged?.Invoke(mScrollOffset);
}
Vector2 mScrollOffset;
Vector2 mContentSize;
VisualElement mBottomContainer;
VisualElement mCorner;
Scroller mHorizontalScroller;
Scroller mVerticalScroller;
// Matches Unity's default UIElementsUtility.singleLineHeight (18px)
const float MOUSE_WHEEL_SCROLL_SIZE = 18f;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e894a18d7962e4847a912b6e5058a75e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,55 @@
using UnityEngine;
using Codice.Client.BaseCommands.BranchExplorer;
using Codice.CM.Common;
using PlasticGui.WorkspaceWindow.Configuration;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes.Colors;
using ShapeType = PlasticGui.WorkspaceWindow.BranchExplorer.ShapeType;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal class ChangesetParentLinkVirtualShape : VirtualShape
{
internal MergeType MergeLinkInvervalType
{
get { return mMergeLinkInvervalType; }
set
{
mMergeLinkInvervalType = value;
if (mVisual == null)
return;
((ParentLinkShape)mVisual).MergeLinkInvervalType = value;
}
}
internal bool IsHighlightedByMergeLinkInterval
{
get { return mIsHighlightedByMergeLinkInterval; }
set
{
mIsHighlightedByMergeLinkInterval = value;
if (mVisual == null)
return;
((ParentLinkShape)mVisual).IsHighlightedByMergeLinkInterval = value;
}
}
internal ChangesetParentLinkVirtualShape(
ObjectDrawInfo drawInfo,
Rect bounds,
WorkspaceUIConfiguration config,
ColorProvider colorProvider = null) : base(
drawInfo, bounds, ShapeType.ChangesetParentLink, config, colorProvider)
{
}
bool mIsHighlightedByMergeLinkInterval;
MergeType mMergeLinkInvervalType;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e34d4d09700143d698cc4baf1facbf28
timeCreated: 1768387507
@@ -0,0 +1,220 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal class QuadTree
{
internal QuadTree(Rect overallBounds)
{
mOverallBounds = overallBounds;
mQuadrant = new Quadrant(null, overallBounds);
mIndexedQuadrants = new Dictionary<IVirtualChild, Quadrant>();
}
internal void InsertNode(IVirtualChild node)
{
mIndexedQuadrants[node] = mQuadrant.InsertNode(node);
}
internal IEnumerable<IVirtualChild> GetNodesInside(Rect bounds)
{
List<QuadNode> result = new List<QuadNode>();
mQuadrant.GetIntersectingNodes(result, bounds);
foreach (QuadNode n in result)
{
yield return n.Node;
}
}
/// The canvas is split up into four Quadrants and objects are stored in the quadrant that contains them
/// and each quadrant is split up into four child Quadrants recurrsively. Objects that overlap more than
/// one quadrant are stored in the mNodes list for this Quadrant.
class Quadrant
{
internal Quadrant(Quadrant parent, Rect bounds)
{
mParent = parent;
mBounds = bounds;
}
internal Quadrant InsertNode(IVirtualChild node)
{
Quadrant child = GetQuadrant(node.Bounds);
if (child != null)
{
return child.InsertNode(node);
}
QuadNode n = new QuadNode(node);
if (mNodes == null)
{
n.Next = n;
}
else
{
// link up in circular link list.
QuadNode x = mNodes;
n.Next = x.Next;
x.Next = n;
}
mNodes = n;
return this;
}
internal void GetIntersectingNodes(List<QuadNode> nodes, Rect bounds)
{
if (bounds == default)
return;
float w = mBounds.width / 2;
float h = mBounds.height / 2;
// assumption that the Rect struct is almost as fast as doing the operations
// manually since Rect is a value type.
Rect topLeft = new Rect(mBounds.xMin, mBounds.yMin, w, h);
Rect topRight = new Rect(mBounds.xMin + w, mBounds.yMin, w, h);
Rect bottomLeft = new Rect(mBounds.xMin, mBounds.yMin + h, w, h);
Rect bottomRight = new Rect(mBounds.xMin + w, mBounds.yMin + h, w, h);
// See if any child quadrants completely contain this node.
if (topLeft.Intersects(bounds) && mTopLeft != null)
{
mTopLeft.GetIntersectingNodes(nodes, bounds);
}
if (topRight.Intersects(bounds) && mTopRight != null)
{
mTopRight.GetIntersectingNodes(nodes, bounds);
}
if (bottomLeft.Intersects(bounds) && mBottomLeft != null)
{
mBottomLeft.GetIntersectingNodes(nodes, bounds);
}
if (bottomRight.Intersects(bounds) && mBottomRight != null)
{
mBottomRight.GetIntersectingNodes(nodes, bounds);
}
GetIntersectingNodes(mNodes, nodes, bounds);
}
Quadrant GetQuadrant(Rect bounds)
{
float w = mBounds.width / 2;
if (w == 0)
{
w = 1;
}
float h = mBounds.height / 2;
if (h == 0)
{
h = 1;
}
// assumption that the Rect struct is almost as fast as doing the operations
// manually since Rect is a value type.
Rect topLeft = new Rect(mBounds.xMin, mBounds.yMin, w, h);
Rect topRight = new Rect(mBounds.xMin + w, mBounds.yMin, w, h);
Rect bottomLeft = new Rect(mBounds.xMin, mBounds.yMin + h, w, h);
Rect bottomRight = new Rect(mBounds.xMin + w, mBounds.yMin + h, w, h);
// See if any child quadrants completely contain this node.
if (topLeft.Contains(bounds))
{
if (mTopLeft == null)
{
mTopLeft = new Quadrant(this, topLeft);
}
return mTopLeft;
}
if (topRight.Contains(bounds))
{
if (mTopRight == null)
{
mTopRight = new Quadrant(this, topRight);
}
return mTopRight;
}
if (bottomLeft.Contains(bounds))
{
if (mBottomLeft == null)
{
mBottomLeft = new Quadrant(this, bottomLeft);
}
return mBottomLeft;
}
if (bottomRight.Contains(bounds))
{
if (mBottomRight == null)
{
mBottomRight = new Quadrant(this, bottomRight);
}
return mBottomRight;
}
return null;
}
static void GetIntersectingNodes(QuadNode last, List<QuadNode> nodes, Rect bounds)
{
if (last == null)
return;
QuadNode n = last;
do
{
n = n.Next; // first node.
if (n.Node.Bounds.Intersects(bounds))
{
nodes.Add(n);
}
} while (n != last);
}
Quadrant mParent;
Rect mBounds;
QuadNode mNodes; // nodes that overlap the sub quadrant boundaries.
// The quadrant is subdivided when nodes are inserted that are
// completely contained within those subdivisions.
Quadrant mTopLeft;
Quadrant mTopRight;
Quadrant mBottomLeft;
Quadrant mBottomRight;
}
class QuadNode
{
internal IVirtualChild Node { get { return mNode; } }
/// QuadNodes form a linked (circular) list in the Quadrant.
internal QuadNode Next
{
get { return mNext; }
set { mNext = value; }
}
internal QuadNode(IVirtualChild node)
{
mNode = node;
}
IVirtualChild mNode;
QuadNode mNext;
}
Rect mOverallBounds;
Quadrant mQuadrant;
IDictionary<IVirtualChild, Quadrant> mIndexedQuadrants;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f2d293552be94873903931c954e5e935
timeCreated: 1768317936
@@ -0,0 +1,58 @@
using System;
using System.Diagnostics;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal class SelfThrottlingWorker
{
internal delegate int QuantizedWorkHandler(int quantum);
internal void CreateQuanta(QuantizedWorkHandler quantizedWorkHandler)
{
mCreateQuanta = SelfThrottlingWork(
mCreateQuanta, mIdealDuration, quantizedWorkHandler);
}
internal void RemoveQuanta(QuantizedWorkHandler quantizedWorkHandler)
{
mRemoveQuanta = SelfThrottlingWork(
mRemoveQuanta, mIdealDuration, quantizedWorkHandler);
}
internal void GcQuanta(QuantizedWorkHandler quantizedWorkHandler)
{
mGcQuanta = SelfThrottlingWork(
mGcQuanta, mIdealDuration, quantizedWorkHandler);
}
//Self-tuning how much time is allocated to the given handler.
/// <param name="quantum">The current quantum allocation</param>
/// <param name="idealDuration">The time in milliseconds we want to take</param>
/// <param name="handler">The handler to call that does the work being throttled</param>
/// <returns>Returns the new quantum to use next time that will more likely hit the ideal time</returns>
static int SelfThrottlingWork(
int quantum, int idealDuration, QuantizedWorkHandler handler)
{
Stopwatch timer = new Stopwatch();
timer.Start();
int count = handler(quantum);
timer.Stop();
long durationInMilliseconds = timer.ElapsedMilliseconds;
if (durationInMilliseconds > 0 && count > 0)
{
long estimatedFullDuration = durationInMilliseconds * (quantum / count);
long newQuanta = (quantum * idealDuration) / estimatedFullDuration;
quantum = Math.Max(100, (int)Math.Min(newQuanta, int.MaxValue));
}
return quantum;
}
int mCreateQuanta = 1000;
int mRemoveQuanta = 2000;
int mGcQuanta = 5000;
int mIdealDuration = 50; // 50 milliseconds.
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cb31873eafb140beb7245ed26f800b10
timeCreated: 1768379571
@@ -0,0 +1,854 @@
using System;
using System.Collections.Generic;
using Codice.Client.BaseCommands.BranchExplorer;
using Codice.Client.BaseCommands.BranchExplorer.Layout;
using PlasticGui.WorkspaceWindow.BranchExplorer;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal interface IVirtualChild
{
Rect Bounds { get; }
VisualElement Visual { get; }
VisualElement CreateVisual();
void DisposeVisual();
}
internal interface IVirtualCanvasUpdateVisualsListener
{
void OnVisualsUpdated();
}
internal interface IVirtualCanvasUpdateListener
{
void OnDelayedCanvasUpdate();
}
internal class VirtualCanvas : VisualElement
{
internal Vector2 ViewPortSize { get { return mViewPortSize; } }
internal Vector2 ExtentSize { get { return mExtent; } }
internal Vector2 ScrollOffset
{
get { return mScrollView.ScrollOffset; }
}
internal float ZoomLevel
{
get { return style.scale.value.value.x; }
}
internal BrExLayout Layout { get; set; }
internal List<IVirtualChild> VirtualChildren { get { return mChildren; } }
internal VirtualCanvas(
CanvasScrollView scrollView,
IVirtualCanvasUpdateVisualsListener loadedListener,
IVirtualCanvasUpdateListener updatedListener)
{
mScrollView = scrollView;
mLoadedListener = loadedListener;
mUpdatedListener = updatedListener;
mExtraSizeAroundEdges = new Vector2(10, 10);
mChildren = new List<IVirtualChild>();
style.transformOrigin = new TransformOrigin(0, 0, 0);
mBackdrop = new VisualElement();
Add(mBackdrop);
mSelfThrottlingWorker = new SelfThrottlingWorker();
mScrollView.Viewport.RegisterCallback<GeometryChangedEvent>(OnViewPortGeometryChanged);
}
internal void Dispose()
{
mScrollView.Viewport.UnregisterCallback<GeometryChangedEvent>(OnViewPortGeometryChanged);
if (mChildren != null)
{
foreach (IVirtualChild child in mChildren)
child.DisposeVisual();
mChildren.Clear();
}
mVisualPositions?.Clear();
mVisibleRegions?.Clear();
mDirtyRegions?.Clear();
mIndex = null;
}
internal List<BrExShape> GetShapes()
{
List<BrExShape> result = new List<BrExShape>();
foreach (VisualElement control in Children())
{
BrExShape shape = control as BrExShape;
if (shape == null)
continue;
result.Add(shape);
}
return result;
}
internal List<ChangesetShape> GetChangesetShapes()
{
List<ChangesetShape> result = new List<ChangesetShape>();
foreach (var element in Children())
{
ChangesetShape commitShape = element as ChangesetShape;
if (commitShape != null)
result.Add(commitShape);
}
return result;
}
// A virtualized node that is off-screen has no VisualElement, so a
// Q<>() against its name returns null. Bring it into the visible
// rect and realize its visual so tests can resolve and drive it.
internal void ScrollChangesetIntoView(long changesetId)
{
IVirtualChild target = FindChangesetChild(changesetId);
if (target == null)
return;
Rect bounds = target.Bounds;
float zoom = ZoomLevel;
mScrollView.ScrollOffset = new Vector2(bounds.x * zoom, bounds.y * zoom);
OnScrollChanged();
LazyUpdateVisuals();
}
IVirtualChild FindChangesetChild(long changesetId)
{
foreach (IVirtualChild child in mChildren)
{
VirtualShape virtualShape = child as VirtualShape;
if (virtualShape == null ||
virtualShape.ShapeType != ShapeType.Changeset)
continue;
ChangesetDrawInfo changesetDraw =
virtualShape.DrawInfo as ChangesetDrawInfo;
if (changesetDraw == null)
continue;
BrExChangeset changeset = changesetDraw.Tag as BrExChangeset;
if (changeset != null && changeset.Id == changesetId)
return child;
}
return null;
}
internal void SetLayout(BrExLayout layout)
{
Layout = layout;
if (layout == null)
return;
mExtent = new Vector2(
Layout.Size.Width,
Layout.Size.Height);
}
internal void ClearVirtualChildren()
{
mChildren.Clear();
}
internal void AddVirtualChild(IVirtualChild child)
{
mChildren.Add(child);
}
internal void Redraw()
{
foreach (IVirtualChild child in mChildren)
{
if (child.Visual == null)
continue;
BrExShape brExShape = (BrExShape)child.Visual;
brExShape.Redraw();
}
}
internal void RedrawChangesetShapes()
{
foreach (IVirtualChild child in mChildren)
{
if (child.Visual == null)
continue;
VirtualShape virtualShape = (VirtualShape)child;
if (virtualShape.ShapeType != ShapeType.Changeset)
continue;
BrExShape brExShape = (BrExShape)child.Visual;
brExShape.Redraw();
}
}
internal void RedrawChangesetCommentShapes()
{
foreach (IVirtualChild child in mChildren)
{
if (child.Visual == null)
continue;
VirtualShape virtualShape = (VirtualShape)child;
if (virtualShape.ShapeType != ShapeType.ChangesetComment)
continue;
BrExShape brExShape = (BrExShape)child.Visual;
brExShape.Redraw();
}
}
internal void RebuildVisuals()
{
InvalidateVisuals();
StartLazyUpdate();
}
internal void InvalidateVisuals()
{
mIndex = null;
mVisualPositions = null;
mVisible = new Rect();
mVisualsUpdated = false;
mDone = false;
foreach (VisualElement e in Children())
{
IVirtualChild n = e.userData as IVirtualChild;
if (n != null)
{
e.userData = null;
n.DisposeVisual();
}
}
Clear();
Add(mBackdrop);
InvalidateLayout();
}
internal void LazyUpdateVisuals()
{
// Do a quantized unit of work for creating newly visible visuals,
//and cleaning up visuals that are no longer needed.
if (mIndex == null)
{
this.CalculateNeededSizeToDisplayAllTheVirtualChildren();
}
mLastCanvasUpdateTicks = Environment.TickCount;
mDone = true;
mAdded = 0;
mRemoved = 0;
mSelfThrottlingWorker.CreateQuanta(
new SelfThrottlingWorker.QuantizedWorkHandler(LazyCreateNodes));
mSelfThrottlingWorker.RemoveQuanta(
new SelfThrottlingWorker.QuantizedWorkHandler(LazyRemoveNodes));
mSelfThrottlingWorker.GcQuanta(
new SelfThrottlingWorker.QuantizedWorkHandler(LazyGarbageCollectNodes));
if (mAdded > 0)
{
InvalidateLayout();
}
if (!mDone)
{
StartLazyUpdate();
}
MarkDirtyRepaint();
}
internal void UpdateVisualBounds(VisualElement visual, Rect bounds)
{
visual.style.left = bounds.xMin;
visual.style.top = bounds.yMin;
}
internal void BeginUpdateScroll()
{
mIsUpdatingScroll = true;
}
internal void EndUpdateScroll()
{
mIsUpdatingScroll = false;
}
internal void OnZoomChanged()
{
mScrollView.ContentSize = new Vector2(mExtent.x * ZoomLevel, mExtent.y * ZoomLevel);
OnScrollChanged();
}
// The visible region has changed, so we need to queue up work for dirty regions and new visible regions
// then start asynchronously building new visuals via StartLazyUpdate.
internal void OnScrollChanged()
{
if (mIsUpdatingScroll)
return;
Rect dirty = mVisible;
AddTheCurrentVisibleRectToTheListOfRegionsToProcess();
mNodeCollectCycle = 0;
mDone = false;
RegionsUpdater.UpdateDirtyRegions(mDirtyRegions, dirty, mVisible);
StartLazyUpdate();
foreach (var child in mChildren)
((BrExShape)child.Visual)?.OnScrollChanged();
}
internal Rect GetVisibleRectStrict()
{
// The rect visible by the user, without any kind of security margins
float xstart = mScrollView.ScrollOffset.x / ZoomLevel;
float ystart = mScrollView.ScrollOffset.y / ZoomLevel;
float xend = (mScrollView.ScrollOffset.x + mViewPortSize.x) / ZoomLevel;
float yend = (mScrollView.ScrollOffset.y + mViewPortSize.y) / ZoomLevel;
return new Rect(xstart, ystart, xend - xstart, yend - ystart);
}
internal void RestoreZOrder(VisualElement element)
{
IVirtualChild child = element.userData as IVirtualChild;
if (child == null)
return;
int position = GetVisualPosition(child);
int insertIndex = FindInsertionIndex(position);
// move the element to the correct position
hierarchy.Insert(insertIndex, element);
}
internal Rect GetVisibleFrame(int offset)
{
Rect visibleRect = GetVisibleRect();
return visibleRect.Inflate(-offset / ZoomLevel, -offset / ZoomLevel);
}
void InvalidateLayout()
{
schedule.Execute(PerformLayout);
}
void OnViewPortGeometryChanged(GeometryChangedEvent evt)
{
// Equivalent to checking if availableSize changed in MeasureOverride
if (evt.oldRect.size == evt.newRect.size)
return;
Vector2 newSize = evt.newRect.size;
if (newSize == mViewPortSize)
return;
SetViewportSize(newSize);
PerformLayout();
}
void PerformLayout()
{
CalculateNeededSizeToDisplayAllTheVirtualChildren();
if (mIndex == null)
{
StartLazyUpdate();
}
NotifyVisualsUpdated();
}
/// <summary>
/// Calculate the size needed to display all the virtual children.
/// </summary>
void CalculateNeededSizeToDisplayAllTheVirtualChildren()
{
bool rebuild = false;
if (mIndex == null || mExtent.x == 0 || mExtent.y == 0 ||
float.IsNaN(mExtent.x) || float.IsNaN(mExtent.y))
{
rebuild = true;
int width = Layout == null ? 0 : Layout.Size.Width;
int height = Layout == null ? 0 : Layout.Size.Height;
mExtent = new Vector2(width, height);
// Ok, now we know the size we can create the index.
mIndex = new QuadTree(new Rect(0, 0, mExtent.x, mExtent.y));
mVisualPositions = new Dictionary<IVirtualChild, int>();
int index = 0;
foreach (IVirtualChild n in mChildren)
{
// Position is calculated as: (zIndex * BASE_MULTIPLIER) + originalIndex
// This ensures proper z-ordering while maintaining relative order within each z-index level.
// Shapes with lower z-index are placed earlier in the hierarchy (drawn at the back),
// shapes with higher z-index are placed later (drawn at the front).
int zIndex = ZIndex.GetZIndex((VirtualShape)n);
mVisualPositions[n] = (zIndex * ZIndex.BASE_MULTIPLIER) + index++;
if (n.Bounds.width > 0 && n.Bounds.height > 0)
{
mIndex.InsertNode(n);
}
}
}
float w = mExtent.x;
float h = mExtent.y;
style.width = w;
style.height = h;
mScrollView.ContentSize = new Vector2(w * ZoomLevel, h * ZoomLevel);
if (!float.IsInfinity(mViewPortSize.x) &&
!float.IsInfinity(mViewPortSize.y))
{
w = Math.Max(w, mViewPortSize.x / ZoomLevel);
h = Math.Max(h, mViewPortSize.y / ZoomLevel);
mBackdrop.style.width = w;
mBackdrop.style.height = h;
}
if (rebuild)
{
AddTheCurrentVisibleRectToTheListOfRegionsToProcess();
}
}
void NotifyVisualsUpdated()
{
if (mVisualsUpdated)
return;
if (Layout == null)
return;
mVisualsUpdated = true;
mLoadedListener.OnVisualsUpdated();
}
Rect GetVisibleRect()
{
// Add a bit of extra around the edges so we are sure to create
// nodes that have a tiny bit showing.
float xstart = (mScrollView.ScrollOffset.x - mExtraSizeAroundEdges.x) / ZoomLevel;
float ystart = (mScrollView.ScrollOffset.y - mExtraSizeAroundEdges.y) / ZoomLevel;
float xend = (mScrollView.ScrollOffset.x + (mViewPortSize.x +
(2 * mExtraSizeAroundEdges.x))) / ZoomLevel;
float yend = (mScrollView.ScrollOffset.y + (mViewPortSize.y +
(2 * mExtraSizeAroundEdges.y))) / ZoomLevel;
return new Rect(xstart, ystart, xend - xstart, yend - ystart);
}
void SetViewportSize(Vector2 s)
{
if (s == mViewPortSize)
return;
mViewPortSize = s;
OnScrollChanged();
}
void AddTheCurrentVisibleRectToTheListOfRegionsToProcess()
{
mVisible = GetVisibleRect();
mVisibleRegions.Clear();
mVisibleRegions.Add(mVisible);
}
void StartLazyUpdate()
{
if (mTimer == null)
{
mTimer = schedule.Execute(OnStartLazyUpdate).Every(5);
}
if (mDelayedUpdateTimer == null)
{
mDelayedUpdateTimer = schedule.Execute(OnDelayedCanvasUpdate).Every(50);
}
mTimer.Resume();
mDelayedUpdateTimer.Resume();
}
void OnStartLazyUpdate()
{
mTimer.Pause();
this.LazyUpdateVisuals();
}
void OnDelayedCanvasUpdate()
{
long ellapsedTimeSinceLastUpdate =
Environment.TickCount - mLastCanvasUpdateTicks;
// It hasn't passed enough time since the last canvas update
// to warn the DelayedCanvasUpdateListener about it.
if (ellapsedTimeSinceLastUpdate < UPDATE_LISTENER_DELAY)
return;
mDelayedUpdateTimer.Pause();
mUpdatedListener.OnDelayedCanvasUpdate();
}
int LazyCreateNodes(int quantum)
{
if (mVisible.width <= 0 || mVisible.height <= 0)
{
mVisible = GetVisibleRect();
mVisibleRegions.Add(mVisible);
mDone = false;
}
int count = 0;
int regionCount = 0;
while (mVisibleRegions.Count > 0 && count < quantum)
{
Rect visibleRegion = mVisibleRegions[0];
mVisibleRegions.RemoveAt(0);
regionCount++;
// Iterate over the visible range of nodes and make sure they have visuals.
foreach (IVirtualChild n in mIndex.GetNodesInside(visibleRegion))
{
if (n.Visual == null ||
n.Visual.userData == null)
{
EnsureVisual(n);
mAdded++;
}
count++;
if (count >= quantum)
{
RegionsUpdater.UpdateRegions(
mVisibleRegions, regionCount, visibleRegion, mExtraSizeAroundEdges);
mDone = false;
break;
}
}
}
return count;
}
// Remove visuals for nodes that are no longer visible.
int LazyRemoveNodes(int quantum)
{
Rect visible = GetVisibleRect();
int count = 0;
// Also remove nodes that are no longer visible.
int regionCount = 0;
while (mDirtyRegions.Count > 0 && count < quantum)
{
int last = mDirtyRegions.Count - 1;
Rect dirtyRegion = mDirtyRegions[last];
mDirtyRegions.RemoveAt(last);
regionCount++;
// Iterate over the visible range of nodes and make sure they have visuals.
foreach (IVirtualChild n in mIndex.GetNodesInside(dirtyRegion))
{
VisualElement e = n.Visual;
if (e != null)
{
Rect nrect = n.Bounds;
if (!nrect.Intersects(visible))
{
e.userData = null;
Remove(e);
n.DisposeVisual();
mRemoved++;
}
}
count++;
if (count >= quantum)
{
RegionsUpdater.UpdateRegions(
mDirtyRegions, regionCount, dirtyRegion, mExtraSizeAroundEdges);
mDone = false;
break;
}
}
}
return count;
}
// Check all child nodes to see if any leaked from LazyRemoveNodes and remove their visuals.
int LazyGarbageCollectNodes(int quantum)
{
int count = 0;
// Now after every update also do a full incremental scan over all the children
// to make sure we didn't leak any nodes that need to be removed.
while (count < quantum && mNodeCollectCycle < childCount)
{
VisualElement e = hierarchy[mNodeCollectCycle];
IVirtualChild n = e.userData as IVirtualChild;
if (n != null)
{
Rect nrect = n.Bounds;
if (!nrect.Intersects(mVisible))
{
e.userData = null;
Remove(e);
n.DisposeVisual();
mRemoved++;
count++;
continue;
}
}
mNodeCollectCycle++;
}
if (mNodeCollectCycle < childCount)
{
mDone = false;
}
return count;
}
// Insert the visual for the child in the correct z-order position.
//
// GetNodesInside returns nodes in spatial order (from QuadTree), not z-order,
// so we use binary search with mVisualPositions to find the correct insertion point.
// This gives O(log M) insertion where M is the number of visible children.
void EnsureVisual(IVirtualChild child)
{
VisualElement e = child.CreateVisual();
e.style.position = Position.Absolute;
e.userData = child;
if (!(e is ParentLinkShape) && !(e is MergeLinkShape))
UpdateVisualBounds(e, child.Bounds);
int position = GetVisualPosition(child);
int insertIndex = FindInsertionIndex(position);
hierarchy.Insert(insertIndex, e);
}
int GetVisualPosition(IVirtualChild child)
{
if (mVisualPositions != null && mVisualPositions.TryGetValue(child, out int position))
return position;
// Fallback: calculate position from z-index if not in dictionary
// Use a high index within the z-level to place at end of that level
VirtualShape virtualShape = child as VirtualShape;
if (virtualShape != null)
{
int zIndex = ZIndex.GetZIndex(virtualShape);
return (zIndex * ZIndex.BASE_MULTIPLIER) + (ZIndex.BASE_MULTIPLIER - 1);
}
// Last resort: place at the end
return int.MaxValue;
}
// Binary search to find the insertion index for an element with the given position.
// Returns the first index where all preceding elements have smaller positions.
// Non-virtual elements (like backdrop) are treated as having position -infinity.
int FindInsertionIndex(int targetPosition)
{
int low = 0;
int high = hierarchy.childCount;
while (low < high)
{
int mid = (low + high) / 2;
VisualElement v = hierarchy[mid];
IVirtualChild n = v.userData as IVirtualChild;
if (n == null)
{
// Non-virtual elements (backdrop) are at the beginning
low = mid + 1;
}
else
{
int midPosition = GetVisualPosition(n);
if (midPosition < targetPosition)
{
low = mid + 1;
}
else
{
high = mid;
}
}
}
return low;
}
static class RegionsUpdater
{
internal static void UpdateRegions(
IList<Rect> regions, int regionCount, Rect region, Vector2 extraSizeAroundEdges)
{
// This region is too big, so subdivide it into smaller slices.
if (regionCount == 1)
{
// We didn't even complete 1 region, so we better split it.
SplitRegion(region, regions, extraSizeAroundEdges);
return;
}
regions.Add(region); // put it back since we're not done!
}
internal static void UpdateDirtyRegions(
IList<Rect> dirtyRegions, Rect dirtyRegion, Rect visibleRegion)
{
Rect intersection = dirtyRegion.Intersect(visibleRegion);
if (intersection.width <= 0 || intersection.height <= 0)
{
dirtyRegions.Add(dirtyRegion); // the whole thing is dirty
return;
}
// Add left stripe
if (dirtyRegion.xMin < intersection.xMin)
{
dirtyRegions.Add(new Rect(
dirtyRegion.xMin, dirtyRegion.yMin,
intersection.xMin - dirtyRegion.xMin, dirtyRegion.height));
}
// Add right stripe
if (dirtyRegion.xMax > intersection.xMax)
{
dirtyRegions.Add(new Rect(
intersection.xMax, dirtyRegion.yMin,
dirtyRegion.xMax - intersection.xMax, dirtyRegion.height));
}
// Add top stripe
if (dirtyRegion.yMin < intersection.yMin)
{
dirtyRegions.Add(new Rect(
dirtyRegion.xMin, dirtyRegion.yMin,
dirtyRegion.width, intersection.yMin - dirtyRegion.yMin));
}
// Add right stripe
if (dirtyRegion.yMax > intersection.yMax)
{
dirtyRegions.Add(new Rect(
dirtyRegion.xMin, intersection.yMax,
dirtyRegion.width, dirtyRegion.yMax - intersection.yMax));
}
}
static void SplitRegion(
Rect region, IList<Rect> regions, Vector2 extraSizeAroundEdges)
{
float minWidth = extraSizeAroundEdges.x * 2;
float minHeight = extraSizeAroundEdges.y * 2;
if (region.width > region.height && region.height > minHeight)
{
// horizontal slices
float h = region.height / 2;
regions.Add(new Rect(region.xMin, region.yMin, region.width, h + 10));
regions.Add(new Rect(region.xMin, region.yMin + h, region.width, h + 10));
return;
}
if (region.width < region.height && region.width > minWidth)
{
// vertical slices
float w = region.width / 2;
regions.Add(new Rect(region.xMin, region.yMin, w + 10, region.height));
regions.Add(new Rect(region.xMin + w, region.yMin, w + 10, region.height));
return;
}
regions.Add(region); // put it back since we're not done!
}
}
bool mIsUpdatingScroll = false;
bool mVisualsUpdated = false;
bool mDone = true;
int mAdded;
int mRemoved;
int mNodeCollectCycle;
long mLastCanvasUpdateTicks;
IVisualElementScheduledItem mTimer;
IVisualElementScheduledItem mDelayedUpdateTimer;
SelfThrottlingWorker mSelfThrottlingWorker;
VisualElement mBackdrop;
Vector2 mViewPortSize;
Vector2 mExtent;
Rect mVisible;
IList<Rect> mVisibleRegions = new List<Rect>();
IList<Rect> mDirtyRegions = new List<Rect>();
QuadTree mIndex;
List<IVirtualChild> mChildren;
IDictionary<IVirtualChild, int> mVisualPositions;
readonly CanvasScrollView mScrollView;
readonly Vector2 mExtraSizeAroundEdges;
readonly IVirtualCanvasUpdateVisualsListener mLoadedListener;
readonly IVirtualCanvasUpdateListener mUpdatedListener;
static readonly int UPDATE_LISTENER_DELAY = 200;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cc8c3a097e504aeabef3c8c2f7669916
timeCreated: 1768317989
@@ -0,0 +1,393 @@
using System;
using System.Collections.Generic;
using Codice.Client.BaseCommands.BranchExplorer;
using Codice.Client.BaseCommands.BranchExplorer.Layout;
using Codice.LogWrapper;
using PlasticGui.WorkspaceWindow.BranchExplorer;
using PlasticGui.WorkspaceWindow.Configuration;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes.Colors;
using UnityEngine;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal static class VirtualCanvasFiller
{
internal static void FillVirtualCanvas(
VirtualCanvas canvas,
BrExLayout layout,
WorkspaceUIConfiguration config,
ColorProvider colorProvider,
AsyncTaskLoader taskLoader,
AsyncUserNameResolver userNameResolver,
AsyncChangesetCommentResolver commentResolver)
{
BuildVirtualShapes(canvas, layout, config, colorProvider, taskLoader, userNameResolver, commentResolver);
canvas.RebuildVisuals();
}
static void BuildVirtualShapes(
VirtualCanvas canvas,
BrExLayout layout,
WorkspaceUIConfiguration config,
ColorProvider colorProvider,
AsyncTaskLoader taskLoader,
AsyncUserNameResolver userNameResolver,
AsyncChangesetCommentResolver commentResolver)
{
int ini = Environment.TickCount;
canvas.SetLayout(layout);
canvas.ClearVirtualChildren();
BuildColumnVirtualShapes(canvas, layout.ColumnDraws, config);
BuildLabelVirtualShapes(canvas, layout.LabelDraws, config, colorProvider);
BuildChangesetVirtualShapes(canvas, layout.ChangesetDraws, config, colorProvider, userNameResolver);
BuildChangesetNumberVirtualShapes(canvas, layout.ChangesetDraws, config);
BuildChangesetCommentVirtualShapes(canvas, layout.ChangesetDraws, config, commentResolver);
BuildChangesetParentLinksVirtualShapes(canvas, layout.ChangesetDraws, config);
BuildBranchParentLinksVirtualShapes(canvas, layout.BranchDraws, config);
BuildLinkVirtualShapes(canvas, layout.LinkDraws, config);
BuildBranchVirtualShapes(canvas, layout.BranchDraws, config, colorProvider, taskLoader);
mLog.DebugFormat("CreateLayout time {0}",
Environment.TickCount - ini);
}
static void BuildColumnVirtualShapes(
VirtualCanvas canvas,
List<ColumnDrawInfo> columnDraws,
WorkspaceUIConfiguration config)
{
foreach (ColumnDrawInfo columnDraw in columnDraws)
{
Rect columnRect = new Rect(columnDraw.Bounds.X, columnDraw.Bounds.Y,
columnDraw.Bounds.Width, columnDraw.Bounds.Height);
canvas.AddVirtualChild(new VirtualShape(
columnDraw,
columnRect,
ShapeType.ColumnHeader,
config));
canvas.AddVirtualChild(new VirtualShape(
columnDraw,
columnRect,
ShapeType.Column,
config));
}
}
static void BuildChangesetVirtualShapes(
VirtualCanvas canvas,
List<ChangesetDrawInfo> changesetDraws,
WorkspaceUIConfiguration config,
ColorProvider colorProvider,
AsyncUserNameResolver userNameResolver)
{
foreach (ChangesetDrawInfo changesetDraw in changesetDraws)
{
VirtualShape virtualShape = new VirtualShape(
changesetDraw,
BuildRect(changesetDraw.Bounds),
ShapeType.Changeset,
config,
colorProvider,
null,
userNameResolver);
changesetDraw.Visual = virtualShape;
canvas.AddVirtualChild(virtualShape);
}
}
static void BuildChangesetNumberVirtualShapes(
VirtualCanvas canvas,
List<ChangesetDrawInfo> changesetDraws,
WorkspaceUIConfiguration config)
{
foreach (ChangesetDrawInfo changesetDraw in changesetDraws)
{
string text = ChangesetNumberShape.GetNumberText(changesetDraw);
if (string.IsNullOrEmpty(text))
continue;
Vector2 box = ChangesetNumberShape.MeasureNumberBoxSize(text);
if (box.x <= 0f || box.y <= 0f)
continue;
BrExRectangle csBounds = changesetDraw.Bounds;
Rect numberRect = new Rect(
csBounds.X + (csBounds.Width - box.x) / 2f,
csBounds.Y - NumberVerticalMargin - box.y,
box.x,
box.y);
canvas.AddVirtualChild(new VirtualShape(
changesetDraw,
numberRect,
ShapeType.ChangesetNumber,
config));
}
}
static void BuildChangesetCommentVirtualShapes(
VirtualCanvas canvas,
List<ChangesetDrawInfo> changesetDraws,
WorkspaceUIConfiguration config,
AsyncChangesetCommentResolver commentResolver)
{
foreach (ChangesetDrawInfo changesetDraw in changesetDraws)
{
Rect commentRect = BuildCommentRect(changesetDraw.Bounds);
VirtualShape commentShape = new VirtualShape(
changesetDraw,
commentRect,
ShapeType.ChangesetComment,
config,
commentResolver: commentResolver);
canvas.AddVirtualChild(commentShape);
}
}
static Rect BuildCommentRect(BrExRectangle csBounds)
{
return new Rect(
csBounds.X,
csBounds.Y + csBounds.Height + CommentVerticalMargin,
csBounds.Width,
CommentHeight);
}
static void BuildBranchVirtualShapes(
VirtualCanvas canvas,
List<BranchDrawInfo> branchDraws,
WorkspaceUIConfiguration config,
ColorProvider colorProvider,
AsyncTaskLoader taskLoader)
{
foreach (BranchDrawInfo branchDraw in branchDraws)
{
VirtualShape branchShape = new VirtualShape(
branchDraw,
BuildRect(BuildBoundsWithSubBranches(branchDraw)),
ShapeType.Branch,
config,
colorProvider);
branchDraw.Visual = branchShape;
VirtualShape captionShape = new VirtualShape(
branchDraw,
BuildRect(BuildBoundsWithSubBranches(branchDraw)),
ShapeType.BranchCaption,
config,
null,
taskLoader);
canvas.AddVirtualChild(branchShape);
canvas.AddVirtualChild(captionShape);
}
}
static void BuildLabelVirtualShapes(
VirtualCanvas canvas,
List<LabelDrawInfo> labelDraws,
WorkspaceUIConfiguration config,
ColorProvider colorProvider)
{
foreach (LabelDrawInfo labelDraw in labelDraws)
{
VirtualShape virtualShape = new VirtualShape(
labelDraw,
BuildRect(labelDraw.Bounds),
ShapeType.Label,
config,
colorProvider);
labelDraw.Visual = virtualShape;
canvas.AddVirtualChild(virtualShape);
}
}
static void BuildChangesetParentLinksVirtualShapes(
VirtualCanvas canvas,
List<ChangesetDrawInfo> commitDraws,
WorkspaceUIConfiguration config)
{
List<ChangesetDrawInfo> parentCommitLinksOnSameBranch;
List<ChangesetDrawInfo> parentCommitLinksOnDifferentBranch;
GetParentChangesetLinks(
commitDraws,
config,
out parentCommitLinksOnSameBranch,
out parentCommitLinksOnDifferentBranch);
parentCommitLinksOnDifferentBranch.Sort(
new ChangesetDrawInfoByBranchRowDescComparer());
BuildParentLinkVirtualShapes(
canvas,
parentCommitLinksOnSameBranch,
config);
BuildParentLinkVirtualShapes(
canvas,
parentCommitLinksOnDifferentBranch,
config);
}
static void BuildParentLinkVirtualShapes(
VirtualCanvas canvas,
List<ChangesetDrawInfo> changesetDraws,
WorkspaceUIConfiguration config)
{
foreach (ChangesetDrawInfo changesetDraw in changesetDraws)
{
ChangesetDrawInfo parentDraw = (config.DisplayOptions.DisplayOnlyRelevantChangesets) ?
changesetDraw.RelevantParent :
changesetDraw.Parent;
if (parentDraw == null)
continue;
BrExRectangle rectangle = BrExConnectionRectangle.CreateConnectionRectangle(
changesetDraw.Bounds, parentDraw.Bounds);
VirtualShape parentLinkShape = new ChangesetParentLinkVirtualShape(
changesetDraw,
BuildRect(rectangle),
config);
changesetDraw.ParentLinkVisual = parentLinkShape;
canvas.AddVirtualChild(parentLinkShape);
}
}
static void BuildBranchParentLinksVirtualShapes(
VirtualCanvas canvas,
List<BranchDrawInfo> branchDraws,
WorkspaceUIConfiguration config)
{
foreach (BranchDrawInfo branchDraw in branchDraws)
{
BrExBranch explorerBranch = branchDraw.Tag as BrExBranch;
if (!explorerBranch.IsEmpty())
continue;
if (branchDraw.HeadChangeset == null)
continue;
BrExRectangle rectangle = BrExConnectionRectangle.CreateConnectionRectangle(
branchDraw.Bounds, branchDraw.HeadChangeset.Bounds);
canvas.AddVirtualChild(new VirtualShape(
branchDraw,
BuildRect(rectangle),
ShapeType.BranchParentLink,
config));
}
}
static void BuildLinkVirtualShapes(
VirtualCanvas canvas,
List<LinkDrawInfo> linkDraws,
WorkspaceUIConfiguration config)
{
foreach (LinkDrawInfo linkDraw in linkDraws)
{
BrExRectangle rectangle = BrExConnectionRectangle.CreateConnectionRectangle(
linkDraw.SourceChangeset.Bounds, linkDraw.DestinationChangeset.Bounds);
VirtualShape virtualShape = new VirtualShape(
linkDraw,
BuildRect(rectangle),
ShapeType.MergeLink,
config);
linkDraw.Visual = virtualShape;
canvas.AddVirtualChild(virtualShape);
}
}
static Rect BuildRect(BrExRectangle rect)
{
return new Rect(rect.X, rect.Y, rect.Width, rect.Height);
}
static BrExRectangle BuildBoundsWithSubBranches(
BranchDrawInfo branchDraw)
{
if (branchDraw.SubBranchContainers == null ||
branchDraw.SubBranchContainers.Length == 0)
return branchDraw.Bounds;
int maxBottom = branchDraw.Bounds.Bottom;
foreach (var container in branchDraw.SubBranchContainers)
maxBottom = Math.Max(maxBottom, container.Bounds.Bottom);
return new BrExRectangle(
branchDraw.Bounds.X,
branchDraw.Bounds.Y,
branchDraw.Bounds.Width,
maxBottom - branchDraw.Bounds.Y + BrExDrawProperties.ChangesetRadius);
}
static void GetParentChangesetLinks(
List<ChangesetDrawInfo> changesetDraws,
WorkspaceUIConfiguration config,
out List<ChangesetDrawInfo> parentChangesetLinksOnSameBranch,
out List<ChangesetDrawInfo> parentChangesetLinksOnDifferentBranch)
{
parentChangesetLinksOnSameBranch = new List<ChangesetDrawInfo>();
parentChangesetLinksOnDifferentBranch = new List<ChangesetDrawInfo>();
foreach (ChangesetDrawInfo changesetDraw in changesetDraws)
{
ChangesetDrawInfo parentDraw = (config.DisplayOptions.DisplayOnlyRelevantChangesets) ?
changesetDraw.RelevantParent :
changesetDraw.Parent;
if (parentDraw == null)
continue;
if (changesetDraw.Branch == parentDraw.Branch)
{
parentChangesetLinksOnSameBranch.Add(changesetDraw);
continue;
}
parentChangesetLinksOnDifferentBranch.Add(changesetDraw);
}
}
class ChangesetDrawInfoByBranchRowDescComparer : IComparer<ChangesetDrawInfo>
{
int IComparer<ChangesetDrawInfo>.Compare(ChangesetDrawInfo xChangeset, ChangesetDrawInfo yChangeset)
{
if (xChangeset == null && yChangeset == null)
return 0;
if (xChangeset == null)
return -1;
if (yChangeset == null)
return 1;
if (xChangeset.Equals(yChangeset))
return 0;
if (xChangeset.Branch.Row == yChangeset.Branch.Row)
return 0;
return xChangeset.Branch.Row > yChangeset.Branch.Row ? -1 : 1;
}
}
const int NumberVerticalMargin = 4;
const int CommentVerticalMargin = 4;
const int CommentHeight = 24;
static readonly ILog mLog = LogManager.GetLogger("VirtualCanvasFiller");
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5b7531dfaf9b4de9ab2be2bbad7f83ad
timeCreated: 1768387348
@@ -0,0 +1,187 @@
using UnityEngine;
using Codice.Client.BaseCommands.BranchExplorer;
using UnityEngine.UIElements;
using PlasticGui.WorkspaceWindow.BranchExplorer;
using PlasticGui.WorkspaceWindow.Configuration;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes.Colors;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal class VirtualShape : IVirtualChild, IVirtualShape
{
internal VirtualShape(
ObjectDrawInfo drawInfo,
Rect bounds,
ShapeType shapeType,
WorkspaceUIConfiguration config,
ColorProvider colorProvider = null,
AsyncTaskLoader taskLoader = null,
AsyncUserNameResolver userNameResolver = null,
AsyncChangesetCommentResolver commentResolver = null)
{
mDrawInfo = drawInfo;
mShapeType = shapeType;
Bounds = bounds;
mConfig = config;
mTaskLoader = taskLoader;
mUserNameResolver = userNameResolver;
mColorProvider = colorProvider;
mCommentResolver = commentResolver;
}
internal Rect Bounds
{
get { return mBounds; }
set { mBounds = value; }
}
internal VisualElement Visual
{
get { return mVisual; }
set { mVisual = (BrExShape)value; }
}
internal Rect BoundsForNavigation
{
get
{
return (ShapeType != ShapeType.Branch) ? Bounds :
new Rect(
Bounds.x + Bounds.width - BrExDrawProperties.ChangesetWidth,
Bounds.y, BrExDrawProperties.ChangesetWidth, Bounds.height);
}
}
public bool IsSelected
{
get { return mIsSelected; }
set
{
mIsSelected = value;
if (mVisual == null)
return;
mVisual.IsSelected = value;
}
}
public bool IsSearchResult
{
get { return mIsSearchResult; }
set
{
mIsSearchResult = value;
if (mVisual == null)
return;
mVisual.IsSearchResult = value;
}
}
public bool IsCurrentSearchResult
{
get { return mIsCurrentSearchResult; }
set
{
mIsCurrentSearchResult = value;
if (mVisual == null)
return;
mVisual.IsCurrentSearchResult = value;
}
}
internal bool IsLinkNavigationTarget
{
get { return mIsLinkNavigationTarget; }
set
{
mIsLinkNavigationTarget = value;
if (mVisual == null)
return;
mVisual.IsLinkNavigationTarget = value;
}
}
public ShapeType ShapeType
{
get { return mShapeType; }
}
public ObjectDrawInfo DrawInfo
{
get { return mDrawInfo; }
}
internal WorkspaceUIConfiguration Config
{
get { return mConfig; }
}
Rect IVirtualChild.Bounds
{
get { return Bounds; }
}
VisualElement IVirtualChild.Visual
{
get { return Visual; }
}
VisualElement IVirtualChild.CreateVisual()
{
if (mVisual != null)
return mVisual;
mVisual = VisualShapeFactory.CreateVisual(
this,
mConfig,
mColorProvider,
mTaskLoader,
mUserNameResolver,
mCommentResolver);
return mVisual;
}
void IVirtualChild.DisposeVisual()
{
if (mVisual != null)
mVisual.Dispose();
mVisual = null;
}
void IVirtualShape.InvalidateVisual()
{
if (mVisual == null)
return;
mVisual.Redraw();
}
protected BrExShape mVisual;
bool mIsSelected;
bool mIsSearchResult;
bool mIsCurrentSearchResult;
bool mIsLinkNavigationTarget;
Rect mBounds;
readonly ObjectDrawInfo mDrawInfo;
readonly ShapeType mShapeType;
readonly WorkspaceUIConfiguration mConfig;
readonly AsyncTaskLoader mTaskLoader;
readonly AsyncUserNameResolver mUserNameResolver;
readonly AsyncChangesetCommentResolver mCommentResolver;
readonly ColorProvider mColorProvider;
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21e4725fe8214df78113a02c68fff633
timeCreated: 1768318272
@@ -0,0 +1,134 @@
using System;
using Codice.Client.BaseCommands.BranchExplorer;
using PlasticGui.WorkspaceWindow.BranchExplorer;
using PlasticGui.WorkspaceWindow.Configuration;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes.Colors;
using Unity.PlasticSCM.Editor.Views.BranchExplorer.Drawing.Shapes.Connections;
namespace Unity.PlasticSCM.Editor.Views.BranchExplorer.Virtualization
{
internal static class VisualShapeFactory
{
internal static BrExShape CreateVisual(
VirtualShape shape,
WorkspaceUIConfiguration configuration,
ColorProvider colorProvider,
AsyncTaskLoader taskLoader,
AsyncUserNameResolver userNameResolver,
AsyncChangesetCommentResolver commentResolver)
{
switch (shape.ShapeType)
{
case ShapeType.ColumnHeader:
return CreateColumnHeaderShape(shape);
case ShapeType.Column:
return CreateColumnShape(shape);
case ShapeType.Changeset:
return CreateChangesetShape(shape, colorProvider, userNameResolver);
case ShapeType.Branch:
return CreateBranchShape(shape, colorProvider);
case ShapeType.BranchCaption:
return CreateBranchCaptionShape(shape, taskLoader);
case ShapeType.Label:
return CreateLabelShape(shape, colorProvider);
case ShapeType.ChangesetComment:
return CreateChangesetCommentShape(shape, commentResolver);
case ShapeType.ChangesetNumber:
return CreateChangesetNumberShape(shape);
case ShapeType.ChangesetParentLink:
return CreateChangesetParentLinkShape(
shape,
configuration.DisplayOptions.DisplayOnlyRelevantChangesets);
case ShapeType.BranchParentLink:
return CreateBranchParentLinkShape(shape);
case ShapeType.MergeLink:
return CreateMergeLinkShape(shape);
}
return null;
}
static BrExShape CreateColumnHeaderShape(VirtualShape shape)
{
return new ColumnHeaderShape(shape);
}
static BrExShape CreateColumnShape(VirtualShape shape)
{
return new ColumnShape(shape);
}
static BrExShape CreateChangesetShape(
VirtualShape shape,
ColorProvider colorProvider,
AsyncUserNameResolver userNameResolver)
{
return new ChangesetShape(shape, colorProvider, userNameResolver);
}
static BrExShape CreateBranchShape(VirtualShape shape, ColorProvider colorProvider)
{
return new BranchShape(shape, colorProvider);
}
static BrExShape CreateBranchCaptionShape(VirtualShape shape, AsyncTaskLoader taskLoader)
{
return new BranchCaptionShape(shape, taskLoader);
}
static BrExShape CreateLabelShape(VirtualShape shape, ColorProvider colorProvider)
{
return new LabelShape(shape, colorProvider);
}
static BrExShape CreateChangesetCommentShape(
VirtualShape shape,
AsyncChangesetCommentResolver commentResolver)
{
return new ChangesetCommentShape(shape, commentResolver);
}
static BrExShape CreateChangesetNumberShape(VirtualShape shape)
{
return new ChangesetNumberShape(shape);
}
static BrExShape CreateChangesetParentLinkShape(
VirtualShape shape,
bool bDisplayOnlyRelevantCommits)
{
ChangesetDrawInfo changesetDrawInfo = (ChangesetDrawInfo)shape.DrawInfo;
ChangesetDrawInfo parentDrawInfo = (bDisplayOnlyRelevantCommits) ?
changesetDrawInfo.RelevantParent :
changesetDrawInfo.Parent;
return ShapeConnectionBuilder.BuildParentLinkConnection(
shape,
(VirtualShape)changesetDrawInfo.Visual,
(VirtualShape)parentDrawInfo.Visual,
parentDrawInfo != changesetDrawInfo.Parent);
}
static BrExShape CreateBranchParentLinkShape(VirtualShape shape)
{
BranchDrawInfo branchDrawInfo = (BranchDrawInfo)shape.DrawInfo;
return ShapeConnectionBuilder.BuildParentLinkConnection(
shape,
(VirtualShape)branchDrawInfo.Visual,
(VirtualShape)branchDrawInfo.HeadChangeset.Visual,
false);
}
static BrExShape CreateMergeLinkShape(VirtualShape shape)
{
LinkDrawInfo linkDrawInfo = (LinkDrawInfo)shape.DrawInfo;
return ShapeConnectionBuilder.BuildMergeLinkConnection(shape,
(VirtualShape)linkDrawInfo.SourceChangeset.Visual,
(VirtualShape)linkDrawInfo.DestinationChangeset.Visual);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: be0d618f3c51404cb94628b6b6cdc8d7
timeCreated: 1768387710