Files

191 lines
6.0 KiB
C#
Raw Permalink Normal View History

2026-01-08 16:50:20 +00:00
using System.IO;
using UnityEngine;
namespace Unity.PlasticSCM.Editor.UI
{
internal interface ICalcTextSize
{
float GetTextWidth(string text);
}
internal class CalcTextSize : ICalcTextSize
{
internal static ICalcTextSize FromStyle(GUIStyle style)
{
return new CalcTextSize(style);
}
CalcTextSize(GUIStyle style)
{
mStyle = style;
}
float ICalcTextSize.GetTextWidth(string text)
{
return mStyle.CalcSize(new GUIContent(text)).x;
}
readonly GUIStyle mStyle;
}
internal static class PathTrimming
{
/// <summary>
/// Truncates a path to fit within the available width using binary search.
/// Always preserves the beginning and end of the path when truncating.
/// </summary>
internal static string TruncatePath(
string path,
float availableWidth,
ICalcTextSize calcTextSize,
out bool wasTrimmed)
{
wasTrimmed = false;
if (availableWidth <= 0)
return string.Empty;
float fullWidth = calcTextSize.GetTextWidth(path);
// Path fits completely
if (fullWidth <= availableWidth)
return path;
wasTrimmed = true;
// Binary search for the optimal character count
int minChars = 1;
int maxChars = path.Length;
int bestLength = 0;
string bestFit = string.Empty;
while (minChars <= maxChars)
{
int midLength = (minChars + maxChars) / 2;
string truncated = TruncatePathByLength(path, midLength);
float width = calcTextSize.GetTextWidth(truncated);
if (width <= availableWidth)
{
// This fits, try to fit more
bestLength = midLength;
bestFit = truncated;
minChars = midLength + 1;
continue;
}
// Too wide, try less
maxChars = midLength - 1;
}
// If we couldn't fit anything reasonable, ensure at least some content is shown
if (bestLength == 0 || string.IsNullOrEmpty(bestFit))
{
// Show at least part of the filename
int lastSeparator = FindLastSeparator(path);
if (lastSeparator != -1)
{
string filename = path.Substring(lastSeparator + 1);
// Try to fit just the filename with middle truncation
minChars = 1;
maxChars = filename.Length;
while (minChars <= maxChars)
{
int midLength = (minChars + maxChars) / 2;
string truncated = TruncateMid(filename, midLength);
float width = calcTextSize.GetTextWidth(truncated);
if (width <= availableWidth)
{
bestFit = truncated;
minChars = midLength + 1;
continue;
}
maxChars = midLength - 1;
}
}
else
{
// No path separator, just truncate the middle
bestFit = TruncateMid(path, 3);
}
}
return bestFit;
}
/// <summary>
/// Finds the last occurrence of either forward slash or backslash separator.
/// Works cross-platform for both Windows and Unix-style paths.
/// </summary>
static int FindLastSeparator(string path)
{
int lastBackslash = path.LastIndexOf('\\');
int lastForwardSlash = path.LastIndexOf('/');
return Mathf.Max(lastBackslash, lastForwardSlash);
}
/// <summary>
/// Truncates the given string to the number of characters given by the length parameter.
/// The value is truncated (if necessary) by removing characters from the middle of the
/// string and inserting an ellipsis in their place.
/// </summary>
static string TruncateMid(string value, int length)
{
if (value.Length <= length)
return value;
if (length <= 0)
return string.Empty;
if (length == 1)
return ELLIPSIS;
int mid = (length - 1) / 2;
string pre = value.Substring(0, Mathf.FloorToInt(mid));
string post = value.Substring(value.Length - Mathf.CeilToInt(mid));
return string.Concat(pre, ELLIPSIS, post);
}
/// <summary>
/// String truncation for paths.
/// This method takes a path and returns it truncated (if necessary) to the exact
/// number of characters specified by the length parameter.
/// </summary>
static string TruncatePathByLength(string path, int length)
{
if (path.Length <= length)
return path;
if (length <= 0)
return string.Empty;
if (length == 1)
return ELLIPSIS;
int lastSeparator = FindLastSeparator(path);
// No directory prefix, fall back to middle ellipsis
if (lastSeparator == -1)
return TruncateMid(path, length);
int filenameLength = path.Length - lastSeparator - 1;
// File name prefixed with …/ would be too long, fall back to middle ellipsis
if (filenameLength + 2 > length)
return TruncateMid(path, length);
string pre = path.Substring(0, length - filenameLength - 2);
string post = path.Substring(lastSeparator);
return string.Concat(pre, ELLIPSIS, post);
}
const string ELLIPSIS = "…";
}
}