using System; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.SceneManagement; public partial class PaintHud { // --- Persistence: panel positions + the widget-bound painter values --- // // Saved on quit / play-stop and restored on the next start, so the HUD opens where you left it // with the brush settings you last used. This is small structured state (a handful of positions // and scalars), so it goes to a human-readable JSON file - unlike the painter's bulk pixel maps // (MeshPainter.Persistence) which use a raw byte dump. The two saves are independent: this one // owns the HUD layout + brush values, the painter owns the canvas pixels. [Header("Persistence")] [Tooltip("Save the panel positions and their values on quit / play-stop and restore them on the " + "next start. Off = the HUD opens at its authored layout / painter defaults every session.")] public bool persistHud = true; [Tooltip("Save file identifier. Empty = derived from the active scene name. Set explicitly to keep " + "the layout stable across scene renames or to share one layout between scenes.")] public string hudSaveId = ""; private const int HudFileVersion = 1; private bool _hudSavedOnTeardown; // guards a double save across OnApplicationQuit + OnDestroy private static string HudSaveDir => Path.Combine(Application.persistentDataPath, "MeshPaint"); private string HudSavePath() { string key = string.IsNullOrEmpty(hudSaveId) ? SanitizeHudKey(SceneManager.GetActiveScene().name + "_hud") : SanitizeHudKey(hudSaveId); return Path.Combine(HudSaveDir, key + ".hudlayout"); } private static string SanitizeHudKey(string s) { foreach (char c in Path.GetInvalidFileNameChars()) s = s.Replace(c, '_'); return s.Replace(' ', '_'); } [Serializable] private class HudState { public int version = HudFileVersion; public bool hasValues; // false = no painter was bound when saved -> restore positions only public List panels = new List(); public HudValues values = new HudValues(); } [Serializable] private struct PanelPos { public string name; public Vector2 pos; // anchoredPosition (point-anchored panels -> fully determines placement) } // Mirrors exactly the painter fields the HUD widgets bind to (PaintHud.WirePanel). [Serializable] private class HudValues { public bool paintAlbedo, paintMetallicSmoothness, paintOcclusion, paintEmission, spray; public Color brushColor; public float metallic, smoothness, metallicSmoothnessOpacity, occlusion, occlusionOpacity; public Color emissionColor; public float emissionIntensity; public int brushSize; public int sprayDots; public float sprayDotSizeMin, sprayDotSizeMax, sprayDotAlphaMin, sprayDotAlphaMax; } private void OnApplicationQuit() => SaveHudOnTeardown(); /// Saves once during teardown (OnApplicationQuit and OnDestroy both call this; only the first writes). private void SaveHudOnTeardown() { if (_hudSavedOnTeardown) return; _hudSavedOnTeardown = true; SaveHudState(); } /// Writes the current panel positions and bound painter values to disk. public void SaveHudState() { if (!persistHud || !_panel) return; try { var state = new HudState(); var panel = _panel.transform; foreach (var name in DraggablePanels) { if (FindDeep(panel, name) is RectTransform rt) state.panels.Add(new PanelPos { name = name, pos = rt.anchoredPosition }); } if (painter) { state.hasValues = true; var v = state.values; v.paintAlbedo = painter.paintAlbedo; v.paintMetallicSmoothness = painter.paintMetallicSmoothness; v.paintOcclusion = painter.paintOcclusion; v.paintEmission = painter.paintEmission; v.spray = painter.spray; v.brushColor = painter.brushColor; v.metallic = painter.metallic; v.smoothness = painter.smoothness; v.metallicSmoothnessOpacity = painter.metallicSmoothnessOpacity; v.occlusion = painter.occlusion; v.occlusionOpacity = painter.occlusionOpacity; v.emissionColor = painter.emissionColor; v.emissionIntensity = painter.emissionIntensity; v.brushSize = painter.brushSize; v.sprayDots = painter.sprayDots; v.sprayDotSizeMin = painter.sprayDotSizeMin; v.sprayDotSizeMax = painter.sprayDotSizeMax; v.sprayDotAlphaMin = painter.sprayDotAlphaMin; v.sprayDotAlphaMax = painter.sprayDotAlphaMax; } Directory.CreateDirectory(HudSaveDir); File.WriteAllText(HudSavePath(), JsonUtility.ToJson(state, true)); } catch (Exception e) { Debug.LogWarning($"[PaintHud] Saving HUD state failed: {e.Message}", this); } } /// /// Restores the saved panel positions and brush values. Values are written into the painter (the /// source of truth the widgets read) and then pushed to every widget via , /// reusing the exact same painter -> HUD path the pipette pick uses. Positions are applied last. /// public void RestoreHudState() { if (!persistHud || !_panel) return; var state = LoadHudState(); if (state == null) return; if (painter && state.hasValues && state.values != null) { var v = state.values; painter.paintAlbedo = v.paintAlbedo; painter.paintMetallicSmoothness = v.paintMetallicSmoothness; painter.paintOcclusion = v.paintOcclusion; painter.paintEmission = v.paintEmission; painter.spray = v.spray; painter.brushColor = v.brushColor; painter.metallic = v.metallic; painter.smoothness = v.smoothness; painter.metallicSmoothnessOpacity = v.metallicSmoothnessOpacity; painter.occlusion = v.occlusion; painter.occlusionOpacity = v.occlusionOpacity; painter.emissionColor = v.emissionColor; painter.emissionIntensity = v.emissionIntensity; painter.brushSize = v.brushSize; painter.sprayDots = v.sprayDots; painter.sprayDotSizeMin = v.sprayDotSizeMin; painter.sprayDotSizeMax = v.sprayDotSizeMax; painter.sprayDotAlphaMin = v.sprayDotAlphaMin; painter.sprayDotAlphaMax = v.sprayDotAlphaMax; SyncFromPainter(); } // Point-anchored panels: anchoredPosition is resolution-independent under the canvas' // ScaleWithScreenSize scaler, so the raw saved value places them identically next session. var root = _panel.transform; if (state.panels != null) foreach (var p in state.panels) if (FindDeep(root, p.name) is RectTransform rt) rt.anchoredPosition = p.pos; } private HudState LoadHudState() { string path = HudSavePath(); if (!File.Exists(path)) return null; try { var state = JsonUtility.FromJson(File.ReadAllText(path)); if (state == null || state.version != HudFileVersion) return null; return state; } catch (Exception e) { Debug.LogWarning($"[PaintHud] Loading HUD state failed: {e.Message}", this); return null; } } /// Deletes the saved HUD layout (next start opens at the authored layout / defaults). Editor convenience. [ContextMenu("Clear Saved HUD Layout")] public void ClearSavedHudState() { try { string path = HudSavePath(); if (File.Exists(path)) File.Delete(path); Debug.Log($"[PaintHud] Cleared saved HUD layout: {path}", this); } catch (Exception e) { Debug.LogWarning($"[PaintHud] Clearing saved HUD layout failed: {e.Message}", this); } } }