From e8c9fce40e3186488f7900d1b8451aac9a88dafe Mon Sep 17 00:00:00 2001 From: Demexis Date: Wed, 26 Feb 2025 01:31:16 +0200 Subject: [PATCH] feat: improved text-gizmos API. + Can change font size. + Can change text alignment (TextAnchor). + Can set background color. + Can set world scaling. --- Runtime/Gizmos/DebugX.base.cs | 52 ------- Runtime/Gizmos/DebugX.text.cs | 253 ++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 52 deletions(-) create mode 100644 Runtime/Gizmos/DebugX.text.cs diff --git a/Runtime/Gizmos/DebugX.base.cs b/Runtime/Gizmos/DebugX.base.cs index 6f4649f..201f021 100644 --- a/Runtime/Gizmos/DebugX.base.cs +++ b/Runtime/Gizmos/DebugX.base.cs @@ -222,58 +222,6 @@ namespace DCFApixels } #endregion - #region Text - [IN(LINE)] public DrawHandler Text(Vector3 position, object text) => Gizmo(new TextGizmo(position, text)); - private readonly struct TextGizmo : IGizmo - { - public readonly Vector3 Position; - public readonly string Text; - [IN(LINE)] - public TextGizmo(Vector3 position, object text) - { - Position = position; - Text = text.ToString(); - } - public IGizmoRenderer RegisterNewRenderer() { return new Renderer(); } - - #region Renderer - private class Renderer : IGizmoRenderer_UnityGizmos - { - private static GUIStyle _labelStyle; - private static GUIContent _labelDummy; - public int ExecuteOrder => default(UnlitMat).GetExecuteOrder(); - public bool IsStaticRender => false; - public void Prepare(Camera camera, GizmosList list) { } - public void Render(Camera camera, GizmosList list, CommandBuffer cb) { } - public void Render_UnityGizmos(Camera camera, GizmosList list) - { -#if UNITY_EDITOR - Color defaultColor = GUI.color; - if (_labelStyle == null || _labelDummy == null) - { - _labelStyle = GUI.skin.label; - _labelStyle.richText = false; - _labelDummy = new GUIContent(); - } - Handles.BeginGUI(); - foreach (ref readonly var item in list) - { - GUI.color = item.Color * GlobalColor; - _labelDummy.text = item.Value.Text; - if (!(HandleUtility.WorldToGUIPointWithDepth(item.Value.Position).z < 0f)) - { - GUI.Label(HandleUtility.WorldPointToSizedRect(item.Value.Position, _labelDummy, _labelStyle), _labelDummy, _labelStyle); - } - } - Handles.EndGUI(); - GUI.color = defaultColor; -#endif - } - } - #endregion - } - #endregion - // Base Renderers #region MeshRendererBase diff --git a/Runtime/Gizmos/DebugX.text.cs b/Runtime/Gizmos/DebugX.text.cs new file mode 100644 index 0000000..578dd42 --- /dev/null +++ b/Runtime/Gizmos/DebugX.text.cs @@ -0,0 +1,253 @@ +using DCFApixels.DebugXCore; +#if UNITY_EDITOR +using UnityEditor; +#endif +using UnityEngine; +using UnityEngine.Rendering; + +namespace DCFApixels { + using IN = System.Runtime.CompilerServices.MethodImplAttribute; + + public static partial class DebugX { + public readonly partial struct DrawHandler { + #region Text + + /// + /// Draw text at the world position. + /// Can pass any object where ToString() will be called. + /// + /// World position. + /// String or any other object. + [IN(LINE)] public DrawHandler Text(Vector3 position, object text) => Gizmo(new TextGizmo(new TextBuilder(position, text))); + + /// + /// Draw text at the world position. + /// Can pass any object where ToString() will be called. + /// + /// World position. + /// String or any other object. + /// Text font size. + [IN(LINE)] public DrawHandler Text(Vector3 position, object text, int fontSize) => Gizmo(new TextGizmo(new TextBuilder(position, text, fontSize))); + + /// + /// Draw text at the world position. + /// Can pass any object where ToString() will be called. + /// + /// World position. + /// String or any other object. + /// Text font size. + /// Text alignment. + [IN(LINE)] public DrawHandler Text(Vector3 position, object text, int fontSize, TextAnchor textAnchor) => Gizmo(new TextGizmo(new TextBuilder(position, text, fontSize, textAnchor))); + + /// + /// Use text builder to pass more details about how text-gizmo should be drawn. + /// + /// Settings with a builder pattern. + [IN(LINE)] public DrawHandler Text(TextBuilder textBuilder) => Gizmo(new TextGizmo(textBuilder)); + + private readonly struct TextGizmo : IGizmo { + private const int BACKGROUND_TEXTURE_WIDTH = 2; + private const int BACKGROUND_TEXTURE_HEIGHT = 2; + private const int BACKGROUND_TEXTURE_PIXELS_COUNT = BACKGROUND_TEXTURE_WIDTH * BACKGROUND_TEXTURE_HEIGHT; + + // TODO: Normally Texture2D should be destroyed when not needed anymore. Though it will live through entire app lifetime. What about editor? + private static Texture2D _backgroundTexture; + private static Color32[] _backgroundTexturePixels; + public readonly TextBuilder TextBuilderInstance; + [IN(LINE)] + public TextGizmo(TextBuilder textBuilder) { + TextBuilderInstance = textBuilder; + CheckTextureInstance(); + } + + /// + /// Should be called once after the app domain is cleared. + /// + private void CheckTextureInstance() { + if (_backgroundTexture != null) { + return; + } + + _backgroundTexture = new Texture2D(BACKGROUND_TEXTURE_WIDTH, BACKGROUND_TEXTURE_HEIGHT); + _backgroundTexturePixels = new Color32[BACKGROUND_TEXTURE_PIXELS_COUNT]; + _backgroundTexture.SetPixels32(_backgroundTexturePixels); + _backgroundTexture.Apply(); + } + + public IGizmoRenderer RegisterNewRenderer() { return new Renderer(); } + + #region Renderer + private class Renderer : IGizmoRenderer_UnityGizmos + { + private static GUIStyle _labelStyle; + private static GUIContent _labelDummy; + public int ExecuteOrder => default(UnlitMat).GetExecuteOrder(); + public bool IsStaticRender => false; + public void Prepare(Camera camera, GizmosList list) { } + public void Render(Camera camera, GizmosList list, CommandBuffer cb) { } + public void Render_UnityGizmos(Camera camera, GizmosList list) + { +#if UNITY_EDITOR + Color defaultColor = GUI.color; + if (_labelStyle == null || _labelDummy == null) + { + _labelStyle = GUI.skin.label; + _labelStyle.richText = false; + _labelDummy = new GUIContent(); + } + Handles.BeginGUI(); + foreach (ref readonly var item in list) + { + GUI.color = item.Color * GlobalColor; + _labelDummy.text = item.Value.TextBuilderInstance.Text.ToString(); + + if (item.Value.TextBuilderInstance.UseWorldScale) { + var zoom = 1f; + + var cam = GetCurrentCamera(); + if (cam != null) { + zoom = cam.orthographicSize; + } else { + var currentDrawingSceneView = SceneView.currentDrawingSceneView; + + if (currentDrawingSceneView != null + && SceneView.currentDrawingSceneView.camera != null) { + cam = currentDrawingSceneView.camera; + + if (camera != null) { + zoom = cam.orthographicSize; + } + } + } + + _labelStyle.fontSize = Mathf.FloorToInt(item.Value.TextBuilderInstance.FontSize / zoom); + } else { + _labelStyle.fontSize = item.Value.TextBuilderInstance.FontSize; + } + + _labelStyle.alignment = item.Value.TextBuilderInstance.TextAnchor; + + _labelStyle.normal = new GUIStyleState { + textColor = item.Color * GlobalColor, + }; + + if (item.Value.TextBuilderInstance.UseBackground) { + for (int i = 0; i < BACKGROUND_TEXTURE_PIXELS_COUNT; i++) { + _backgroundTexturePixels[i] = item.Value.TextBuilderInstance.BackgroundColor; + } + + _backgroundTexture.SetPixels32(_backgroundTexturePixels); + _backgroundTexture.Apply(); + + _labelStyle.normal.background = _backgroundTexture; + } + + if (!(HandleUtility.WorldToGUIPointWithDepth(item.Value.TextBuilderInstance.Position).z < 0f)) + { + GUI.Label(HandleUtility.WorldPointToSizedRect(item.Value.TextBuilderInstance.Position, _labelDummy, _labelStyle), _labelDummy, _labelStyle); + } + } + Handles.EndGUI(); + GUI.color = defaultColor; +#endif + } + } + #endregion + } + #endregion + + #region TextBuilder + /// + /// Set text gizmos instance settings using a builder pattern. + /// + public struct TextBuilder { + private const TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.MiddleLeft; + private const int DEFAULT_FONT_SIZE = 16; + + /// + /// Text world position. + /// + public Vector3 Position { get; set; } + + /// + /// Text. Uses ToString() of the passed object. + /// + public object Text { get; set; } + + /// + /// Font size. Default is . + /// + public int FontSize { get; set; } + + /// + /// Text alignment. Default is . + /// + public TextAnchor TextAnchor { get; set; } + + /// + /// Background texture color. + /// + public Color BackgroundColor { get; set; } + + /// + /// Flag to use background texture and background color when rendering text gizmo instance. + /// + public bool UseBackground { get; set; } + + /// + /// If set true - camera zooming will affect text scale to keep same size in the world. + /// + public bool UseWorldScale { get; set; } + + public TextBuilder(Vector3 position, object text, int fontSize = DEFAULT_FONT_SIZE, TextAnchor textAnchor = DEFAULT_TEXT_ANCHOR) : this() { + Position = position; + Text = text; + FontSize = fontSize; + TextAnchor = textAnchor; + } + + public TextBuilder SetPosition(Vector3 position) { + Position = position; + return this; + } + + public TextBuilder SetText(object text) { + Text = text; + return this; + } + + public TextBuilder SetFontSize(int fontSize) { + FontSize = fontSize; + return this; + } + + public TextBuilder SetTextAnchor(TextAnchor textAnchor) { + TextAnchor = textAnchor; + return this; + } + + public TextBuilder SetBackground(Color backgroundColor) { + UseBackground = true; + BackgroundColor = backgroundColor; + return this; + } + + public TextBuilder RemoveBackground() { + UseBackground = false; + return this; + } + + public TextBuilder SetWorldScaling() { + UseWorldScale = true; + return this; + } + + public TextBuilder RemoveWorldScaling() { + UseWorldScale = false; + return this; + } + } + #endregion + } + } +} \ No newline at end of file