Files
2025-11-25 07:14:59 +09:00

1007 lines
34 KiB
C#

using System;
using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using LightlessSync.UI;
// imagine putting this goober name here
namespace LightlessSync.UI.Style;
public enum SeluneGradientMode
{
Vertical,
Horizontal,
Both,
}
public enum SeluneHighlightMode
{
Horizontal,
Vertical,
Both,
Point,
}
public sealed class SeluneGradientSettings
{
public Vector4 GradientColor { get; init; } = UIColors.Get("LightlessPurple");
public Vector4? HighlightColor { get; init; }
public float GradientPeakOpacity { get; init; } = 0.07f;
public float HighlightPeakAlpha { get; init; } = 0.13f;
public float HighlightEdgeAlpha { get; init; } = 0f;
public float HighlightMidpoint { get; init; } = 0.45f;
public float MinimumHighlightHalfHeight { get; init; } = 25f;
public float MinimumHighlightHalfWidth { get; init; } = 25f;
public float HighlightFadeInSpeed { get; init; } = 14f;
public float HighlightFadeOutSpeed { get; init; } = 8f;
public float HighlightBorderThickness { get; init; } = 10f;
public float HighlightBorderRounding { get; init; } = 8f;
public SeluneGradientMode BackgroundMode { get; init; } = SeluneGradientMode.Vertical;
public SeluneHighlightMode HighlightMode { get; init; } = SeluneHighlightMode.Horizontal;
public static SeluneGradientSettings Default
=> new()
{
GradientColor = UIColors.Get("LightlessPurple"),
HighlightColor = UIColors.Get("LightlessPurple"),
};
}
public static class Selune
{
[ThreadStatic] private static SeluneCanvas? _activeCanvas;
public static SeluneCanvas Begin(SeluneBrush brush, ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, SeluneGradientSettings? settings = null)
{
var canvas = new SeluneCanvas(brush, drawList, windowPos, windowSize, settings ?? SeluneGradientSettings.Default, _activeCanvas);
_activeCanvas = canvas;
return canvas;
}
internal static void Release(SeluneCanvas canvas)
{
if (_activeCanvas == canvas)
_activeCanvas = canvas.Previous;
}
public static void RegisterHighlight(
Vector2 rectMin,
Vector2 rectMax,
SeluneHighlightMode? modeOverride = null,
bool borderOnly = false,
float? borderThicknessOverride = null,
bool exactSize = false,
bool clipToElement = false,
Vector2? clipPadding = null,
float? roundingOverride = null,
bool spanFullWidth = false,
Vector4? highlightColorOverride = null,
float? highlightAlphaOverride = null)
=> _activeCanvas?.RegisterHighlight(
rectMin,
rectMax,
modeOverride,
borderOnly,
borderThicknessOverride,
exactSize,
clipToElement,
clipPadding,
roundingOverride,
spanFullWidth,
highlightColorOverride,
highlightAlphaOverride);
}
public sealed class SeluneBrush
{
private Vector2? _highlightCenter;
private Vector2 _highlightHalfSize;
private SeluneHighlightMode _highlightMode;
private bool _highlightBorderOnly;
private float _borderThickness;
private bool _useClipRect;
private Vector2 _clipMin;
private Vector2 _clipMax;
private float _highlightRounding;
private bool _highlightUsedThisFrame;
private float _highlightIntensity;
private Vector4? _highlightColorOverride;
private float? _highlightAlphaOverride;
internal void BeginFrame()
{
_highlightUsedThisFrame = false;
}
internal void RegisterHighlight(Vector2 center, Vector2 halfSize, SeluneHighlightMode mode, bool borderOnly, float borderThickness, bool useClipRect, Vector2 clipMin, Vector2 clipMax, float rounding, Vector4? highlightColorOverride, float? highlightAlphaOverride)
{
if (halfSize.X <= 0f || halfSize.Y <= 0f)
return;
_highlightUsedThisFrame = true;
_highlightCenter = center;
_highlightHalfSize = halfSize;
_highlightMode = mode;
_highlightBorderOnly = borderOnly;
_borderThickness = borderOnly ? Math.Max(borderThickness, 0f) : 0f;
_useClipRect = useClipRect;
_clipMin = clipMin;
_clipMax = clipMax;
_highlightRounding = rounding;
_highlightColorOverride = highlightColorOverride;
_highlightAlphaOverride = highlightAlphaOverride;
}
internal void UpdateFade(float deltaTime, SeluneGradientSettings settings)
{
if (deltaTime <= 0f)
return;
if (_highlightUsedThisFrame)
{
_highlightIntensity = MathF.Min(1f, _highlightIntensity + deltaTime * settings.HighlightFadeInSpeed);
}
else
{
_highlightIntensity = MathF.Max(0f, _highlightIntensity - deltaTime * settings.HighlightFadeOutSpeed);
if (_highlightIntensity <= 0.001f)
{
ResetHighlightState();
}
}
}
internal SeluneHighlightRenderState GetRenderState()
=> new(
_highlightCenter,
_highlightHalfSize,
_highlightMode,
_highlightBorderOnly,
_borderThickness,
_useClipRect,
_clipMin,
_clipMax,
_highlightRounding,
_highlightIntensity,
_highlightColorOverride,
_highlightAlphaOverride);
private void ResetHighlightState()
{
_highlightCenter = null;
_highlightHalfSize = Vector2.Zero;
_highlightBorderOnly = false;
_borderThickness = 0f;
_useClipRect = false;
_highlightRounding = 0f;
_highlightColorOverride = null;
_highlightAlphaOverride = null;
}
}
internal readonly struct SeluneHighlightRenderState
{
public SeluneHighlightRenderState(
Vector2? center,
Vector2 halfSize,
SeluneHighlightMode mode,
bool borderOnly,
float borderThickness,
bool useClipRect,
Vector2 clipMin,
Vector2 clipMax,
float rounding,
float intensity,
Vector4? colorOverride,
float? alphaOverride)
{
Center = center;
HalfSize = halfSize;
Mode = mode;
BorderOnly = borderOnly;
BorderThickness = borderThickness;
UseClipRect = useClipRect;
ClipMin = clipMin;
ClipMax = clipMax;
Rounding = rounding;
Intensity = intensity;
ColorOverride = colorOverride;
AlphaOverride = alphaOverride;
}
public Vector2? Center { get; }
public Vector2 HalfSize { get; }
public SeluneHighlightMode Mode { get; }
public bool BorderOnly { get; }
public float BorderThickness { get; }
public bool UseClipRect { get; }
public Vector2 ClipMin { get; }
public Vector2 ClipMax { get; }
public float Rounding { get; }
public float Intensity { get; }
public Vector4? ColorOverride { get; }
public float? AlphaOverride { get; }
public bool HasHighlight => Center.HasValue && HalfSize.X > 0f && HalfSize.Y > 0f && Intensity > 0.001f;
}
public sealed class SeluneCanvas : IDisposable
{
private readonly SeluneBrush _brush;
private readonly ImDrawListPtr _drawList;
private readonly Vector2 _windowPos;
private readonly Vector2 _windowSize;
private readonly SeluneGradientSettings _settings;
private bool _fadeUpdatedThisFrame;
internal SeluneCanvas? Previous { get; }
internal SeluneCanvas(SeluneBrush brush, ImDrawListPtr drawList, Vector2 windowPos, Vector2 windowSize, SeluneGradientSettings settings, SeluneCanvas? previous)
{
_brush = brush;
_drawList = drawList;
_windowPos = windowPos;
_windowSize = windowSize;
_settings = settings;
Previous = previous;
_fadeUpdatedThisFrame = false;
_brush.BeginFrame();
}
public void DrawGradient(float gradientTopY, float gradientBottomY, float deltaTime)
{
DrawInternal(gradientTopY, gradientBottomY, deltaTime, true, true);
}
public void DrawHighlightOnly(float gradientTopY, float gradientBottomY, float deltaTime)
{
DrawInternal(gradientTopY, gradientBottomY, deltaTime, false, true);
}
public void DrawHighlightOnly(float deltaTime)
=> DrawHighlightOnly(_windowPos.Y, _windowPos.Y + _windowSize.Y, deltaTime);
public void Animate(float deltaTime)
{
UpdateFadeOnce(deltaTime);
}
private void UpdateFadeOnce(float deltaTime)
{
if (_fadeUpdatedThisFrame)
return;
_brush.UpdateFade(deltaTime, _settings);
_fadeUpdatedThisFrame = true;
}
private void DrawInternal(float gradientTopY, float gradientBottomY, float deltaTime, bool drawBackground, bool drawHighlight)
{
UpdateFadeOnce(deltaTime);
SeluneRenderer.DrawGradient(
_drawList,
_windowPos,
_windowSize,
gradientTopY,
gradientBottomY,
_brush.GetRenderState(),
_settings,
drawBackground,
drawHighlight);
}
internal void RegisterHighlight(
Vector2 rectMin,
Vector2 rectMax,
SeluneHighlightMode? modeOverride,
bool borderOnly,
float? borderThicknessOverride,
bool exactSize,
bool clipToElement,
Vector2? clipPadding,
float? roundingOverride,
bool spanFullWidth,
Vector4? highlightColorOverride,
float? highlightAlphaOverride)
{
if (spanFullWidth)
{
rectMin.X = _windowPos.X;
rectMax.X = _windowPos.X + _windowSize.X;
}
var size = rectMax - rectMin;
if (size.X <= 0f || size.Y <= 0f)
return;
var center = rectMin + size * 0.5f;
var halfWidth = exactSize ? size.X * 0.5f : Math.Max(size.X * 0.5f, _settings.MinimumHighlightHalfWidth * ImGuiHelpers.GlobalScale);
var halfHeight = exactSize ? size.Y * 0.5f : Math.Max(size.Y * 0.5f, _settings.MinimumHighlightHalfHeight * ImGuiHelpers.GlobalScale);
var mode = modeOverride ?? _settings.HighlightMode;
var thickness = borderOnly ? (borderThicknessOverride ?? _settings.HighlightBorderThickness) * ImGuiHelpers.GlobalScale : 0f;
var useClip = clipToElement;
var padding = clipPadding ?? (borderOnly && thickness > 0f
? new Vector2(thickness)
: Vector2.Zero);
var clipMin = rectMin - padding;
var clipMax = rectMax + padding;
var rounding = (roundingOverride ?? _settings.HighlightBorderRounding) * ImGuiHelpers.GlobalScale;
_brush.RegisterHighlight(center, new Vector2(halfWidth, halfHeight), mode, borderOnly, thickness, useClip, clipMin, clipMax, rounding, highlightColorOverride, highlightAlphaOverride);
_fadeUpdatedThisFrame = false;
}
public void Dispose()
=> Selune.Release(this);
}
// i wonder which sync will copy this shitty code now
internal static class SeluneRenderer
{
public static void DrawGradient(
ImDrawListPtr drawList,
Vector2 windowPos,
Vector2 windowSize,
float gradientTopY,
float gradientBottomY,
SeluneHighlightRenderState highlightState,
SeluneGradientSettings settings,
bool drawBackground = true,
bool drawHighlight = true)
{
var gradientLeft = windowPos.X;
var gradientRight = windowPos.X + windowSize.X;
var windowBottomY = windowPos.Y + windowSize.Y;
var clampedTopY = MathF.Max(gradientTopY, windowPos.Y);
var clampedBottomY = MathF.Min(gradientBottomY, windowBottomY);
if (clampedBottomY <= clampedTopY)
return;
var color = settings.GradientColor;
var topColorVec = new Vector4(color.X, color.Y, color.Z, 0f);
var bottomColorVec = new Vector4(color.X, color.Y, color.Z, 0f);
var midColorVec = new Vector4(color.X, color.Y, color.Z, settings.GradientPeakOpacity);
if (drawBackground)
{
DrawBackground(
drawList,
gradientLeft,
gradientRight,
clampedTopY,
clampedBottomY,
topColorVec,
midColorVec,
bottomColorVec,
settings.BackgroundMode);
}
if (!drawHighlight)
return;
DrawHighlight(
drawList,
gradientLeft,
gradientRight,
clampedTopY,
clampedBottomY,
highlightState,
settings);
}
private static void DrawBackground(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector4 topColorVec,
Vector4 midColorVec,
Vector4 bottomColorVec,
SeluneGradientMode mode)
{
switch (mode)
{
case SeluneGradientMode.Vertical:
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
break;
case SeluneGradientMode.Horizontal:
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
break;
case SeluneGradientMode.Both:
DrawVerticalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
DrawHorizontalBackground(drawList, gradientLeft, gradientRight, clampedTopY, clampedBottomY, topColorVec, midColorVec, bottomColorVec);
break;
}
}
private static void DrawVerticalBackground(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector4 topColorVec,
Vector4 midColorVec,
Vector4 bottomColorVec)
{
var topColor = ImGui.ColorConvertFloat4ToU32(topColorVec);
var midColor = ImGui.ColorConvertFloat4ToU32(midColorVec);
var bottomColor = ImGui.ColorConvertFloat4ToU32(bottomColorVec);
var midY = clampedTopY + (clampedBottomY - clampedTopY) * 0.035f;
drawList.AddRectFilledMultiColor(
new Vector2(gradientLeft, clampedTopY),
new Vector2(gradientRight, midY),
topColor,
topColor,
midColor,
midColor);
drawList.AddRectFilledMultiColor(
new Vector2(gradientLeft, midY),
new Vector2(gradientRight, clampedBottomY),
midColor,
midColor,
bottomColor,
bottomColor);
}
private static void DrawHorizontalBackground(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector4 leftColorVec,
Vector4 midColorVec,
Vector4 rightColorVec)
{
var leftColor = ImGui.ColorConvertFloat4ToU32(leftColorVec);
var midColor = ImGui.ColorConvertFloat4ToU32(midColorVec);
var rightColor = ImGui.ColorConvertFloat4ToU32(rightColorVec);
var midX = gradientLeft + (gradientRight - gradientLeft) * 0.035f;
drawList.AddRectFilledMultiColor(
new Vector2(gradientLeft, clampedTopY),
new Vector2(midX, clampedBottomY),
leftColor,
midColor,
midColor,
leftColor);
drawList.AddRectFilledMultiColor(
new Vector2(midX, clampedTopY),
new Vector2(gradientRight, clampedBottomY),
midColor,
rightColor,
rightColor,
midColor);
}
private static void DrawHighlight(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
SeluneHighlightRenderState highlightState,
SeluneGradientSettings settings)
{
if (!highlightState.HasHighlight)
return;
var highlightColor = highlightState.ColorOverride ?? settings.HighlightColor ?? settings.GradientColor;
var clampedIntensity = Math.Clamp(highlightState.Intensity, 0f, 1f);
var alphaScale = Math.Clamp(highlightState.AlphaOverride ?? 1f, 0f, 1f);
var peakAlpha = settings.HighlightPeakAlpha * clampedIntensity * alphaScale;
var edgeAlpha = settings.HighlightEdgeAlpha * clampedIntensity * alphaScale;
if (peakAlpha <= 0f && edgeAlpha <= 0f)
return;
var highlightEdgeVec = new Vector4(highlightColor.X, highlightColor.Y, highlightColor.Z, edgeAlpha);
var highlightPeakVec = new Vector4(highlightColor.X, highlightColor.Y, highlightColor.Z, peakAlpha);
var center = highlightState.Center!.Value;
var halfSize = highlightState.HalfSize;
if (highlightState.UseClipRect)
drawList.PushClipRect(highlightState.ClipMin, highlightState.ClipMax, true);
switch (highlightState.Mode)
{
case SeluneHighlightMode.Horizontal:
DrawHorizontalHighlight(
drawList,
gradientLeft,
gradientRight,
clampedTopY,
clampedBottomY,
center,
halfSize,
highlightEdgeVec,
highlightPeakVec,
settings.HighlightMidpoint,
highlightState.BorderOnly,
highlightState.BorderThickness,
highlightState.Rounding);
break;
case SeluneHighlightMode.Vertical:
DrawVerticalHighlight(
drawList,
gradientLeft,
gradientRight,
clampedTopY,
clampedBottomY,
center,
halfSize,
highlightEdgeVec,
highlightPeakVec,
settings.HighlightMidpoint,
highlightState.BorderOnly,
highlightState.BorderThickness,
highlightState.Rounding);
break;
case SeluneHighlightMode.Both:
DrawCombinedHighlight(
drawList,
gradientLeft,
gradientRight,
clampedTopY,
clampedBottomY,
center,
halfSize,
highlightEdgeVec,
highlightPeakVec,
highlightState.BorderOnly,
highlightState.BorderThickness,
highlightState.Rounding);
break;
case SeluneHighlightMode.Point:
DrawPointHighlight(
drawList,
center,
halfSize,
highlightEdgeVec,
highlightPeakVec,
highlightState.BorderOnly,
highlightState.BorderThickness);
break;
}
if (highlightState.UseClipRect)
drawList.PopClipRect();
}
private static void DrawHorizontalHighlight(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector2 center,
Vector2 halfSize,
Vector4 edgeColor,
Vector4 peakColor,
float midpoint,
bool borderOnly,
float borderThickness,
float rounding)
{
var highlightTop = MathF.Max(clampedTopY, center.Y - halfSize.Y);
var highlightBottom = MathF.Min(clampedBottomY, center.Y + halfSize.Y);
if (highlightBottom <= highlightTop)
return;
var highlightLeft = MathF.Max(gradientLeft, center.X - halfSize.X);
var highlightRight = MathF.Min(gradientRight, center.X + halfSize.X);
if (highlightRight <= highlightLeft || highlightBottom <= highlightTop)
return;
if (!borderOnly || borderThickness <= 0f)
{
DrawHorizontalHighlightRect(
drawList,
highlightLeft,
highlightRight,
highlightTop,
highlightBottom,
edgeColor,
peakColor,
midpoint,
1f);
return;
}
var innerTop = MathF.Min(highlightBottom, MathF.Max(highlightTop, center.Y - MathF.Max(halfSize.Y - borderThickness, 0f)));
var innerBottom = MathF.Max(highlightTop, MathF.Min(highlightBottom, center.Y + MathF.Max(halfSize.Y - borderThickness, 0f)));
var edgeU32 = ImGui.ColorConvertFloat4ToU32(edgeColor);
var peakU32 = ImGui.ColorConvertFloat4ToU32(peakColor);
if (innerTop > highlightTop)
{
drawList.AddRectFilledMultiColor(
new Vector2(highlightLeft, highlightTop),
new Vector2(highlightRight, innerTop),
edgeU32,
edgeU32,
peakU32,
peakU32);
}
if (innerBottom < highlightBottom)
{
drawList.AddRectFilledMultiColor(
new Vector2(highlightLeft, innerBottom),
new Vector2(highlightRight, highlightBottom),
peakU32,
peakU32,
edgeU32,
edgeU32);
}
}
private static void DrawVerticalHighlight(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector2 center,
Vector2 halfSize,
Vector4 edgeColor,
Vector4 peakColor,
float midpoint,
bool borderOnly,
float borderThickness,
float rounding)
{
var highlightTop = MathF.Max(clampedTopY, center.Y - halfSize.Y);
var highlightBottom = MathF.Min(clampedBottomY, center.Y + halfSize.Y);
var highlightLeft = MathF.Max(gradientLeft, center.X - halfSize.X);
var highlightRight = MathF.Min(gradientRight, center.X + halfSize.X);
if (highlightRight <= highlightLeft || highlightBottom <= highlightTop)
return;
if (!borderOnly || borderThickness <= 0f)
{
DrawVerticalHighlightRect(
drawList,
highlightLeft,
highlightRight,
highlightTop,
highlightBottom,
edgeColor,
peakColor,
midpoint,
1f);
return;
}
var innerLeft = MathF.Min(highlightRight, MathF.Max(highlightLeft, center.X - MathF.Max(halfSize.X - borderThickness, 0f)));
var innerRight = MathF.Max(highlightLeft, MathF.Min(highlightRight, center.X + MathF.Max(halfSize.X - borderThickness, 0f)));
var edgeU32 = ImGui.ColorConvertFloat4ToU32(edgeColor);
var peakU32 = ImGui.ColorConvertFloat4ToU32(peakColor);
if (innerLeft > highlightLeft)
{
drawList.AddRectFilledMultiColor(
new Vector2(highlightLeft, highlightTop),
new Vector2(innerLeft, highlightBottom),
edgeU32,
peakU32,
peakU32,
edgeU32);
}
if (innerRight < highlightRight)
{
drawList.AddRectFilledMultiColor(
new Vector2(innerRight, highlightTop),
new Vector2(highlightRight, highlightBottom),
peakU32,
edgeU32,
edgeU32,
peakU32);
}
}
private static void DrawCombinedHighlight(
ImDrawListPtr drawList,
float gradientLeft,
float gradientRight,
float clampedTopY,
float clampedBottomY,
Vector2 center,
Vector2 halfSize,
Vector4 edgeColor,
Vector4 peakColor,
bool borderOnly,
float borderThickness,
float rounding)
{
var highlightLeft = MathF.Max(gradientLeft, center.X - halfSize.X);
var highlightRight = MathF.Min(gradientRight, center.X + halfSize.X);
var highlightTop = MathF.Max(clampedTopY, center.Y - halfSize.Y);
var highlightBottom = MathF.Min(clampedBottomY, center.Y + halfSize.Y);
if (highlightRight <= highlightLeft || highlightBottom <= highlightTop)
return;
if (borderOnly && borderThickness > 0f)
{
DrawRoundedBorderGlow(drawList, center, halfSize, borderThickness, edgeColor, peakColor, rounding);
return;
}
if (!borderOnly || borderThickness <= 0f)
{
const float combinedScale = 0.85f;
DrawHorizontalHighlightRect(
drawList,
highlightLeft,
highlightRight,
highlightTop,
highlightBottom,
edgeColor,
peakColor,
0.5f,
combinedScale);
DrawVerticalHighlightRect(
drawList,
highlightLeft,
highlightRight,
highlightTop,
highlightBottom,
edgeColor,
peakColor,
0.5f,
combinedScale);
return;
}
var outerLeft = MathF.Max(gradientLeft, highlightLeft - borderThickness);
var outerRight = MathF.Min(gradientRight, highlightRight + borderThickness);
var outerTop = MathF.Max(clampedTopY, highlightTop - borderThickness);
var outerBottom = MathF.Min(clampedBottomY, highlightBottom + borderThickness);
var edge = ImGui.ColorConvertFloat4ToU32(edgeColor);
var peak = ImGui.ColorConvertFloat4ToU32(peakColor);
if (outerTop < highlightTop)
{
drawList.AddRectFilledMultiColor(
new Vector2(outerLeft, outerTop),
new Vector2(outerRight, highlightTop),
edge,
edge,
peak,
peak);
}
if (outerBottom > highlightBottom)
{
drawList.AddRectFilledMultiColor(
new Vector2(outerLeft, highlightBottom),
new Vector2(outerRight, outerBottom),
peak,
peak,
edge,
edge);
}
if (outerLeft < highlightLeft)
{
drawList.AddRectFilledMultiColor(
new Vector2(outerLeft, highlightTop),
new Vector2(highlightLeft, highlightBottom),
edge,
peak,
peak,
edge);
}
if (outerRight > highlightRight)
{
drawList.AddRectFilledMultiColor(
new Vector2(highlightRight, highlightTop),
new Vector2(outerRight, highlightBottom),
peak,
edge,
edge,
peak);
}
}
private static void DrawPointHighlight(
ImDrawListPtr drawList,
Vector2 center,
Vector2 halfSize,
Vector4 edgeColor,
Vector4 peakColor,
bool borderOnly,
float borderThickness)
{
if (halfSize.X <= 0f || halfSize.Y <= 0f)
return;
if (borderOnly && borderThickness > 0f)
{
DrawPointBorderGlow(drawList, center, halfSize, borderThickness, edgeColor, peakColor);
return;
}
const int layers = 7;
for (int layer = 0; layer < layers; layer++)
{
float t = layers <= 1 ? 1f : layer / (layers - 1f);
float scale = 1f - 0.75f * t;
var scaledHalfSize = new Vector2(MathF.Max(1f, halfSize.X * scale), MathF.Max(1f, halfSize.Y * scale));
var color = Vector4.Lerp(edgeColor, peakColor, t);
DrawEllipseFilled(drawList, center, scaledHalfSize, ImGui.ColorConvertFloat4ToU32(color));
}
}
private static void DrawPointBorderGlow(
ImDrawListPtr drawList,
Vector2 center,
Vector2 halfSize,
float thickness,
Vector4 edgeColor,
Vector4 peakColor)
{
int layers = Math.Max(6, (int)MathF.Ceiling(thickness));
for (int i = 0; i < layers; i++)
{
float t = layers <= 1 ? 1f : i / (layers - 1f);
float offset = thickness * t;
var expandedHalfSize = new Vector2(MathF.Max(1f, halfSize.X + offset), MathF.Max(1f, halfSize.Y + offset));
var color = Vector4.Lerp(peakColor, edgeColor, t);
color.W = Math.Clamp((peakColor.W * 0.8f) + (edgeColor.W - peakColor.W) * t, 0f, 1f);
DrawEllipseStroke(drawList, center, expandedHalfSize, ImGui.ColorConvertFloat4ToU32(color), 2f);
}
}
private static void DrawEllipseFilled(ImDrawListPtr drawList, Vector2 center, Vector2 halfSize, uint color, int segments = 48)
{
if (halfSize.X <= 0f || halfSize.Y <= 0f)
return;
BuildEllipsePath(drawList, center, halfSize, segments);
drawList.PathFillConvex(color);
}
private static void DrawEllipseStroke(ImDrawListPtr drawList, Vector2 center, Vector2 halfSize, uint color, float thickness, int segments = 48)
{
if (halfSize.X <= 0f || halfSize.Y <= 0f)
return;
BuildEllipsePath(drawList, center, halfSize, segments);
drawList.PathStroke(color, ImDrawFlags.None, MathF.Max(1f, thickness));
}
private static void BuildEllipsePath(ImDrawListPtr drawList, Vector2 center, Vector2 halfSize, int segments)
{
const float twoPi = MathF.PI * 2f;
segments = Math.Clamp(segments, 12, 96);
drawList.PathClear();
for (int i = 0; i < segments; i++)
{
float angle = twoPi * (i / (float)segments);
var point = new Vector2(
center.X + MathF.Cos(angle) * halfSize.X,
center.Y + MathF.Sin(angle) * halfSize.Y);
drawList.PathLineTo(point);
}
}
private static void DrawRoundedBorderGlow(
ImDrawListPtr drawList,
Vector2 center,
Vector2 halfSize,
float thickness,
Vector4 edgeColor,
Vector4 peakColor,
float rounding)
{
int layers = Math.Max(6, (int)MathF.Ceiling(thickness));
for (int i = 0; i < layers; i++)
{
float t = layers <= 1 ? 0f : i / (layers - 1f);
float offset = thickness * t;
var min = new Vector2(center.X - halfSize.X - offset, center.Y - halfSize.Y - offset);
var max = new Vector2(center.X + halfSize.X + offset, center.Y + halfSize.Y + offset);
var color = Vector4.Lerp(peakColor, edgeColor, t);
color.W = Math.Clamp((peakColor.W * 0.8f) + (edgeColor.W - peakColor.W) * t, 0f, 1f);
drawList.AddRect(min, max, ImGui.ColorConvertFloat4ToU32(color), MathF.Max(0f, rounding + offset), ImDrawFlags.RoundCornersAll, 2f);
}
}
private static void DrawHorizontalHighlightRect(
ImDrawListPtr drawList,
float left,
float right,
float top,
float bottom,
Vector4 edgeColor,
Vector4 peakColor,
float midpoint,
float alphaScale)
{
if (right <= left || bottom <= top)
return;
edgeColor.W *= alphaScale;
peakColor.W *= alphaScale;
var edge = ImGui.ColorConvertFloat4ToU32(edgeColor);
var peak = ImGui.ColorConvertFloat4ToU32(peakColor);
var highlightMid = top + (bottom - top) * midpoint;
drawList.AddRectFilledMultiColor(
new Vector2(left, top),
new Vector2(right, highlightMid),
edge,
edge,
peak,
peak);
drawList.AddRectFilledMultiColor(
new Vector2(left, highlightMid),
new Vector2(right, bottom),
peak,
peak,
edge,
edge);
}
private static void DrawVerticalHighlightRect(
ImDrawListPtr drawList,
float left,
float right,
float top,
float bottom,
Vector4 edgeColor,
Vector4 peakColor,
float midpoint,
float alphaScale)
{
if (right <= left || bottom <= top)
return;
edgeColor.W *= alphaScale;
peakColor.W *= alphaScale;
var edge = ImGui.ColorConvertFloat4ToU32(edgeColor);
var peak = ImGui.ColorConvertFloat4ToU32(peakColor);
var highlightMid = left + (right - left) * midpoint;
drawList.AddRectFilledMultiColor(
new Vector2(left, top),
new Vector2(highlightMid, bottom),
edge,
peak,
peak,
edge);
drawList.AddRectFilledMultiColor(
new Vector2(highlightMid, top),
new Vector2(right, bottom),
peak,
edge,
edge,
peak);
}
}