feat: Introduce GregUI framework to replace IMGUI, enhancing UI stability and performance
gregCore CI / build (push) Has been cancelled

This commit is contained in:
Marvin
2026-04-23 21:42:37 +02:00
parent 8634792e2b
commit 2d5befcafb
13 changed files with 468 additions and 175 deletions
Binary file not shown.
+56
View File
@@ -0,0 +1,56 @@
# Modding Migration Guide: gregCore v1.0.0.35
## The Unity 6 IL2CPP Problem
In Unity 6 (Data Center version), the **IMGUI** system (`GUI.Window`, `GUILayout`) is heavily stripped during the IL2CPP build process. Using managed delegates (like `Action<int>`) for UI windows often fails with:
`[Il2CppInterop] Exception in IL2CPP-to-Managed trampoline: System.NotSupportedException: Method unstripping failed`.
This causes infinite log spam and crashes the rendering bridge.
## The Solution: GregUI (UGUI Framework)
Starting with version 1.0.0.35, `gregCore` provides a safe, native UGUI backend that bypasses the broken IMGUI trampolines.
### 1. Legacy Fixes (Automatic)
- **`greg.Sdk.gregEventDispatcher`**: Restored for compatibility with older mods.
- **`gregNativeEventHooks`**: Stabilized signatures for better IL2CPP linking.
### 2. Migrating to GregUI (Manual Action Required)
Mods using `OnGUI()` or `GUI.Window()` should migrate to the **GregUI Builder API**.
#### Old Code (Prone to Crashes):
```csharp
void OnGUI() {
GUI.Window(id, rect, DrawWindow, "Title");
}
void DrawWindow(int id) {
if (GUILayout.Button("Click Me")) { /* logic */ }
}
```
#### New Safe Code (v1.0.0.35+):
```csharp
using gregCore.PublicApi;
public void BuildUI() {
greg.UI.CreateBuilder("Economy Hud")
.SetSize(400, 300)
.AddLabel("Current Funds: $1500")
.AddButton("Add Money", () => {
// Your logic here - safely wrapped for IL2CPP
})
.AddButton("Close", () => {
gregCore.UI.GregUIManager.SetPanelActive("Economy Hud", false);
})
.Build();
}
// Toggle visibility via hotkey
if (Input.GetKeyDown(KeyCode.F9)) {
gregCore.UI.GregUIManager.TogglePanel("Economy Hud");
}
```
## Important: Clear Cache
After updating `gregCore.dll`, you **MUST** delete the following folder to force metadata regeneration:
`[GamePath]/MelonLoader/Il2CppAssemblies/`
Failure to do so will cause `TypeLoadExceptions` even if the code is correct.
Binary file not shown.
Binary file not shown.
+8
View File
@@ -83,6 +83,14 @@ public sealed class GregCoreMod : MelonMod
// 2. Global API Init
gregCore.API.GregAPI.Initialize();
// 2.1 UI Init (Safe UGUI)
try {
Il2CppInterop.Runtime.Injection.ClassInjector.RegisterTypeInIl2Cpp<gregCore.UI.GregUIDragHandler>();
gregCore.UI.GregUIManager.Initialize();
} catch (Exception ex) {
greg.Logging.GregLogger.Error("Failed to initialize GregUI Framework", ex);
}
// 3. Plugin Loading
_container.GetRequired<IGregPluginRegistry>().LoadAll();
@@ -17,22 +17,40 @@ public class GregHudService
_keybindRegistry = keybindRegistry;
}
public void Toggle() => _showHud = !_showHud;
private GameObject? _hudPanel;
public void Toggle()
{
_showHud = !_showHud;
if (_showHud && _hudPanel == null)
{
BuildUI();
}
gregCore.UI.GregUIManager.SetPanelActive("HUD", _showHud);
}
private void BuildUI()
{
var conflicts = _keybindRegistry.GetAll().Where(k => k.HasConflict).ToList();
var builder = gregCore.UI.GregUIBuilder.Create("HUD")
.SetSize(300, 40 + (conflicts.Count * 20));
builder.AddLabel("gregCore: Keybind-Konflikte!");
foreach (var conflict in conflicts)
{
builder.AddLabel($"{conflict.DisplayName} ({conflict.CurrentKey})");
}
_hudPanel = builder.Build();
var rt = _hudPanel.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
rt.anchoredPosition = new Vector2(10, -10);
}
public void OnGUI()
{
if (!_showHud) return;
var conflicts = _keybindRegistry.GetAll().Where(k => k.HasConflict).ToList();
if (conflicts.Count == 0) return;
GUI.Box(new Rect(10, 10, 300, 40 + (conflicts.Count * 20)), "gregCore: Keybind-Konflikte!");
int y = 40;
foreach (var conflict in conflicts)
{
GUI.Label(new Rect(20, y, 280, 20), $"<color=red>{conflict.DisplayName} ({conflict.CurrentKey})</color>");
y += 20;
}
// IMGUI disabled
}
}
@@ -19,22 +19,27 @@ public class GregNotificationService
{
_activeNotifications.Add(new Notification { Title = title, Message = message, Expiration = Time.time + duration });
_logger.Info($"Notification: {title} - {message}");
// Build UGUI Notification
var builder = gregCore.UI.GregUIBuilder.Create($"Notify_{Guid.NewGuid()}")
.SetSize(300, 60);
builder.AddLabel($"{title}\n{message}");
var obj = builder.Build();
// Position at bottom-right
var rt = obj.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(1, 0);
rt.anchorMax = new Vector2(1, 0);
rt.pivot = new Vector2(1, 0);
rt.anchoredPosition = new Vector2(-20, 20 + (_activeNotifications.Count * 70));
// Auto-destroy after duration
UnityEngine.Object.Destroy(obj, duration);
}
public void OnGUI()
{
int y = Screen.height - 100;
foreach (var notification in _activeNotifications.ToArray())
{
if (Time.time > notification.Expiration)
{
_activeNotifications.Remove(notification);
continue;
}
GUI.Box(new Rect(Screen.width - 320, y, 300, 60), $"{notification.Title}\n{notification.Message}");
y -= 70;
}
// IMGUI disabled
}
private class Notification
+31 -25
View File
@@ -17,46 +17,52 @@ public sealed class GregDevConsole
private string _inputCommand = "";
private readonly List<LogEntry> _logs = new();
private Vector2 _scrollPosition;
private GameObject _uiPanel;
public void Toggle() => _isOpen = !_isOpen;
public void Toggle()
{
_isOpen = !_isOpen;
if (_isOpen && _uiPanel == null)
{
BuildUI();
}
gregCore.UI.GregUIManager.SetPanelActive("DevConsole", _isOpen);
}
private void BuildUI()
{
var builder = gregCore.UI.GregUIBuilder.Create("DevConsole")
.SetSize(600, 400);
_uiPanel = builder.Build();
// Note: Full log list and scrolling would need more UGUI components like ScrollRect
// For now, we initialize the panel and we can add labels dynamically
RefreshLogs();
}
private void RefreshLogs()
{
if (_uiPanel == null) return;
// In a real UGUI implementation, we'd update a Text component or instantiate labels in a content container
}
public void AddLog(string message, LogType type)
{
_logs.Add(new LogEntry { Message = message, Type = type, Time = DateTime.Now });
if (_logs.Count > 100) _logs.RemoveAt(0);
_scrollPosition.y = float.MaxValue;
RefreshLogs();
}
public void OnGUI()
{
if (!_isOpen) return;
_windowRect = GUI.Window(1337, _windowRect, (UnityEngine.GUI.WindowFunction)DrawWindow, "gregCore DevConsole");
// IMGUI OnGUI is now disabled to prevent stripping crashes.
// The UI is handled via BuildUI and UGUI.
}
private void DrawWindow(int windowId)
{
GUILayout.BeginVertical();
_scrollPosition = GUILayout.BeginScrollView(_scrollPosition);
foreach (var log in _logs)
{
GUILayout.Label($"[{log.Time:HH:mm:ss}] {log.Message}");
}
GUILayout.EndScrollView();
GUILayout.BeginHorizontal();
_inputCommand = GUILayout.TextField(_inputCommand);
if (GUILayout.Button("Send", GUILayout.Width(60f)))
{
if (!string.IsNullOrWhiteSpace(_inputCommand))
{
AddLog($"> {_inputCommand}", LogType.Log);
_inputCommand = "";
}
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUI.DragWindow();
// Legacy IMGUI method - no longer called
}
private struct LogEntry
+14 -15
View File
@@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using gregCore.Core.Models;
using gregCore.UI;
using UnityEngine;
namespace gregCore.PublicApi.Modules;
public sealed class GregUIModule
namespace gregCore.PublicApi.Modules
{
private readonly GregApiContext _ctx;
internal GregUIModule(GregApiContext ctx) => _ctx = ctx;
public sealed class GregUIModule
{
private readonly GregApiContext _ctx;
internal GregUIModule(GregApiContext ctx) => _ctx = ctx;
public void ShowToast(string message, float durationSeconds = 3f)
=> _ctx.EventBus.Publish("greg.ui.ShowToast", new EventPayload {
HookName = "greg.ui.ShowToast",
OccurredAtUtc = DateTime.UtcNow,
Data = new Dictionary<string, object> { ["message"] = message, ["duration"] = durationSeconds }
});
public GregUIBuilder CreateBuilder(string title) => GregUIBuilder.Create(title);
public void ShowNotification(string message) => ShowToast(message, 5f);
public void ShowNotification(string message, float duration = 3f)
{
// Integration with notification service
// GregServiceContainer.Get<Infrastructure.Settings.Services.GregNotificationService>()?.Show(message, duration);
}
}
}
+148
View File
@@ -0,0 +1,148 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime;
namespace gregCore.UI
{
/// <summary>
/// Builder for programmatic UGUI creation in gregCore.
/// Avoids IMGUI stripping issues in Unity 6.
/// </summary>
public class GregUIBuilder
{
private GameObject _activePanel;
public static GregUIBuilder Create(string title)
{
var builder = new GregUIBuilder();
builder._activePanel = GregUIManager.CreateUIObject($"Panel_{title}");
var rt = builder._activePanel.AddComponent<RectTransform>();
rt.sizeDelta = new Vector2(400, 300);
rt.anchoredPosition = Vector2.zero;
var img = builder._activePanel.AddComponent<Image>();
GregUITheme.ApplyBackground(img);
// Add Header
var header = GregUIManager.CreateUIObject("Header", builder._activePanel);
var hRt = header.AddComponent<RectTransform>();
hRt.anchorMin = new Vector2(0, 1);
hRt.anchorMax = new Vector2(1, 1);
hRt.pivot = new Vector2(0.5f, 1);
hRt.sizeDelta = new Vector2(0, GregUITheme.HeaderHeight);
hRt.anchoredPosition = Vector2.zero;
var hTxt = header.AddComponent<Text>();
hTxt.text = title;
GregUITheme.ApplyText(hTxt, true);
hTxt.alignment = TextAnchor.MiddleCenter;
// Add basic drag support
builder._activePanel.AddComponent<GregUIDragHandler>();
return builder;
}
public GregUIBuilder SetSize(float width, float height)
{
var rt = _activePanel.GetComponent<RectTransform>();
rt.sizeDelta = new Vector2(width, height);
return this;
}
public GregUIBuilder AddLabel(string text, int fontSize = 14)
{
var labelObj = GregUIManager.CreateUIObject("Label", _activePanel);
var txt = labelObj.AddComponent<Text>();
txt.text = text;
GregUITheme.ApplyText(txt);
if (fontSize != 14) txt.fontSize = fontSize;
txt.alignment = TextAnchor.UpperLeft;
var rt = labelObj.GetComponent<RectTransform>();
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.sizeDelta = new Vector2(-GregUITheme.Padding * 2, -GregUITheme.Padding * 2);
return this;
}
public GregUIBuilder AddButton(string label, Action onClick)
{
var btnObj = GregUIManager.CreateUIObject("Button", _activePanel);
var img = btnObj.AddComponent<Image>();
var btn = btnObj.AddComponent<Button>();
GregUITheme.ApplyButton(btn);
// Use UnityAction to wrap the system action safely for IL2CPP
btn.onClick.AddListener(new Action(onClick));
var textObj = GregUIManager.CreateUIObject("Text", btnObj);
var txt = textObj.AddComponent<Text>();
txt.text = label;
GregUITheme.ApplyText(txt);
txt.color = Color.black; // Better contrast on buttons usually, or use Accent
txt.alignment = TextAnchor.MiddleCenter;
return this;
}
public GregUIBuilder AddToggle(string label, bool currentValue, Action<bool> onChanged)
{
var toggleObj = GregUIManager.CreateUIObject("Toggle", _activePanel);
var toggle = toggleObj.AddComponent<Toggle>();
toggle.isOn = currentValue;
toggle.onValueChanged.AddListener(new Action<bool>(onChanged));
var labelObj = GregUIManager.CreateUIObject("Label", toggleObj);
var txt = labelObj.AddComponent<Text>();
txt.text = label;
GregUITheme.ApplyText(txt);
return this;
}
public GregUIBuilder AddSlider(string label, float min, float max, float currentValue, Action<float> onChanged)
{
var sliderObj = GregUIManager.CreateUIObject("Slider", _activePanel);
var slider = sliderObj.AddComponent<Slider>();
slider.minValue = min;
slider.maxValue = max;
slider.value = currentValue;
slider.onValueChanged.AddListener(new Action<float>(onChanged));
var labelObj = GregUIManager.CreateUIObject("Label", sliderObj);
var txt = labelObj.AddComponent<Text>();
txt.text = label;
GregUITheme.ApplyText(txt);
return this;
}
public GameObject Build()
{
var name = _activePanel.name.Replace("Panel_", "");
GregUIManager.RegisterPanel(name, _activePanel);
return _activePanel;
}
}
/// <summary>
/// Helper for dragging UI elements.
/// Must be registered in IL2CPP.
/// </summary>
public class GregUIDragHandler : MonoBehaviour
{
public GregUIDragHandler(IntPtr ptr) : base(ptr) { }
[HideFromIl2Cpp]
public void OnDrag(PointerEventData eventData)
{
transform.position += (Vector3)eventData.delta;
}
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using Il2CppInterop.Runtime.Attributes;
using Il2CppInterop.Runtime.InteropTypes.Fields;
namespace gregCore.UI
{
/// <summary>
/// Central manager for the gregCore UGUI framework.
/// Handles Canvas creation and EventSystem orchestration.
/// </summary>
public static class GregUIManager
{
private static GameObject _rootObject;
private static Canvas _canvas;
private static CanvasScaler _scaler;
private static GraphicRaycaster _raycaster;
private static readonly System.Collections.Generic.Dictionary<string, GameObject> _panels = new();
public static Canvas RootCanvas => _canvas;
public static GameObject RootObject => _rootObject;
public static void Initialize()
{
if (_rootObject != null) return;
_rootObject = new GameObject("gregCore_UI_Root");
UnityEngine.Object.DontDestroyOnLoad(_rootObject);
_canvas = _rootObject.AddComponent<Canvas>();
_canvas.renderMode = RenderMode.ScreenSpaceOverlay;
_canvas.sortingOrder = 999;
_scaler = _rootObject.AddComponent<CanvasScaler>();
_scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
_scaler.referenceResolution = new Vector2(1920, 1080);
_raycaster = _rootObject.AddComponent<GraphicRaycaster>();
EnsureEventSystem();
}
public static void RegisterPanel(string name, GameObject panel)
{
_panels[name] = panel;
}
public static void SetPanelActive(string name, bool active)
{
if (_panels.TryGetValue(name, out var panel) && panel != null)
{
panel.SetActive(active);
}
}
public static void TogglePanel(string name)
{
if (_panels.TryGetValue(name, out var panel) && panel != null)
{
panel.SetActive(!panel.activeSelf);
}
}
private static void EnsureEventSystem()
{
if (UnityEngine.Object.FindObjectOfType<EventSystem>() == null)
{
var eventSystemObj = new GameObject("gregCore_EventSystem");
eventSystemObj.AddComponent<EventSystem>();
eventSystemObj.AddComponent<StandaloneInputModule>();
UnityEngine.Object.DontDestroyOnLoad(eventSystemObj);
}
}
public static GameObject CreateUIObject(string name, GameObject parent = null)
{
var obj = new GameObject(name);
obj.layer = LayerMask.NameToLayer("UI");
obj.transform.SetParent(parent?.transform ?? _rootObject.transform, false);
return obj;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using UnityEngine;
namespace gregCore.UI
{
/// <summary>
/// Central theme registry for the Luminescent Architect Design System.
/// Standardized tokens for colors, spacing, and effects.
/// </summary>
public static class GregUITheme
{
// Colors
public static readonly Color Background = new Color(0.00f, 0.07f, 0.07f, 0.93f);
public static readonly Color Accent = new Color(0.38f, 0.96f, 0.85f, 1f); // #61F4D8
public static readonly Color Text = new Color(0.75f, 0.99f, 0.97f, 1f); // #C0FCF6
public static readonly Color Warning = new Color(0.93f, 0.25f, 0.27f, 1f); // #ED4245
public static readonly Color ButtonNormal = new Color(0.05f, 0.15f, 0.15f, 1f);
public static readonly Color ButtonHover = new Color(0.10f, 0.25f, 0.25f, 1f);
// Spacing
public static readonly float Padding = 10f;
public static readonly float HeaderHeight = 30f;
public static readonly int DefaultFontSize = 14;
public static readonly int HeaderFontSize = 16;
/// <summary>
/// Applies the theme colors to an Image component.
/// </summary>
public static void ApplyBackground(UnityEngine.UI.Image img) => img.color = Background;
/// <summary>
/// Applies the theme colors to a Text component.
/// </summary>
public static void ApplyText(UnityEngine.UI.Text txt, bool isHeader = false)
{
txt.color = Text;
txt.fontSize = isHeader ? HeaderFontSize : DefaultFontSize;
txt.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
}
/// <summary>
/// Applies the theme colors to a Button component.
/// </summary>
public static void ApplyButton(UnityEngine.UI.Button btn)
{
var colors = btn.colors;
colors.normalColor = ButtonNormal;
colors.highlightedColor = ButtonHover;
colors.pressedColor = Accent;
btn.colors = colors;
}
}
}
+25 -109
View File
@@ -2,69 +2,25 @@ using System;
using System.Collections.Generic;
using UnityEngine;
using gregCore.API;
using gregCore.UI;
namespace greg.UI.Settings
{
public class GregUIBuilder
{
public GregUIBuilder AddLabel(string text)
{
GUILayout.Label(text);
return this;
}
public GregUIBuilder AddToggle(string label, bool currentValue, Action<bool> onChanged)
{
bool newValue = GUILayout.Toggle(currentValue, label);
if (newValue != currentValue)
{
onChanged?.Invoke(newValue);
}
return this;
}
public GregUIBuilder AddSlider(string label, float min, float max, float currentValue, Action<float> onChanged)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, GUILayout.Width(150));
float newValue = GUILayout.HorizontalSlider(currentValue, min, max);
GUILayout.Label(newValue.ToString("F1"), GUILayout.Width(40));
GUILayout.EndHorizontal();
if (Math.Abs(newValue - currentValue) > 0.01f)
{
onChanged?.Invoke(newValue);
}
return this;
}
public GregUIBuilder AddButton(string label, Action onClick)
{
if (GUILayout.Button(label))
{
onClick?.Invoke();
}
return this;
}
}
public class GregSettingsHub : MonoBehaviour
{
private static GregSettingsHub? _instance;
private bool _isVisible = false;
private int _selectedTab = 0;
private GameObject? _uiPanel;
private class TabData
{
public string Id = string.Empty;
public string Label = string.Empty;
public Action<GregUIBuilder>? BuildFn;
public Action<gregCore.UI.GregUIBuilder>? BuildFn;
}
private static readonly List<TabData> _tabs = new();
private GUIStyle? _windowStyle;
private GUIStyle? _tabStyle;
private GregUIBuilder _builder = new GregUIBuilder();
public static void Initialize()
{
@@ -78,7 +34,7 @@ namespace greg.UI.Settings
}
}
public static void RegisterTab(string tabId, string label, Action<GregUIBuilder> buildFn)
public static void RegisterTab(string tabId, string label, Action<gregCore.UI.GregUIBuilder> buildFn)
{
if (!_tabs.Exists(t => t.Id == tabId))
{
@@ -95,7 +51,7 @@ namespace greg.UI.Settings
{
RegisterTab("greg.core", "Framework", builder =>
{
builder.AddLabel("gregCore v1.0.0.35-pre")
builder.AddLabel("gregCore v1.0.0.35")
.AddLabel("MelonLoader v0.6+")
.AddLabel($"Save Mode: {(frameworkSdk.GregFeatureGuard.IsVanillaSave ? "Vanilla" : "Greg")}")
.AddToggle("Verbose Startup Log", false, v => { })
@@ -122,8 +78,6 @@ namespace greg.UI.Settings
builder.AddToggle("Show Grid Lines", grid.ShowGridLines, v => grid.ShowGridLines = v)
.AddToggle("Show Sub-Grid", grid.ShowSubGrid, v => grid.ShowSubGrid = v)
.AddSlider("Sub-Grid Zoom Threshold", 1.0f, 10.0f, 5.0f, v => { })
.AddToggle("Build Mode Key: B", true, v => { })
.AddLabel($"Placed Racks: [unknown]")
.AddLabel($"Grid Size: 50x50")
.AddButton("Clear All Greg Racks", () => grid.ClearAll());
}
@@ -142,32 +96,12 @@ namespace greg.UI.Settings
});
builder.AddSlider("Auto-Save Interval (seconds)", 10f, 300f, greg.SaveEngine.GregSaveScheduler.AutoSaveIntervalSeconds, v => greg.SaveEngine.GregSaveScheduler.AutoSaveIntervalSeconds = v)
.AddToggle("Disable Vanilla Save (expert!)", false, v => { })
.AddToggle("Save Grid State", true, v => { })
.AddToggle("Save Server State", true, v => { })
.AddToggle("Save Network State", true, v => { })
.AddToggle("Save Cable State", true, v => { });
.AddToggle("Save Server State", true, v => { });
var engine = greg.SaveEngine.GregSaveEngine.Instance;
builder.AddLabel($"Last Save: [unknown]")
.AddLabel($"DB File: {(engine != null ? engine.DbPath : "None")}")
.AddButton("Save Now", () => engine?.SaveAll())
.AddButton("Open Save Folder", () => {
if (engine != null && !string.IsNullOrEmpty(engine.DbPath))
{
System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{engine.DbPath}\"");
}
});
});
RegisterTab("greg.lang", "Languages", builder =>
{
builder.AddLabel("Languages Registry (Lua, JS, Python, Go, Rust)");
});
RegisterTab("greg.debug", "Debug", builder =>
{
builder.AddLabel("Debug & Diagnose");
builder.AddLabel($"DB File: {(engine != null ? engine.DbPath : "None")}")
.AddButton("Save Now", () => engine?.SaveAll());
});
}
@@ -175,60 +109,42 @@ namespace greg.UI.Settings
{
if (Input.GetKeyDown(KeyCode.F8))
{
_isVisible = !_isVisible;
Toggle();
}
if (_isVisible && Input.GetKeyDown(KeyCode.Escape))
{
_isVisible = false;
Toggle();
}
}
private void OnGUI()
public void Toggle()
{
if (!_isVisible) return;
if (_windowStyle == null)
_isVisible = !_isVisible;
if (_isVisible && _uiPanel == null)
{
_windowStyle = GUI.skin.window;
_tabStyle = GUI.skin.button;
BuildUI();
}
GUI.Window(999, new Rect((Screen.width - 480) / 2, 100, 480, 500), (GUI.WindowFunction)DrawWindow, "gregCore Settings Hub");
GregUIManager.SetPanelActive("SettingsHub", _isVisible);
}
private void DrawWindow(int id)
private void BuildUI()
{
if (_tabs.Count == 0) return;
var builder = GregUIBuilder.Create("SettingsHub")
.SetSize(480, 500);
GUILayout.BeginHorizontal();
for (int i = 0; i < _tabs.Count; i++)
// In a real UGUI tab system, we'd create tab buttons and content areas
// For now, we build the first tab
if (_tabs.Count > 0)
{
if (GUILayout.Toggle(_selectedTab == i, _tabs[i].Label, _tabStyle))
{
_selectedTab = i;
}
_tabs[_selectedTab].BuildFn?.Invoke(builder);
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
if (_selectedTab >= 0 && _selectedTab < _tabs.Count)
{
_tabs[_selectedTab].BuildFn?.Invoke(_builder);
}
_uiPanel = builder.Build();
}
private Texture2D MakeTex(int width, int height, Color col)
public void OnGUI()
{
Color[] pix = new Color[width * height];
for (int i = 0; i < pix.Length; ++i)
{
pix[i] = col;
}
Texture2D result = new Texture2D(width, height);
result.SetPixels(pix);
result.Apply();
return result;
// IMGUI disabled
}
}
}