Skip to main content

Custom

custom

It allows you to render custom-shaped widgets instead of using predefined ones.

See the CustomDemo.cs.

using Gridrand.Contracts;
using Gridrand.RimGui.Manual.Utility;
using System;
using System.Collections.Generic;

namespace Gridrand.RimGui.Manual
{
/// <summary>
/// This is a demo of <see cref="Gui.Custom{TWidget}(bool, int)"/> and
/// <see cref="Gui.CustomDelegate{TState}(TState, System.Action{Shaper, CustomDelegateWidget{TState}}, bool, int)"/>.
///
/// It allows you to render custom-shaped widgets instead of using predefined ones.
/// </summary>
class CustomDemo : ManualBase, IManual
{
readonly float[] barGraphHeights = new float[] { 0.2f, 0.8f, 0.5f, 1.0f, 0.3f, 0.7f, 0.9f, 0.1f, 0.6f, 0.4f };

readonly float[] circleGraphPercentages = new float[] { 0.6f, 0.25f, 0.15f };
readonly Color32[] circleGraphsSegmentColors = new Color32[] { new(128, 30, 30, 255), new(30, 128, 30, 255), new(30, 30, 128, 255) };

float[] graphDataPoints = new float[] { 0.2f, 0.8f, 0.5f, 1.0f, 0.3f, 0.7f, 0.9f, 0.1f, 0.6f, 0.4f };
float graphOffsetX = 0f;

public CustomDemo(ManualBaseResource p) : base(p)
{
}

public void Draw()
{
DrawBarGraph();
Gui.Separator();
DrawCircleGraph();
Gui.Separator();
DrawWavingTriangle();
Gui.Separator();
DrawSin();
Gui.Separator();
DrawGraph();
}

void DrawBarGraph()
{
Gui.ToolTip("Hover over the graph to change color.");

var barGraph = Gui.NextHeight(100f).Custom<BarGraphWidget>(true);

var spacing = 5f;
var totalSpacing = (barGraphHeights.Length - 1) * spacing;

// Get the rectangle of the widget
var rect = barGraph.Rect;
// Calculate the width of each individual bar
var barWidth = (barGraph.Rect.width - totalSpacing) / barGraphHeights.Length;

// Initialize the bar graph widget
barGraph.HoveredIndex = null;
barGraph.Rects.Clear();

for (int i = 0; i < barGraphHeights.Length; i++)
{
// Calculate the position and size of each bar rectangle
barGraph.Rects.Add(new Rect(rect.x + spacing * i + barWidth * i, rect.y, barWidth, barGraphHeights[i] * rect.height));
}

// Check for mouse hover interaction with the bars
for (int i = 0; i < barGraph.Rects.Count; i++)
{
// If the mouse position is within the bounds of a bar rectangle
if (barGraph.Rects[i].Contains(Input.Pointer.Pos))
{
// Set the hovered index to the current bar index
barGraph.HoveredIndex = i;
break;
}
}

for (int i = 0; i < barGraph.Rects.Count; i++)
{
var r = barGraph.Rects[i];
using (Style.Text.AlignmentXs.Begin(AlignmentX.Center))
Gui.NextRect(new Rect(r.x, r.YMax, r.width, 30f)).Text(barGraphHeights[i].ToString());
}


// Display SubWindow if hovered.
if (barGraph.HoveredIndex is int hoveredIndex)
{
DrawMousePosSubWindow(barGraphHeights[hoveredIndex].ToString());
}
}

void DrawCircleGraph()
{
Gui.ToolTip("Hover over the graph to change color.");

var widget = Gui.NextHeight(100f).Custom<CircleGraphWidget>(true);
widget.Percentages = circleGraphPercentages;
widget.SegmentColors = circleGraphsSegmentColors;

widget.HoveredIndex = PieChartUtil.GetIndex(Input.Pointer.Pos, widget.Center, widget.Radius, circleGraphPercentages);

using (Style.Text.AlignmentXs.Begin(AlignmentX.Center))
{
for (int i = 0; i < circleGraphPercentages.Length; i++)
{
var nullCenterPos = PieChartUtil.GetSegmentCenterPos(widget.Center, widget.Radius, circleGraphPercentages, i);
if (nullCenterPos is Vector2 centerPos)
{
Gui.NextRect(RectUtil.CreateFromCenter(centerPos, new Vector2(50, 50))).Text(circleGraphPercentages[i].ToString());
}
}
}

// Display SubWindow if hovered.
if (widget.HoveredIndex is int hoveredIndex)
{
DrawMousePosSubWindow(circleGraphPercentages[hoveredIndex].ToString());
}
}

void DrawMousePosSubWindow(string text)
{
using (Style.SubWindow.BeginAutoSizeLayout(Input.Pointer.Pos + new Vector2(15, -15), new Vector2(50, 50)))
{
if (Gui.BeginSubWindow("Demo"))
{
Gui.Text(text);
Gui.EndSubWindow();
}
}
}

void DrawWavingTriangle()
{
var wavingTriangleWidget = Gui.NextHeight(100f).Custom<WavingTriangle>(true);
// You can change it if you like.
wavingTriangleWidget.Color = new(117, 82, 189, 255);
}

void DrawSin()
{
var numPoints = 70;
/// Defining the delegate as static can help avoid GC allocation every frame.
///
/// Note: This delegate is not executed within CustomDelegate(),
/// but rather inside <see cref="Gui.CompleteFrame"/>.
Gui.NextHeight(100f).CustomDelegate(
numPoints,
static (shaper, widget) =>
{
var width = widget.Rect.width;
var height = widget.Rect.height;
var startPoint = new Vector2(widget.Rect.x, widget.Rect.Center.y);
var prevPoint = startPoint;

// The value passed as an argument is in State.
var numPoints = widget.State;
for (var i = 1; i <= numPoints; i++)
{
var t = (float)i / numPoints;
var x = widget.Rect.x + width * t;
var yOffset = MathF.Sin(x * 0.1f + Time.TotalTime * 3f) * height * 0.3f;
var nextPoint = new Vector2(x, widget.Rect.Center.y + yOffset);
shaper.AddLine(prevPoint, nextPoint, 2f, new(60, 149, 113, 255));
prevPoint = nextPoint;
}
},
true);
}

void DrawGraph()
{
graphOffsetX += Time.DeltaTime * 2f;

Gui.NextHeight(100f).CustomDelegate(
graphDataPoints,
graphOffsetX,
static (shaper, widget) =>
{
// The value passed as an argument is in State.
var dataPoints = widget.State1;
var offsetX = widget.State2;

var y = widget.Rect.YMin;
var width = widget.Rect.width;
var height = widget.Rect.height;
var xStart = widget.Rect.XMin;

var dataLength = dataPoints.Length;

for (int i = 0; i < width; i++)
{
var normalizedX = (float)i / width;
var timeIndexFloat = offsetX + normalizedX * dataLength;

var index1 = MathUtil.FloorToInt(timeIndexFloat) % dataLength;

var index2 = (index1 + 1) % dataLength;

// Calculate the fractional part of timeIndexFloat.
// This value (between 0 and 1) is used as the interpolation factor.
var t = timeIndexFloat - MathUtil.FloorToInt(timeIndexFloat);

var dataValue = MathUtil.Lerp(dataPoints[index1], dataPoints[index2], t);

var x = xStart + i;
var yPos = y + dataValue * height;
var currentPoint = new Vector2(x, yPos);

if (0 < i)
{
var prevNormalizedX = (float)(i - 1) / width;
// Calculate the corresponding data index for the previous point.
var prevTimeIndexFloat = offsetX + prevNormalizedX * dataLength;

var prevIndex1 = MathUtil.FloorToInt(prevTimeIndexFloat) % dataLength;
var prevIndex2 = (prevIndex1 + 1) % dataLength;
var prevT = prevTimeIndexFloat - MathUtil.FloorToInt(prevTimeIndexFloat);
var prevDataValue = MathUtil.Lerp(dataPoints[prevIndex1], dataPoints[prevIndex2], prevT);
var prevX = xStart + (i - 1);
var prevYPos = y + prevDataValue * height;
var prevPoint = new Vector2(prevX, prevYPos);

shaper.AddLine(prevPoint, currentPoint, 2f, new(60, 149, 113, 255));
}
}
},
true);

if (Gui.BeginScroll())
{
//using var s = Ranges.BeginFloat(0f, 1f);
for (int i = 0; i < graphDataPoints.Length; i++)
{
Gui.LabelSliderNumeric($"{i}", ref graphDataPoints[i], 0f, 1f);
}

Gui.EndScroll();
}
}

class WavingTriangle : PrimitiveWidget
{
// Frequency on the X axis
public float FrequencyX { get; set; } = 1.5f;
// Frequency on the Y axis
public float FrequencyY { get; set; } = 2.5f;
// Amplitude of the oscillation
public float Amplitude { get; set; } = 30f;
// Time progression
public float TimeScale { get; set; } = 1f;
public Color32 Color { get; set; } = new(155, 99, 71, 255);
public int TriangleCount => 30;

public override void Build(Shaper shaper)
{
float time = Gridrand.Time.TotalTime * TimeScale;
float spacing = Rect.width / TriangleCount;

for (int i = 0; i < TriangleCount; i++)
{
var x = Rect.x + i * spacing;
var normalizedIndex = (float)i / TriangleCount;

// Lissajous-like offset (complex time-based position calculation)
var offsetX = (float)Math.Sin(time * FrequencyX + normalizedIndex * AdvancedMathUtil.PI * 2f) * Amplitude;
var offsetY = (float)Math.Cos(time * FrequencyY + normalizedIndex * AdvancedMathUtil.PI * 2f) * Amplitude;

var top = new Vector2(x + spacing * 0.5f + offsetX, Rect.Center.y + offsetY);
var left = new Vector2(x, Rect.y);
var right = new Vector2(x + spacing, Rect.y);

shaper.AddTriangle(left, top, right, Color, Color, Color);
}
}
}

class BarGraphWidget : PrimitiveWidget
{
public int? HoveredIndex { get; set; }
public List<Rect> Rects { get; set; } = new();
public Color32 Color { get; set; } = new(140, 148, 155, 255);
public Color32 HovredColor { get; set; } = new(138, 43, 116, 255);

public override void Build(Shaper shaper)
{
for (int i = 0; i < Rects.Count; i++)
{
shaper.AddRect(Rects[i], HoveredIndex == i ? HovredColor : Color);
}
}
}

class CircleGraphWidget : PrimitiveWidget
{
public int? HoveredIndex { get; set; }
public float[] Percentages { get; set; }
public Color32[] SegmentColors { get; set; }
public Color32 HoveredColor { get; set; } = new(60, 60, 60, 255);

public Vector2 Center => Rect.Center;
public float Radius => Math.Min(Rect.width, Rect.height) / 2f;

public override void Build(Shaper shaper)
{
var currentAngle = 0f;
var center = Rect.Center;

for (int i = 0; i < Percentages.Length; i++)
{
var angle = Percentages[i] * 360f;
var startAngle = currentAngle;
var endAngle = currentAngle + angle;

var color = GetColor(i);

var segmentCount = Math.Max(1, (int)Math.Ceiling(angle / 5f));
var step = angle / segmentCount;

for (int j = 0; j < segmentCount; j++)
{
var segStartAngle = startAngle + j * step;
var segEndAngle = segStartAngle + step;

// Convert angles to radians and adjust for clockwise drawing starting from the top (0 degrees).
var a0 = (-segStartAngle + 90f) * MathUtil.Deg2Rad;
var a1 = (-segEndAngle + 90f) * MathUtil.Deg2Rad;

var point1 = center + new Vector2(MathUtil.Cos(a0), MathUtil.Sin(a0)) * Radius;
var point2 = center + new Vector2(MathUtil.Cos(a1), MathUtil.Sin(a1)) * Radius;

shaper.AddTriangle(point2, center, point1, color, color, color);
}

currentAngle = endAngle;
}
}

Color32 GetColor(int i)
{
if (HoveredIndex == i)
return HoveredColor;

if (i < SegmentColors.Length)
return SegmentColors[i];

return Color32.White;
}
}

class OctagonWidget : PrimitiveWidget
{
public bool[] IsHovereds { get; } = new bool[8];
public Vector2[] Vertices { get; } = new Vector2[8];
public Vector2 Center { get; set; }
public Color32 Color { get; set; } = new(140, 148, 155, 255);
public Color32 LineColor { get; set; } = new(89, 106, 83, 255);
public Color32 HoveredColor { get; set; } = new(138, 43, 116, 255);

public override void Build(Shaper shaper)
{
for (int i = 0; i < 8; i++)
{
var nextIndex = (i + 1) % 8;
var color = IsHovereds[i] ? HoveredColor : Color;
shaper.AddTriangle(
Vertices[i],
Center,
Vertices[nextIndex],
color,
color,
color
);
}
for (int i = 0; i < 8; i++)
{
var nextIndex = (i + 1) % 8;
shaper.AddLine(Center, Vertices[i], 1f, LineColor);
shaper.AddLine(Vertices[i], Vertices[nextIndex], 1f, LineColor);
}
}
}
}
}