Bake paint HUD layout into a prefab with separate channel panels

Split the single HUD panel into four independent per-channel sub-panels
plus a global brush-size panel, each a standalone RectTransform with its
own header (drag-handle ready). PaintHud now binds to a baked layout
prefab via recursive name lookup instead of building everything in code,
with a code-gen fallback. HsvColorPicker gains Rebind() to re-apply the
procedural gradient sprites and drag relays that a prefab cannot serialize.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-24 12:35:36 +02:00
1 parent 4340970691
commit ace1f949dc
5 files changed
+7484 -115

No files matched your search

+71 -24
View File
@@ -3,12 +3,16 @@ using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Compact runtime HSV colour picker: a saturation/value square, a hue bar and a live
/// swatch. Builds its own UI as children when <see cref="Build"/> is called and raises
/// <see cref="Changed"/> on user interaction. The square is a pure-hue base with two
/// static gradient overlays on top (white-out -> saturation, black-out -> value), so
/// only the base tint changes per hue - no per-frame texture rebuilds. Hue is never
/// lost while dragging the square (the square only edits S/V, the bar only edits H).
/// Compact runtime HSV colour picker: a saturation/value square, a hue bar and a live swatch.
/// The square is a pure-hue base with two static gradient overlays on top (white-out ->
/// saturation, black-out -> value), so only the base tint changes per hue - no per-frame
/// texture rebuilds. Hue is never lost while dragging the square (the square only edits S/V,
/// the bar only edits H).
///
/// <see cref="Build"/> creates the child structure (stable names, no sprites or hooks); it can
/// be baked into a prefab. <see cref="Rebind"/> then (re)assigns the runtime gradient sprites
/// (which a prefab cannot store) and (re)hooks the drag relays - it runs for both the code-built
/// picker and one instantiated from a prefab, so the two are behaviourally identical.
/// </summary>
public class HsvColorPicker : MonoBehaviour
{
@@ -41,43 +45,86 @@ public class HsvColorPicker : MonoBehaviour
Refresh();
}
/// <summary>Builds the picker (square + hue bar + swatch) under this object, top-left aligned.</summary>
/// <summary>
/// Builds the picker structure (square + hue bar + swatch) under this object, top-left aligned.
/// Layout only - no gradient sprites and no event hooks, so the result is safe to bake into a
/// prefab. Call <see cref="Rebind"/> afterwards (at runtime) to make it functional.
/// </summary>
public void Build(float squareSize, float hueHeight)
{
// Saturation / value square: base hue + two gradient overlays, top-left of this rect.
// Saturation / value square: base hue + two named gradient overlays, top-left of this rect.
var sv = PaintHud.NewRect("SV", transform);
TopLeft(sv, new Vector2(0f, 0f), new Vector2(squareSize, squareSize));
_hueBase = PaintHud.AddImage(sv.gameObject, Color.red); // raycast target for the drag relay
PaintHud.AddImage(sv.gameObject, Color.red); // raycast target for the drag relay
PaintHud.AddOverlay(sv, PaintHud.SaturationSprite());
PaintHud.AddOverlay(sv, PaintHud.ValueSprite());
PaintHud.AddOverlay(sv, "SatOverlay");
PaintHud.AddOverlay(sv, "ValOverlay");
sv.gameObject.AddComponent<UiPointerSurface>().Picked += OnSvPicked;
sv.gameObject.AddComponent<UiPointerSurface>();
_svHandle = PaintHud.NewRect("Handle", sv);
_svHandle.sizeDelta = new Vector2(12f, 12f);
var svHandleImg = PaintHud.AddImage(_svHandle.gameObject, Color.white);
svHandleImg.raycastTarget = false;
var svHandle = PaintHud.NewRect("Handle", sv);
svHandle.sizeDelta = new Vector2(12f, 12f);
PaintHud.AddImage(svHandle.gameObject, Color.white).raycastTarget = false;
// Hue bar below the square.
var hue = PaintHud.NewRect("Hue", transform);
TopLeft(hue, new Vector2(0f, -(squareSize + 6f)), new Vector2(squareSize, hueHeight));
var hueImg = hue.gameObject.AddComponent<Image>();
hueImg.sprite = PaintHud.HueSprite();
hueImg.type = Image.Type.Simple;
hue.gameObject.AddComponent<UiPointerSurface>().Picked += OnHuePicked;
hue.gameObject.AddComponent<UiPointerSurface>();
_hueHandle = PaintHud.NewRect("Handle", hue);
_hueHandle.sizeDelta = new Vector2(4f, hueHeight + 4f);
var hueHandleImg = PaintHud.AddImage(_hueHandle.gameObject, Color.white);
hueHandleImg.raycastTarget = false;
var hueHandle = PaintHud.NewRect("Handle", hue);
hueHandle.sizeDelta = new Vector2(4f, hueHeight + 4f);
PaintHud.AddImage(hueHandle.gameObject, Color.white).raycastTarget = false;
// Swatch, to the right of the square.
var sw = PaintHud.NewRect("Swatch", transform);
TopLeft(sw, new Vector2(squareSize + 10f, 0f), new Vector2(34f, 34f));
_swatch = PaintHud.AddImage(sw.gameObject, Color.white);
_swatch.raycastTarget = false;
PaintHud.AddImage(sw.gameObject, Color.white).raycastTarget = false;
}
/// <summary>
/// Re-finds the child widgets, (re)assigns the runtime gradient sprites and (re)hooks the drag
/// relays. Idempotent (unsubscribe-then-subscribe), so it is safe whether the structure was just
/// built in code or instantiated from a prefab. Call before <see cref="SetColor"/>.
/// </summary>
public void Rebind()
{
var sv = transform.Find("SV");
if (sv)
{
_hueBase = sv.GetComponent<Image>();
var sat = sv.Find("SatOverlay");
if (sat && sat.TryGetComponent(out Image satImg)) satImg.sprite = PaintHud.SaturationSprite();
var val = sv.Find("ValOverlay");
if (val && val.TryGetComponent(out Image valImg)) valImg.sprite = PaintHud.ValueSprite();
_svHandle = sv.Find("Handle") as RectTransform;
if (sv.TryGetComponent(out UiPointerSurface svPointer))
{
svPointer.Picked -= OnSvPicked;
svPointer.Picked += OnSvPicked;
}
}
var hue = transform.Find("Hue");
if (hue)
{
if (hue.TryGetComponent(out Image hueImg)) hueImg.sprite = PaintHud.HueSprite();
_hueHandle = hue.Find("Handle") as RectTransform;
if (hue.TryGetComponent(out UiPointerSurface huePointer))
{
huePointer.Picked -= OnHuePicked;
huePointer.Picked += OnHuePicked;
}
}
var swatch = transform.Find("Swatch");
if (swatch) _swatch = swatch.GetComponent<Image>();
Refresh();
}