using UnityEngine;
using UnityEngine.UIElements;
namespace Luneraria.UI
{
///
/// A vector-drawn plate (port of scripts/ui/theme/forged_style.gd): chamfered corners, a
/// vertical fill gradient, an edge line, an inset line, an optional top highlight and a soft
/// shadow. Drawn from the element's size, so it stays crisp at every UI scale.
///
public sealed class LunePlateStyle
{
public Color FillTop = LuneUiTheme.Hex("1b2027");
public Color FillBottom = LuneUiTheme.Hex("12161b");
public Color Edge = LuneUiTheme.Hex("8a6a3c");
public float EdgeWidth = 1.5f;
public Color Inner = new Color(1f, 1f, 1f, .05f);
public float InnerInset = 4f;
public float Chamfer = 7f;
public Color Highlight = Color.clear;
public Color Shadow = Color.clear;
public float ShadowSize;
/// Grows the drawing outside the element (focus rings).
public float Grow;
public bool DrawFill = true;
/// Small rivet diamonds in the middle of the long edges (menu plates).
public bool Ornaments;
public Color OrnamentColor = LuneUiTheme.Hex("c99a58");
/// Content margins (Godot content_margin_*), applied as padding.
public float PadX = 12f;
public float PadY = 12f;
/// SaumwachtTheme.plate (inner .045) plus LuneUiTheme.plate (inner .035, inset 3, margins).
public static LunePlateStyle Hud(Color top, Color bottom, Color edge, float edgeWidth = 1f, float cut = 6f, float padX = 12f, float padY = 10f)
{
return new LunePlateStyle { FillTop = top, FillBottom = bottom, Edge = edge, EdgeWidth = edgeWidth, Chamfer = cut, Inner = new Color(1f, 1f, 1f, .035f), InnerInset = 3f, PadX = padX, PadY = padY };
}
/// SaumwachtTheme.plate with margins: menu plates (inner .045, inset 4).
public static LunePlateStyle Menu(Color top, Color bottom, Color edge, float edgeWidth = 1.5f, float cut = 7f, float padX = 12f, float padY = 12f)
{
return new LunePlateStyle { FillTop = top, FillBottom = bottom, Edge = edge, EdgeWidth = edgeWidth, Chamfer = cut, Inner = new Color(1f, 1f, 1f, .045f), InnerInset = 4f, PadX = padX, PadY = padY };
}
/// Keyboard focus ring outside the element (SaumwachtTheme.focus_ring).
public static LunePlateStyle FocusRing(float cut = 7f)
{
return new LunePlateStyle { DrawFill = false, Edge = LuneUiTheme.Focus, EdgeWidth = 2.5f, Inner = LuneUiTheme.WithAlpha(LuneUiTheme.Focus, .55f), InnerInset = 4.5f,
Chamfer = cut + 4f, Grow = 4f, Shadow = LuneUiTheme.WithAlpha(LuneUiTheme.Focus, .22f), ShadowSize = 5f, PadX = 0f, PadY = 0f };
}
public void ApplyPadding(VisualElement element)
{
element.style.paddingLeft = PadX;
element.style.paddingRight = PadX;
element.style.paddingTop = PadY;
element.style.paddingBottom = PadY;
}
public LunePlateStyle WithShadow(Color shadow, float size)
{
var copy = (LunePlateStyle)MemberwiseClone();
copy.Shadow = shadow;
copy.ShadowSize = size;
return copy;
}
public LunePlateStyle WithInner(Color inner)
{
var copy = (LunePlateStyle)MemberwiseClone();
copy.Inner = inner;
return copy;
}
}
public static class LunePlates
{
static readonly Vector2[] Points = new Vector2[8];
/// The eight corners of a chamfered rectangle, clockwise from the top left cut.
public static Vector2[] PlatePoints(Rect rect, float cut, Vector2[] into = null)
{
Vector2[] result = into ?? new Vector2[8];
float c = Mathf.Min(cut, Mathf.Min(rect.width, rect.height) * .5f);
Vector2 p = rect.min;
Vector2 e = rect.max;
result[0] = new Vector2(p.x + c, p.y);
result[1] = new Vector2(e.x - c, p.y);
result[2] = new Vector2(e.x, p.y + c);
result[3] = new Vector2(e.x, e.y - c);
result[4] = new Vector2(e.x - c, e.y);
result[5] = new Vector2(p.x + c, e.y);
result[6] = new Vector2(p.x, e.y - c);
result[7] = new Vector2(p.x, p.y + c);
return result;
}
static Rect Grow(Rect rect, float amount)
{
return new Rect(rect.x - amount, rect.y - amount, rect.width + amount * 2f, rect.height + amount * 2f);
}
public static void Draw(MeshGenerationContext context, Rect rect, LunePlateStyle style)
{
Rect r = Grow(rect, style.Grow);
if (r.width < 2f || r.height < 2f)
return;
Painter2D painter = context.painter2D;
if (style.Shadow.a > 0f && style.ShadowSize > 0f)
{
for (int layer = 0; layer < 3; layer++)
{
float spread = style.ShadowSize * (layer + 1) / 3f;
var soft = new Color(style.Shadow.r, style.Shadow.g, style.Shadow.b, style.Shadow.a / 3f);
if (style.DrawFill)
{
Rect offset = new Rect(r.x, r.y + spread * .5f, r.width, r.height);
Polygon(painter, PlatePoints(Grow(offset, spread), style.Chamfer + spread, Points), soft);
}
else
{
// Rings only glow outwards; the element underneath stays untinted.
Outline(painter, PlatePoints(Grow(r, spread), style.Chamfer + spread, Points), soft, style.ShadowSize / 3f + 1f);
}
}
}
if (style.DrawFill)
Gradient(context, PlatePoints(r, style.Chamfer, Points), r, style.FillTop, style.FillBottom);
if (style.Highlight.a > 0f)
{
float y = r.y + style.EdgeWidth + 1f;
Line(painter, new Vector2(r.x + style.Chamfer, y), new Vector2(r.xMax - style.Chamfer, y), style.Highlight, 1f);
}
if (style.Inner.a > 0f && style.InnerInset > 0f)
Outline(painter, PlatePoints(Grow(r, -style.InnerInset), Mathf.Max(1f, style.Chamfer - style.InnerInset * .45f), Points), style.Inner, 1f);
if (style.Edge.a > 0f && style.EdgeWidth > 0f)
Outline(painter, PlatePoints(Grow(r, -style.EdgeWidth * .5f), style.Chamfer, Points), style.Edge, style.EdgeWidth);
if (style.Ornaments)
{
// Small rivet diamonds on the long edges mark forged panels without adding frames.
float x = r.x + r.width * .5f;
foreach (float y in new[] { r.y, r.yMax })
{
const float size = 3.5f;
Polygon(painter, new[] { new Vector2(x, y - size), new Vector2(x + size, y), new Vector2(x, y + size), new Vector2(x - size, y) }, style.OrnamentColor);
}
}
}
/// Convex polygon with a vertical colour gradient (Godot: polygon with per-vertex colours).
public static void Gradient(MeshGenerationContext context, Vector2[] points, Rect bounds, Color top, Color bottom)
{
int count = points.Length;
MeshWriteData mesh = context.Allocate(count + 1, count * 3);
Vector2 center = Vector2.zero;
foreach (Vector2 point in points)
center += point;
center /= count;
mesh.SetNextVertex(new Vertex { position = new Vector3(center.x, center.y, Vertex.nearZ), tint = Color.Lerp(top, bottom, Fraction(center.y, bounds)) });
foreach (Vector2 point in points)
mesh.SetNextVertex(new Vertex { position = new Vector3(point.x, point.y, Vertex.nearZ), tint = Color.Lerp(top, bottom, Fraction(point.y, bounds)) });
// Clockwise triangles in screen space (y down).
for (int index = 0; index < count; index++)
{
mesh.SetNextIndex(0);
mesh.SetNextIndex((ushort)(1 + index));
mesh.SetNextIndex((ushort)(1 + (index + 1) % count));
}
}
static float Fraction(float y, Rect bounds)
{
return Mathf.Clamp01((y - bounds.y) / Mathf.Max(1f, bounds.height));
}
public static void Polygon(Painter2D painter, Vector2[] points, Color color)
{
painter.fillColor = color;
painter.BeginPath();
painter.MoveTo(points[0]);
for (int index = 1; index < points.Length; index++)
painter.LineTo(points[index]);
painter.ClosePath();
painter.Fill();
}
public static void Outline(Painter2D painter, Vector2[] points, Color color, float width)
{
painter.strokeColor = color;
painter.lineWidth = width;
painter.lineJoin = LineJoin.Miter;
painter.BeginPath();
painter.MoveTo(points[0]);
for (int index = 1; index < points.Length; index++)
painter.LineTo(points[index]);
painter.ClosePath();
painter.Stroke();
}
public static void Line(Painter2D painter, Vector2 from, Vector2 to, Color color, float width)
{
painter.strokeColor = color;
painter.lineWidth = width;
painter.lineCap = LineCap.Butt;
painter.BeginPath();
painter.MoveTo(from);
painter.LineTo(to);
painter.Stroke();
}
}
}