using UnityEngine; using UnityEngine.UIElements; using System.Collections.Generic; namespace Hallucinate.UI.Components { /// /// An adjustable Vector Element that you can "sketch" and tweak directly in UI Builder (UXML). /// Supports Parametric shapes (Pill, Polygon, Star) and Custom Paths. /// public class VectorShapeElement : VisualElement { public enum ShapeType { Pill, Polygon, Star, CustomPath } public new class UxmlFactory : UxmlFactory { } public new class UxmlTraits : VisualElement.UxmlTraits { UxmlEnumAttributeDescription m_ShapeType = new UxmlEnumAttributeDescription { name = "shape-type", defaultValue = ShapeType.Pill }; UxmlColorAttributeDescription m_FillColor = new UxmlColorAttributeDescription { name = "fill-color", defaultValue = Color.white }; UxmlColorAttributeDescription m_StrokeColor = new UxmlColorAttributeDescription { name = "stroke-color", defaultValue = Color.black }; UxmlFloatAttributeDescription m_StrokeWidth = new UxmlFloatAttributeDescription { name = "stroke-width", defaultValue = 2f }; UxmlFloatAttributeDescription m_CornerRadius = new UxmlFloatAttributeDescription { name = "corner-radius", defaultValue = 10f }; UxmlIntAttributeDescription m_Sides = new UxmlIntAttributeDescription { name = "sides", defaultValue = 5 }; UxmlFloatAttributeDescription m_Inwardness = new UxmlFloatAttributeDescription { name = "star-inwardness", defaultValue = 0.5f }; UxmlStringAttributeDescription m_PathData = new UxmlStringAttributeDescription { name = "path-data", defaultValue = "" }; public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) { base.Init(ve, bag, cc); var ate = ve as VectorShapeElement; ate.shapeType = m_ShapeType.GetValueFromBag(bag, cc); ate.fillColor = m_FillColor.GetValueFromBag(bag, cc); ate.strokeColor = m_StrokeColor.GetValueFromBag(bag, cc); ate.strokeWidth = m_StrokeWidth.GetValueFromBag(bag, cc); ate.cornerRadius = m_CornerRadius.GetValueFromBag(bag, cc); ate.sides = Mathf.Max(3, m_Sides.GetValueFromBag(bag, cc)); ate.inwardness = Mathf.Clamp01(m_Inwardness.GetValueFromBag(bag, cc)); ate.pathData = m_PathData.GetValueFromBag(bag, cc); ate.MarkDirtyRepaint(); } } public ShapeType shapeType { get; set; } public Color fillColor { get; set; } public Color strokeColor { get; set; } public float strokeWidth { get; set; } public float cornerRadius { get; set; } public int sides { get; set; } public float inwardness { get; set; } public string pathData { get; set; } public VectorShapeElement() { generateVisualContent += OnGenerateVisualContent; } private void OnGenerateVisualContent(MeshGenerationContext mgc) { var paint = mgc.painter2D; var rect = contentRect; if (rect.width <= 0 || rect.height <= 0) return; paint.BeginPath(); paint.fillColor = fillColor; paint.strokeColor = strokeColor; paint.lineWidth = strokeWidth; switch (shapeType) { case ShapeType.Pill: DrawPill(paint, rect); break; case ShapeType.Polygon: DrawPolygon(paint, rect, false); break; case ShapeType.Star: DrawPolygon(paint, rect, true); break; case ShapeType.CustomPath: DrawCustomPath(paint); break; } paint.ClosePath(); paint.Fill(); if (strokeWidth > 0) paint.Stroke(); } private void DrawPill(Painter2D paint, Rect rect) { float r = Mathf.Min(cornerRadius, rect.width / 2f, rect.height / 2f); paint.MoveTo(new Vector2(r, 0)); paint.LineTo(new Vector2(rect.width - r, 0)); paint.ArcTo(new Vector2(rect.width, 0), new Vector2(rect.width, r), r); paint.LineTo(new Vector2(rect.width, rect.height - r)); paint.ArcTo(new Vector2(rect.width, rect.height), new Vector2(rect.width - r, rect.height), r); paint.LineTo(new Vector2(r, rect.height)); paint.ArcTo(new Vector2(0, rect.height), new Vector2(0, rect.height - r), r); paint.LineTo(new Vector2(0, r)); paint.ArcTo(new Vector2(0, 0), new Vector2(r, 0), r); } private void DrawPolygon(Painter2D paint, Rect rect, bool isStar) { Vector2 center = rect.center; float radius = Mathf.Min(rect.width, rect.height) / 2f; int totalPoints = isStar ? sides * 2 : sides; float angleStep = 360f / totalPoints; for (int i = 0; i < totalPoints; i++) { float angle = (i * angleStep - 90f) * Mathf.Deg2Rad; float r = radius; if (isStar && i % 2 != 0) r *= inwardness; Vector2 pos = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * r; if (i == 0) paint.MoveTo(pos); else paint.LineTo(pos); } } private void DrawCustomPath(Painter2D paint) { if (string.IsNullOrEmpty(pathData)) return; string[] pairs = pathData.Split(' '); for (int i = 0; i < pairs.Length; i++) { string[] coords = pairs[i].Split(','); if (coords.Length == 2 && float.TryParse(coords[0], out float x) && float.TryParse(coords[1], out float y)) { if (i == 0) paint.MoveTo(new Vector2(x, y)); else paint.LineTo(new Vector2(x, y)); } } } } }