diff --git a/Runtime/Resources/imui_default.shader b/Runtime/Resources/imui_default.shader index ec87373..1e297fd 100644 --- a/Runtime/Resources/imui_default.shader +++ b/Runtime/Resources/imui_default.shader @@ -7,6 +7,7 @@ [PerRendererData] _MaskEnable("Enable Masking", int) = 0 [PerRendererData] _MaskRect("Mask Rect", Vector) = (0, 0, 0, 0) [PerRendererData] _MaskCornerRadius("Mask Corner Radius", float) = 0 + [PerRendererData] _Contrast("Contrast", float) = 1 } SubShader @@ -50,6 +51,7 @@ bool _MaskEnable; float4 _MaskRect; float _MaskCornerRadius; + float _Contrast; // simplified signed distance round box from here: https://iquilezles.org/articles/distfunctions2d/ float sdf_round_box(in float2 p, in float2 s, in float r) @@ -75,10 +77,12 @@ col.a *= _MaskEnable ? 1 - saturate(sdf_round_box(i.vertex.xy - _MaskRect.xy, _MaskRect.zw, _MaskCornerRadius) * 2 + 1) : 1; + col *= i.color; + col.rgb = ((col.rgb - 0.5f) * (1 - _Contrast)) + 0.5f; - return i.color * col; + return col; } - + ENDCG } } diff --git a/Runtime/Scripts/Controls/Utilities.meta b/Runtime/Scripts/Controls/Extensions.meta similarity index 100% rename from Runtime/Scripts/Controls/Utilities.meta rename to Runtime/Scripts/Controls/Extensions.meta diff --git a/Runtime/Scripts/Controls/Layout/ImLayoutGroup.cs b/Runtime/Scripts/Controls/Extensions/ImLayoutExt.cs similarity index 60% rename from Runtime/Scripts/Controls/Layout/ImLayoutGroup.cs rename to Runtime/Scripts/Controls/Extensions/ImLayoutExt.cs index cde6efe..551f0d9 100644 --- a/Runtime/Scripts/Controls/Layout/ImLayoutGroup.cs +++ b/Runtime/Scripts/Controls/Extensions/ImLayoutExt.cs @@ -1,24 +1,41 @@ using Imui.Core; using UnityEngine; -namespace Imui.Controls.Layout +namespace Imui.Controls { - public static class ImLayoutGroup + public static class ImLayoutExt { - public static Vector2 GetAvailableSize(this ImGui gui) + public static Vector2 GetLayoutSize(this ImGui gui) { return gui.Layout.GetAvailableSize(); } - public static float GetAvailableWidth(this ImGui gui) + public static float GetLayoutWidth(this ImGui gui) { return gui.Layout.GetAvailableWidth(); } - public static float GetAvailableHeight(this ImGui gui) + public static float GetLayoutHeight(this ImGui gui) { return gui.Layout.GetAvailableHeight(); } + + public static ImRect GetLayoutBounds(this ImGui gui) + { + return gui.Layout.GetBoundsRect(); + } + + public static ImRect AddLayoutRect(this ImGui gui, float width, float height) + { + return gui.Layout.AddRect(width, height); + } + + public static ImRect AddLayoutRectWithSpacing(this ImGui gui, float width, float height) + { + gui.AddSpacingIfLayoutFrameNotEmpty(); + + return gui.Layout.AddRect(width, height); + } public static void BeginVertical(this ImGui gui, float width = 0.0f, float height = 0.0f) { diff --git a/Runtime/Scripts/Controls/Layout/ImLayoutGroup.cs.meta b/Runtime/Scripts/Controls/Extensions/ImLayoutExt.cs.meta similarity index 100% rename from Runtime/Scripts/Controls/Layout/ImLayoutGroup.cs.meta rename to Runtime/Scripts/Controls/Extensions/ImLayoutExt.cs.meta diff --git a/Runtime/Scripts/Utility/RectUtility.cs b/Runtime/Scripts/Controls/Extensions/ImRectExt.cs similarity index 53% rename from Runtime/Scripts/Utility/RectUtility.cs rename to Runtime/Scripts/Controls/Extensions/ImRectExt.cs index a708651..d77ce08 100644 --- a/Runtime/Scripts/Utility/RectUtility.cs +++ b/Runtime/Scripts/Controls/Extensions/ImRectExt.cs @@ -1,26 +1,16 @@ using Imui.Core; -using Imui.Styling; +using Imui.Controls.Styling; using UnityEngine; -namespace Imui.Utility +namespace Imui.Controls { - public static class RectUtility + public static class ImRectExt { public static Vector2 Max(this Vector2 vec, float x, float y) { return new Vector2(Mathf.Max(vec.x, x), Mathf.Max(vec.y, y)); } - public static Rect Intersection(this Rect rect, Rect other) - { - var x1 = Mathf.Max(rect.x, other.x); - var y1 = Mathf.Max(rect.y, other.y); - var x2 = Mathf.Min(rect.x + rect.width, other.x + other.width); - var y2 = Mathf.Min(rect.y + rect.height, other.y + other.height); - - return new Rect(x1, y1, x2 - x1, y2 - y1); - } - public static ImRect SplitTop(this ImRect rect, float height) { rect.Y += rect.H - height; @@ -29,48 +19,32 @@ public static ImRect SplitTop(this ImRect rect, float height) } - public static ImRect SplitTop(this ImRect rect, float height, out ImRect next) + public static ImRect SplitTop(this ImRect rect, float height, out ImRect bottom) { - next = rect; - next.H = rect.H - height; - rect.Y += next.H; + bottom = rect; + bottom.H = rect.H - height; + rect.Y += bottom.H; rect.H = height; return rect; } - public static ImRect SplitLeft(this ImRect rect, float width, out ImRect next) + public static ImRect SplitLeft(this ImRect rect, float width, out ImRect right) { - next = rect; - next.X += width; - next.W = rect.W - width; + right = rect; + right.X += width; + right.W = rect.W - width; rect.W = width; return rect; } - public static ImRect SplitLeft(this ImRect rect, float width, float space, out ImRect next) + public static ImRect SplitLeft(this ImRect rect, float width, float space, out ImRect right) { - next = rect; - next.X += width + space; - next.W = rect.W - width - space; + right = rect; + right.X += width + space; + right.W = rect.W - width - space; rect.W = width; return rect; } - - public static void AddPadding(this ref ImRect rect, float size) - { - rect.X += size; - rect.Y += size; - rect.W -= size * 2; - rect.H -= size * 2; - } - - public static void AddPadding(this ref ImRect rect, ImPadding padding) - { - rect.X += padding.Left; - rect.Y += padding.Bottom; - rect.W -= padding.Left + padding.Right; - rect.H -= padding.Top + padding.Bottom; - } public static ImRect WithAspect(this ImRect rect, float aspect) { @@ -92,17 +66,7 @@ public static ImRect WithPadding(this ImRect rect, float size) return rect; } - - public static ImRect WithPadding(this ImRect rect, float left, float top, float right, float bottom) - { - rect.X += left; - rect.Y += bottom; - rect.W -= left + right; - rect.H -= top + bottom; - - return rect; - } - + public static ImRect WithPadding(this ImRect rect, ImPadding padding) { rect.X += padding.Left; @@ -112,39 +76,58 @@ public static ImRect WithPadding(this ImRect rect, ImPadding padding) return rect; } - - public static void ApplyPadding(this ref ImRect rect, float padding) + + public static void AddPaddingToSize(ref Vector2 size, ImPadding padding) { - rect.X += padding; - rect.Y += padding; - rect.W -= padding * 2; - rect.H -= padding * 2; + size.x += padding.Left + padding.Right; + size.y += padding.Bottom + padding.Top; } - public static void ApplyPadding(this ref ImRect rect, ImPadding padding) + public static void AddPadding(this ref ImRect rect, ImPadding padding) { rect.X += padding.Left; rect.Y += padding.Bottom; rect.W -= padding.Left + padding.Right; rect.H -= padding.Top + padding.Bottom; } - - public static ImRect WithMargin(this ImRect rect, ImPadding margin) - { - rect.X -= margin.Left; - rect.Y -= margin.Bottom; - rect.W += margin.Left + margin.Right; - rect.H += margin.Top + margin.Bottom; - - return rect; - } public static ImRect ScaleFromCenter(this ImRect rect, float scale) { + var w = rect.W; + var h = rect.H; + rect.W *= scale; rect.H *= scale; - rect.X += rect.W * 0.5f; - rect.Y += rect.H * 0.5f; + rect.X += (w - rect.W) * 0.5f; + rect.Y += (h - rect.H) * 0.5f; + + return rect; + } + + public static ImRect Clamp(ImRect bounds, ImRect rect) + { + rect.W = Mathf.Min(rect.W, bounds.W); + rect.H = Mathf.Min(rect.H, bounds.H); + + if (rect.X < bounds.X) + { + rect.X += bounds.X - rect.X; + } + + if (rect.Right > bounds.Right) + { + rect.X -= rect.Right - bounds.Right; + } + + if (rect.Y < bounds.Y) + { + rect.Y += bounds.Y - rect.Y; + } + + if (rect.Top > bounds.Top) + { + rect.Y -= rect.Top - bounds.Top; + } return rect; } diff --git a/Runtime/Scripts/Utility/RectUtility.cs.meta b/Runtime/Scripts/Controls/Extensions/ImRectExt.cs.meta similarity index 100% rename from Runtime/Scripts/Utility/RectUtility.cs.meta rename to Runtime/Scripts/Controls/Extensions/ImRectExt.cs.meta diff --git a/Runtime/Scripts/Controls/ImBox.cs b/Runtime/Scripts/Controls/ImBox.cs new file mode 100644 index 0000000..264bfac --- /dev/null +++ b/Runtime/Scripts/Controls/ImBox.cs @@ -0,0 +1,25 @@ +using System; +using Imui.Core; +using UnityEngine; + +namespace Imui.Controls +{ + public static class ImBox + { + public static void Box(this ImGui gui, ImRect rect, in ImBoxStyle style) + { + gui.Canvas.RectWithOutline(rect, style.BackColor, style.BorderColor, style.BorderWidth, style.BorderRadius); + } + } + + + [Serializable] + public struct ImBoxStyle + { + public Color32 BackColor; + public Color32 FrontColor; + public Color32 BorderColor; + public float BorderWidth; + public ImRectRadius BorderRadius; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImBox.cs.meta b/Runtime/Scripts/Controls/ImBox.cs.meta new file mode 100644 index 0000000..840b7ff --- /dev/null +++ b/Runtime/Scripts/Controls/ImBox.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fac8c755633e483c92a38365404de3ae +timeCreated: 1718317779 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImButton.cs b/Runtime/Scripts/Controls/ImButton.cs index 73f92eb..e1cec7a 100644 --- a/Runtime/Scripts/Controls/ImButton.cs +++ b/Runtime/Scripts/Controls/ImButton.cs @@ -1,76 +1,75 @@ using System; using Imui.Core; using Imui.IO.Events; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls { public static class ImButton { - public static ImButtonStyle Style = ImButtonStyle.Default; - - public static bool ButtonFitted(this ImGui gui, in ReadOnlySpan label) + public static ImRect GetRect(ImGui gui, ImSize size, ReadOnlySpan label) { - gui.AddControlSpacing(); + if (size.Type == ImSizeType.Fit) + { + var textSettings = GetTextSettings(); + var textSize = gui.MeasureTextSize(label, in textSettings); + var rectSize = textSize; - var textSettings = GetTextSettings(); - var textSize = gui.MeasureTextSize(label, in textSettings); - var rect = gui.Layout.AddRect(Style.GetButtonSize(textSize)); - return Button(gui, label, in rect); - } + rectSize.x += ImTheme.Active.Button.Padding.Horizontal; + rectSize.y += ImTheme.Active.Controls.ExtraRowHeight; - public static bool Button(this ImGui gui, in ReadOnlySpan label) - { - gui.AddControlSpacing(); + return gui.Layout.AddRect(rectSize); + } - var width = gui.Layout.GetAvailableWidth(); - var height = Style.GetButtonHeight(gui.GetRowHeight()); - var rect = gui.Layout.AddRect(width, height); - return Button(gui, label, in rect); + return ImControls.GetRowRect(gui, size); } - - public static bool Button(this ImGui gui, in ReadOnlySpan label, float width, float height) + + public static bool Button(this ImGui gui, ReadOnlySpan label, ImSize size = default) { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(width, height); - return Button(gui, label, in rect); + gui.AddSpacingIfLayoutFrameNotEmpty(); + + var rect = GetRect(gui, size, label); + return Button(gui, label, rect); } - public static bool Button(this ImGui gui, in ReadOnlySpan label, Vector2 size) + public static bool Button(this ImGui gui, ReadOnlySpan label, ImRect rect) { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(size); - return Button(gui, label, in rect); + return Button(gui, gui.GetNextControlId(), label, rect, out _); } - - public static bool Button(this ImGui gui, in ReadOnlySpan label, in ImRect rect) + + public static bool Button(this ImGui gui, ImRect rect, out ImButtonState state) { - var clicked = Button(gui, in rect, out var state); - var textSettings = GetTextSettings(); - var textColor = Style.GetStyle(state).FrontColor; - gui.Canvas.Text(in label, textColor, Style.GetContentRect(rect), in textSettings); - return clicked; + return Button(gui, gui.GetNextControlId(), rect, out state); } - public static bool Button(this ImGui gui, in ImRect rect, out ImButtonState state) + public static bool Button(this ImGui gui, uint id, ReadOnlySpan label, ImRect rect, out ImButtonState state) { - var id = gui.GetNextControlId(); - var clicked = Button(gui, id, in rect, out state); + var clicked = Button(gui, id, rect, out state); + var textSettings = GetTextSettings(); + var textColor = GetStateFontColor(state); + var textRect = GetContentRect(rect); + + gui.Canvas.Text(label, textColor, textRect, in textSettings); + return clicked; } - public static bool Button(this ImGui gui, uint id, in ImRect rect, out ImButtonState state) + public static bool Button(this ImGui gui, uint id, ImRect rect, out ImButtonState state) { var hovered = gui.IsControlHovered(id); var pressed = gui.IsControlActive(id); var clicked = false; + gui.RegisterControl(id, rect); + state = pressed ? ImButtonState.Pressed : hovered ? ImButtonState.Hovered : ImButtonState.Normal; - gui.DrawBox(in rect, Style.GetStyle(state)); + gui.Box(rect, GetStateBoxStyle(state)); + + if (gui.IsReadOnly) + { + return false; + } ref readonly var evt = ref gui.Input.MouseEvent; switch (evt.Type) @@ -96,8 +95,6 @@ public static bool Button(this ImGui gui, uint id, in ImRect rect, out ImButtonS } break; } - - gui.RegisterControl(id, rect); return clicked; } @@ -106,7 +103,7 @@ public static bool InvisibleButton(this ImGui gui, ImRect rect, bool actOnPress { var id = gui.GetNextControlId(); - return InvisibleButton(gui, id, rect); + return InvisibleButton(gui, id, rect, actOnPress); } public static bool InvisibleButton(this ImGui gui, uint id, ImRect rect, bool actOnPress = false) @@ -115,6 +112,13 @@ public static bool InvisibleButton(this ImGui gui, uint id, ImRect rect, bool ac var pressed = gui.IsControlActive(id); var clicked = false; + gui.RegisterControl(id, rect); + + if (gui.IsReadOnly) + { + return false; + } + ref readonly var evt = ref gui.Input.MouseEvent; switch (evt.Type) { @@ -138,120 +142,84 @@ public static bool InvisibleButton(this ImGui gui, uint id, ImRect rect, bool ac } break; } - - gui.RegisterControl(id, rect); return clicked; } - public static ImTextSettings GetTextSettings() - { - return new ImTextSettings(ImControls.Style.TextSize, Style.Alignment); + public static ImRect GetContentRect(ImRect rect) + { + return rect.WithPadding(ImTheme.Active.Button.Padding); } - } - - public enum ImButtonState - { - Normal, - Hovered, - Pressed - } - - [Serializable] - public struct ImButtonStyle - { - public static readonly ImButtonStyle Default = new ImButtonStyle() - { - Padding = 2.0f, - Alignment = new ImTextAlignment(0.5f, 0.5f), - Normal = new ImBoxStyle() - { - BackColor = ImColors.Gray7, - BorderColor = ImColors.Black, - FrontColor = ImColors.Black, - BorderRadius = 3, - BorderWidth = 1 - }, - Hovered = new ImBoxStyle() - { - BackColor = ImColors.Gray8, - BorderColor = ImColors.Gray1, - FrontColor = ImColors.Gray1, - BorderRadius = 3, - BorderWidth = 1 - }, - Pressed = new ImBoxStyle() - { - BackColor = ImColors.Gray6, - BorderColor = ImColors.Black, - FrontColor = ImColors.Black, - BorderRadius = 3, - BorderWidth = 1 - } - }; - - public ImBoxStyle Normal; - public ImBoxStyle Hovered; - public ImBoxStyle Pressed; - public ImPadding Padding; - public ImTextAlignment Alignment; - public Vector2 GetButtonSize(Vector2 contentSize) + public static Color32 GetStateFontColor(ImButtonState state) { - return new Vector2( - contentSize.x + Padding.Horizontal, - contentSize.y + Padding.Vertical); + ref readonly var stateStyle = ref GetStateStyle(state); + return stateStyle.FrontColor; } - public float GetButtonHeight(float contentHeight) + public static ImBoxStyle GetStateBoxStyle(ImButtonState state) { - return contentHeight + Padding.Vertical; + ref readonly var style = ref ImTheme.Active.Button; + ref readonly var stateStyle = ref GetStateStyle(state); + + return new ImBoxStyle + { + BackColor = stateStyle.BackColor, + FrontColor = stateStyle.FrontColor, + BorderColor = stateStyle.BorderColor, + BorderWidth = style.BorderWidth, + BorderRadius = style.BorderRadius + }; } - public ImRect GetContentRect(ImRect buttonRect) - { - return buttonRect.WithPadding(Padding); - } - - public ImBoxStyle GetStyle(ImButtonState state) + public static ref readonly ImButtonStateStyle GetStateStyle(ImButtonState state) { + ref readonly var style = ref ImTheme.Active.Button; + switch (state) { case ImButtonState.Hovered: - return Hovered; + return ref style.Hovered; case ImButtonState.Pressed: - return Pressed; + return ref style.Pressed; default: - return Normal; + return ref style.Normal; } } - - public void SetBorderRadius(ImRectRadius radius) + + public static ImTextSettings GetTextSettings() { - Normal.BorderRadius = radius; - Hovered.BorderRadius = radius; - Pressed.BorderRadius = radius; - } + ref readonly var style = ref ImTheme.Active.Button; - public void SetBorderWidth(float width) - { - Normal.BorderWidth = width; - Hovered.BorderWidth = width; - Pressed.BorderWidth = width; + return new ImTextSettings(ImTheme.Active.Controls.TextSize, style.Alignment, style.TextWrap); } + } - public void SetTint(Color32 backColor, Color32 frontColor) - { - Color.RGBToHSV(backColor, out var h, out var s, out var v); - - Normal.BackColor = backColor; - Normal.FrontColor = frontColor; - - Hovered.BackColor = Color.HSVToRGB(h, s, v * 1.1f); - Hovered.FrontColor = frontColor; + public enum ImButtonState + { + Normal, + Hovered, + Pressed + } - Pressed.BackColor = Color.HSVToRGB(h, s, v * 0.9f); - Pressed.FrontColor = frontColor; - } + [Serializable] + public struct ImButtonStateStyle + { + public Color32 BackColor; + public Color32 FrontColor; + public Color32 BorderColor; + } + + [Serializable] + public struct ImButtonStyle + { + public ImButtonStateStyle Normal; + public ImButtonStateStyle Hovered; + public ImButtonStateStyle Pressed; + public float BorderWidth; + public ImRectRadius BorderRadius; + public ImPadding Padding; + public ImTextAlignment Alignment; + public bool TextWrap; } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImCheckbox.cs b/Runtime/Scripts/Controls/ImCheckbox.cs new file mode 100644 index 0000000..f613582 --- /dev/null +++ b/Runtime/Scripts/Controls/ImCheckbox.cs @@ -0,0 +1,134 @@ +using System; +using Imui.Core; +using Imui.Controls.Styling; +using UnityEngine; + +namespace Imui.Controls +{ + public static class ImCheckbox + { + public static ImRect GetRect(ImGui gui, ImSize size, ReadOnlySpan label = default) + { + switch (size.Type) + { + case ImSizeType.Fixed: + return gui.Layout.AddRect(size.Width, size.Height); + default: + var boxSize = GetBoxSize(gui); + + if (label.IsEmpty) + { + return gui.Layout.AddRect(boxSize, boxSize); + } + + var textSettings = GetTextSettings(); + var textSize = gui.MeasureTextSize(label, in textSettings); + var width = boxSize + ImTheme.Active.Controls.InnerSpacing + textSize.x; + var height = Mathf.Max(boxSize, textSize.y); + + if (size.Type != ImSizeType.Fit) + { + width = Mathf.Max(gui.GetLayoutWidth(), width); + } + + return gui.Layout.AddRect(width, height); + } + } + + public static void Checkbox(this ImGui gui, ref bool value, ImSize size = default) + { + gui.AddSpacingIfLayoutFrameNotEmpty(); + + var rect = GetRect(gui, size); + Checkbox(gui, ref value, rect); + } + + public static void Checkbox(this ImGui gui, ref bool value, ReadOnlySpan label, ImSize size = default) + { + gui.AddSpacingIfLayoutFrameNotEmpty(); + + var rect = GetRect(gui, size, label); + Checkbox(gui, ref value, label, rect); + } + + public static void Checkbox(this ImGui gui, ref bool value, ReadOnlySpan label, ImRect rect) + { + var id = gui.GetNextControlId(); + var boxSize = GetBoxSize(gui); + var boxRect = rect.SplitLeft(boxSize, out var textRect).WithAspect(1.0f); + + Checkbox(gui, id, ref value, boxRect); + + if (label.IsEmpty) + { + return; + } + + var textSettings = GetTextSettings(); + + textRect.X += ImTheme.Active.Controls.InnerSpacing; + textRect.W -= ImTheme.Active.Controls.InnerSpacing; + gui.Canvas.Text(label, ImTheme.Active.Text.Color, textRect, textSettings); + + if (gui.InvisibleButton(id, textRect)) + { + value = !value; + } + } + + public static void Checkbox(this ImGui gui, ref bool value, ImRect rect) + { + var id = gui.GetNextControlId(); + Checkbox(gui, id, ref value, rect); + } + + public static void Checkbox(this ImGui gui, uint id, ref bool value, ImRect rect) + { + var clicked = gui.Button(id, rect, out var state); + var frontColor = ImButton.GetStateFontColor(state); + + if (value) + { + var checkmarkRect = rect.ScaleFromCenter(ImTheme.Active.Checkbox.CheckmarkScale); + var checkmarkStrokeWidth = checkmarkRect.W * 0.2f; + + DrawCheckmark(gui.Canvas, checkmarkRect, frontColor, checkmarkStrokeWidth); + } + + if (clicked) + { + value = !value; + } + } + + public static void DrawCheckmark(ImCanvas canvas, ImRect rect, Color32 color, float thickness) + { + ReadOnlySpan path = stackalloc Vector2[3] + { + rect.GetPointAtNormalPosition(0.00f, 0.60f), + rect.GetPointAtNormalPosition(0.35f, 0.15f), + rect.GetPointAtNormalPosition(1.00f, 0.80f) + }; + + canvas.LineMiter(path, color, false, thickness); + } + + public static float GetBoxSize(ImGui gui) + { + return gui.GetRowHeight(); + } + + public static ImTextSettings GetTextSettings() + { + return new ImTextSettings(ImTheme.Active.Controls.TextSize, ImTheme.Active.Checkbox.TextAlignment, ImTheme.Active.Checkbox.WrapText); + } + } + + [Serializable] + public struct ImCheckboxStyle + { + public float CheckmarkScale; + public ImTextAlignment TextAlignment; + public bool WrapText; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImCheckmark.cs.meta b/Runtime/Scripts/Controls/ImCheckbox.cs.meta similarity index 100% rename from Runtime/Scripts/Controls/ImCheckmark.cs.meta rename to Runtime/Scripts/Controls/ImCheckbox.cs.meta diff --git a/Runtime/Scripts/Controls/ImCheckmark.cs b/Runtime/Scripts/Controls/ImCheckmark.cs deleted file mode 100644 index 916bd03..0000000 --- a/Runtime/Scripts/Controls/ImCheckmark.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using Imui.Core; -using Imui.Styling; -using Imui.Utility; -using UnityEngine; - -namespace Imui.Controls -{ - public static class ImCheckmark - { - public static ImCheckmarkStyle Style = ImCheckmarkStyle.Default; - - public static void Checkmark(this ImGui gui, ref bool value) - { - gui.AddControlSpacing(); - - var id = gui.GetNextControlId(); - var size = GetCheckmarkBoxSize(gui); - var rect = gui.Layout.AddRect(size, size); - Checkmark(gui, id, ref value, in rect); - } - - public static void Checkmark(this ImGui gui, ref bool value, in ReadOnlySpan label) - { - gui.AddControlSpacing(); - - var textSettings = GetTextSettings(); - var textSize = gui.MeasureTextSize(in label, in textSettings); - var checkmarkBoxSize = GetCheckmarkBoxSize(gui); - var rect = gui.Layout.AddRect( - checkmarkBoxSize + ImControls.Style.InnerSpacing + textSize.x, - Mathf.Max(textSize.y, checkmarkBoxSize)); - Checkmark(gui, ref value, in label, in rect); - } - - public static void Checkmark(this ImGui gui, ref bool value, in ReadOnlySpan label, float width, float height) - { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(width, height); - Checkmark(gui, ref value, in label, in rect); - } - - public static void Checkmark(this ImGui gui, ref bool value, in ReadOnlySpan label, Vector2 size) - { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(size); - Checkmark(gui, ref value, in label, in rect); - } - - public static void Checkmark(this ImGui gui, ref bool value, in ReadOnlySpan label, in ImRect rect) - { - var id = gui.GetNextControlId(); - var checkmarkBox = rect.SplitLeft(GetCheckmarkBoxSize(gui), out var textRect).WithAspect(1.0f); - Checkmark(gui, id, ref value, in checkmarkBox); - - textRect.X += ImControls.Style.InnerSpacing; - textRect.W -= ImControls.Style.InnerSpacing; - gui.Canvas.Text(in label, Style.TextColor, textRect, GetTextSettings()); - - if (gui.InvisibleButton(id, textRect)) - { - value = !value; - } - } - - public static void Checkmark(this ImGui gui, ref bool value, in ImRect rect) - { - var id = gui.GetNextControlId(); - Checkmark(gui, id, ref value, in rect); - } - - public static void Checkmark(this ImGui gui, uint id, ref bool value, in ImRect rect) - { - using var _ = new ImStyleScope(ref ImButton.Style, Style.Button); - - var clicked = gui.Button(id, in rect, out var state); - var style = Style.Button.GetStyle(state); - - if (value) - { - var checkmarkRect = Style.Button.GetContentRect(rect); - var checkmarkStrokeWidth = checkmarkRect.W * 0.2f; - - DrawCheckMark(gui.Canvas, checkmarkRect, style.FrontColor, checkmarkStrokeWidth); - } - - if (clicked) - { - value = !value; - } - } - - public static void DrawCheckMark(ImCanvas canvas, ImRect rect, Color32 color, float thickness) - { - ReadOnlySpan path = stackalloc Vector2[3] - { - rect.GetPointAtNormalPosition(0.00f, 0.60f), - rect.GetPointAtNormalPosition(0.35f, 0.15f), - rect.GetPointAtNormalPosition(1.00f, 0.80f) - }; - - canvas.LineMiter(path, color, false, thickness); - } - - public static ImTextSettings GetTextSettings() - { - return new ImTextSettings(ImControls.Style.TextSize, Style.Button.Alignment); - } - - public static float GetCheckmarkBoxSize(ImGui gui) - { - return gui.GetRowHeight(); - } - } - - public struct ImCheckmarkStyle - { - public static readonly ImCheckmarkStyle Default = CreateDefaultStyle(); - - public static ImCheckmarkStyle CreateDefaultStyle() - { - var style = new ImCheckmarkStyle() - { - Button = ImButtonStyle.Default, - TextColor = ImColors.Black - }; - - style.Button.Padding = 4.0f; - return style; - } - - public ImButtonStyle Button; - public Color32 TextColor; - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImControls.cs b/Runtime/Scripts/Controls/ImControls.cs index c6027ab..82a9233 100644 --- a/Runtime/Scripts/Controls/ImControls.cs +++ b/Runtime/Scripts/Controls/ImControls.cs @@ -1,26 +1,27 @@ using System; +using Imui.Controls.Styling; using Imui.Core; -using UnityEngine; namespace Imui.Controls { // TODO (artem-s): implement table layout helper public static class ImControls { - public static ImControlsStyle Style = ImControlsStyle.Default; - - [Obsolete] - public static float GetTextSize(this ImGui gui) + public static ImRect GetRowRect(ImGui gui, ImSize size) { - return Style.TextSize; + return size.Type switch + { + ImSizeType.Fixed => gui.Layout.AddRect(size.Width, size.Height), + _ => gui.Layout.AddRect(gui.Layout.GetAvailableWidth(), gui.GetRowHeight()) + }; } public static float GetRowHeight(this ImGui gui) { - return gui.TextDrawer.GetLineHeight(Style.TextSize); + return gui.TextDrawer.GetLineHeight(ImTheme.Active.Controls.TextSize) + ImTheme.Active.Controls.ExtraRowHeight; } - public static void AddControlSpacing(this ImGui gui) + public static void AddSpacingIfLayoutFrameNotEmpty(this ImGui gui) { ref readonly var frame = ref gui.Layout.GetFrame(); if (frame.Size.x != 0 || frame.Size.y != 0) @@ -31,55 +32,33 @@ public static void AddControlSpacing(this ImGui gui) public static void AddSpacing(this ImGui gui) { - gui.Layout.AddSpace(Style.Spacing); + gui.Layout.AddSpace(ImTheme.Active.Controls.ControlsSpacing); } public static void AddSpacing(this ImGui gui, float space) { gui.Layout.AddSpace(space); } - - public static void DrawBox(this ImGui gui, in ImRect rect, in ImBoxStyle style) - { - gui.Canvas.RectWithOutline(rect, style.BackColor, style.BorderColor, style.BorderWidth, style.BorderRadius); - } - public static void BeginIdent(this ImGui gui) + public static void BeginIndent(this ImGui gui) { - gui.Layout.AddIdent(Style.Ident); + gui.Layout.AddIndent(ImTheme.Active.Controls.Indent); } - public static void EndIdent(this ImGui gui) + public static void EndIndent(this ImGui gui) { - gui.Layout.AddIdent(-Style.Ident); + gui.Layout.AddIndent(-ImTheme.Active.Controls.Indent); } } + [Serializable] public struct ImControlsStyle { - public static readonly ImControlsStyle Default = new ImControlsStyle() - { - TextSize = 26, - Spacing = 4, - InnerSpacing = 2, - ScrollSpeedScale = 6, - Ident = 20 - }; - + public float ExtraRowHeight; public float TextSize; - public float Spacing; + public float ControlsSpacing; public float InnerSpacing; public float ScrollSpeedScale; - public float Ident; - } - - [Serializable] - public struct ImBoxStyle - { - public Color32 BackColor; - public Color32 FrontColor; - public Color32 BorderColor; - public float BorderWidth; - public ImRectRadius BorderRadius; + public float Indent; } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImDropdown.cs b/Runtime/Scripts/Controls/ImDropdown.cs index 79ac3f8..69e8186 100644 --- a/Runtime/Scripts/Controls/ImDropdown.cs +++ b/Runtime/Scripts/Controls/ImDropdown.cs @@ -1,6 +1,6 @@ using System; using Imui.Core; -using Imui.Styling; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls @@ -10,144 +10,166 @@ public struct ImDropdownState public bool Open; } - // TODO (artem-s): add optional search field - // TODO (artem-s): maybe move panel drawing into separate control for reusing public static class ImDropdown { - public static ImDropdownStyle Style = ImDropdownStyle.Default; - - public static bool Dropdown(this ImGui gui, ref int selected, in ReadOnlySpan options) + public static void BeginDropdown(this ImGui gui, ReadOnlySpan label, out bool open, ImSize size = default) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); - var rect = gui.Layout.AddRect(gui.Layout.GetAvailableWidth(), GetControlHeight(gui)); - return Dropdown(gui, ref selected, in options, in rect); + var rect = ImControls.GetRowRect(gui, size); + BeginDropdown(gui, label, out open, rect); } - public static bool Dropdown(this ImGui gui, ref int selected, in ReadOnlySpan options, float width, float height) + public static void BeginDropdown(this ImGui gui, ReadOnlySpan label, out bool open, ImRect rect) { - gui.AddControlSpacing(); + gui.PushId(label); - var rect = gui.Layout.AddRect(width, height); - return Dropdown(gui, ref selected, in options, in rect); + var id = gui.GetNextControlId(); + ref var state = ref gui.Storage.Get(id); + + var clicked = DropdownButton(gui, id, label, rect); + if (clicked) + { + state.Open = !state.Open; + } + + open = state.Open; } - - public static bool Dropdown(this ImGui gui, ref int selected, in ReadOnlySpan options, Vector2 size) + + public static void EndDropdown(this ImGui gui) { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(size); - return Dropdown(gui, ref selected, in options, in rect); + gui.PopId(); } + + public static bool Dropdown(this ImGui gui, ref int selected, ReadOnlySpan options, ImSize size = default, ReadOnlySpan defaultLabel = default) + { + gui.AddSpacingIfLayoutFrameNotEmpty(); - public static bool Dropdown(this ImGui gui, ref int selected, in ReadOnlySpan options, in ImRect rect) + var rect = ImControls.GetRowRect(gui, size); + return Dropdown(gui, ref selected, options, rect, defaultLabel); + } + + public static bool Dropdown(this ImGui gui, ref int selected, ReadOnlySpan options, ImRect rect, ReadOnlySpan defaultLabel = default) { var id = gui.GetNextControlId(); - return Dropdown(gui, id, ref selected, in options, in rect); + return Dropdown(gui, id, ref selected, options, rect, defaultLabel); } - public static bool Dropdown(this ImGui gui, uint id, ref int selected, in ReadOnlySpan options, in ImRect rect) + public static bool Dropdown(this ImGui gui, uint id, ref int selected, ReadOnlySpan options, ImRect rect, ReadOnlySpan defaultLabel = default) { ref var state = ref gui.Storage.Get(id); - var text = selected < 0 || selected >= options.Length ? string.Empty : options[selected]; - var clicked = gui.Select(text, rect); + var text = selected < 0 || selected >= options.Length ? defaultLabel : options[selected]; + var clicked = DropdownButton(gui, id, text, rect); var changed = false; - var closeClicked = false; + var closed = false; if (state.Open) { - var contentHeight = gui.GetRowHeight(); - var optionButtonHeight = Style.OptionButton.GetButtonHeight(contentHeight); - var totalSpacingHeight = Style.OptionsButtonsSpacing * (options.Length - 1); - var panelHeight = ImPanel.Style.GetHeight(Mathf.Min(Style.MaxPanelHeight, options.Length * optionButtonHeight + totalSpacingHeight)); - var panelRect = new ImRect(rect.X, rect.Y - panelHeight, rect.W, panelHeight); - - gui.Canvas.PushNoClipRect(); - gui.Canvas.PushNoRectMask(); - - gui.BeginPopup(); - gui.BeginPanel(in panelRect); + var position = new Vector2(rect.X, rect.Y); + var width = rect.W; - var optionButtonWidth = gui.Layout.GetAvailableWidth(); + DropdownOptionsPanel(gui, id, ref selected, options, position, width, out closed, out changed); + } + + if (clicked || closed || changed) + { + state.Open = !state.Open; + } - using (new ImStyleScope(ref ImControls.Style)) - { - ImControls.Style.Spacing = Style.OptionsButtonsSpacing; + return changed; + } + + public static void DropdownOptionsPanel(ImGui gui, uint id, ref int selected, ReadOnlySpan options, Vector2 position, float width, out bool closed, out bool changed) + { + changed = false; + + ref readonly var style = ref ImTheme.Active.Dropdown; + + var optionButtonHeight = gui.GetRowHeight(); + var totalSpacingHeight = style.OptionsButtonsSpacing * (options.Length - 1); + var panelHeight = ImPanel.GetEnclosingHeight(Mathf.Min(style.MaxPanelHeight, options.Length * optionButtonHeight + totalSpacingHeight)); + var panelRect = new ImRect(position.x, position.y - panelHeight, width, panelHeight); + + gui.Canvas.PushNoClipRect(); + gui.Canvas.PushNoRectMask(); + + gui.PushId(id); + gui.BeginPopup(); + gui.BeginPanel(panelRect); + gui.BeginScrollable(); + + var optionButtonWidth = gui.Layout.GetAvailableWidth(); + + using (new ImStyleScope(ref ImTheme.Active.Controls)) + { + ImTheme.Active.Controls.ControlsSpacing = style.OptionsButtonsSpacing; - for (int i = 0; i < options.Length; ++i) - { - var isSelected = selected == i; - var style = isSelected ? Style.OptionButtonSelected : Style.OptionButton; + for (int i = 0; i < options.Length; ++i) + { + var isSelected = selected == i; + var optionStyle = isSelected ? style.OptionButtonSelected : style.OptionButton; - using (new ImStyleScope(ref ImButton.Style, style)) + using (new ImStyleScope(ref ImTheme.Active.Button, optionStyle)) + { + if (gui.Button(options[i], (optionButtonWidth, optionButtonHeight))) { - if (gui.Button(options[i], optionButtonWidth, optionButtonHeight)) - { - selected = i; - changed = true; - } + selected = i; + changed = true; } } } - - gui.EndPanel(); - gui.EndPopup(out closeClicked); - - gui.Canvas.PopRectMask(); - gui.Canvas.PopClipRect(); } - if (clicked || closeClicked || changed) - { - state.Open = !state.Open; - } - - return changed; + gui.EndScrollable(); + gui.EndPanel(); + gui.EndPopup(out closed); + gui.PopId(); + + gui.Canvas.PopRectMask(); + gui.Canvas.PopClipRect(); } - public static float GetControlHeight(ImGui gui) + public static bool DropdownButton(ImGui gui, uint id, ReadOnlySpan label, ImRect rect) { - return ImSelect.Style.Button.GetButtonHeight(gui.GetRowHeight()); - } - } + var arrowRect = ImButton.GetContentRect(rect); + var arrowSize = (arrowRect.H - ImTheme.Active.Controls.ExtraRowHeight) * ImTheme.Active.Dropdown.ArrowOuterScale; + arrowRect.X += arrowRect.W - arrowSize; + arrowRect.W = arrowSize; + + using var _ = new ImStyleScope(ref ImTheme.Active.Button); + + ImTheme.Active.Button.Alignment = ImTheme.Active.Dropdown.Alignment; + ImTheme.Active.Button.Padding.Right += arrowRect.W + ImTheme.Active.Controls.InnerSpacing; - public struct ImDropdownStyle - { - public const float DEFAULT_MAX_PANEL_HEIGHT = 300; + var clicked = gui.Button(id, label, rect, out var state); - public static readonly ImDropdownStyle Default = CreateDefaultStyle(); - - private static ImDropdownStyle CreateDefaultStyle() + DrawArrow(gui, arrowRect, ImButton.GetStateFontColor(state)); + + return clicked; + } + + public static void DrawArrow(ImGui gui, ImRect rect, Color32 color) { - var style = new ImDropdownStyle() + rect = rect.WithAspect(1.0f).ScaleFromCenter(ImTheme.Active.Dropdown.ArrowInnerScale).WithAspect(1.1547f); + + Span points = stackalloc Vector2[3] { - MaxPanelHeight = DEFAULT_MAX_PANEL_HEIGHT, - OptionsButtonsSpacing = 0 + new Vector2(rect.X + rect.W * 0.5f, rect.Y), + new Vector2(rect.X + rect.W, rect.Y + rect.H), + new Vector2(rect.X, rect.Y + rect.H), }; - - style.OptionButton = ImButtonStyle.Default; - style.OptionButton.Normal.BackColor = ImColors.Blue.WithAlpha(0); - style.OptionButton.Hovered.BackColor = ImColors.Blue.WithAlpha(32); - style.OptionButton.Pressed.BackColor = ImColors.Blue.WithAlpha(48); - style.OptionButton.Padding += 4; - style.OptionButton.Alignment.X = 0; - style.OptionButton.SetBorderWidth(0); - - style.OptionButtonSelected = ImButtonStyle.Default; - style.OptionButtonSelected.Normal.BackColor = ImColors.Blue; - style.OptionButtonSelected.Normal.FrontColor = ImColors.White; - style.OptionButtonSelected.Hovered.BackColor = ImColors.LightBlue; - style.OptionButtonSelected.Hovered.FrontColor = ImColors.White; - style.OptionButtonSelected.Pressed.BackColor = ImColors.DarkBlue; - style.OptionButtonSelected.Pressed.FrontColor = ImColors.White; - style.OptionButtonSelected.Padding += 4; - style.OptionButtonSelected.Alignment.X = 0; - style.OptionButtonSelected.SetBorderWidth(0); - - return style; - } + gui.Canvas.ConvexFill(points, color); + } + } + + [Serializable] + public struct ImDropdownStyle + { + public float ArrowInnerScale; + public float ArrowOuterScale; + public ImTextAlignment Alignment; public float MaxPanelHeight; public ImButtonStyle OptionButton; public ImButtonStyle OptionButtonSelected; diff --git a/Runtime/Scripts/Controls/ImFoldout.cs b/Runtime/Scripts/Controls/ImFoldout.cs index 20fbb04..e226169 100644 --- a/Runtime/Scripts/Controls/ImFoldout.cs +++ b/Runtime/Scripts/Controls/ImFoldout.cs @@ -1,6 +1,6 @@ using System; +using Imui.Controls.Styling; using Imui.Core; -using Imui.Utility; using UnityEngine; namespace Imui.Controls @@ -8,55 +8,24 @@ namespace Imui.Controls public static class ImFoldout { private const float ARROW_ASPECT_RATIO = 1.1547f; // ~ 2/sqrt(3) - - public static ImFoldoutStyle Style = ImFoldoutStyle.Default; - public static void BeginFoldout(this ImGui gui, in ReadOnlySpan label, out bool open) + public static void BeginFoldout(this ImGui gui, ReadOnlySpan label, out bool open, ImSize size = default) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); var id = gui.PushId(label); - var width = gui.Layout.GetAvailableWidth(); - var height = Style.GetHeight(gui.GetRowHeight()); - var rect = gui.Layout.AddRect(width, height); + var rect = ImControls.GetRowRect(gui, size); ref var state = ref gui.Storage.Get(id); - Foldout(gui, id, ref state, in rect, in label); + Foldout(gui, id, ref state, rect, label); open = state; } - - public static void BeginFoldout(this ImGui gui, ref bool open, in ReadOnlySpan label) - { - gui.AddControlSpacing(); - - var width = gui.Layout.GetAvailableWidth(); - var height = Style.GetHeight(gui.GetRowHeight()); - BeginFoldout(gui, ref open, in label, gui.Layout.AddRect(width, height)); - } - - public static void BeginFoldout(this ImGui gui, ref bool open, in ReadOnlySpan label, Vector2 size) - { - BeginFoldout(gui, ref open, in label, gui.Layout.AddRect(size)); - } - - public static void BeginFoldout(this ImGui gui, ref bool open, in ReadOnlySpan label, float width, float height) - { - BeginFoldout(gui, ref open, in label, gui.Layout.AddRect(width, height)); - } - - public static void BeginFoldout(this ImGui gui, ref bool open, in ReadOnlySpan label, in ImRect rect) - { - gui.AddControlSpacing(); - - var id = gui.PushId(label); - Foldout(gui, id, ref open, in rect, in label); - } - - public static void BeginFoldout(this ImGui gui, ref bool open, in ImRect rect, out ImRect contentRect) + + public static void BeginFoldout(this ImGui gui, ReadOnlySpan label, ref bool open, ImRect rect) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); - var id = gui.PushId(); - Foldout(gui, id, ref open, in rect, out contentRect); + var id = gui.PushId(label); + Foldout(gui, id, ref open, rect, label); } public static void EndFoldout(this ImGui gui) @@ -64,26 +33,28 @@ public static void EndFoldout(this ImGui gui) gui.PopId(); } - public static void Foldout(this ImGui gui, uint id, ref bool open, in ImRect rect, in ReadOnlySpan label) - { - Foldout(gui, id, ref open, in rect, out var contentRect); - gui.Text(in label, GetTextSettings(), contentRect); - } - - public static void Foldout(this ImGui gui, uint id, ref bool open, in ImRect rect, out ImRect contentRect) + public static void Foldout(this ImGui gui, uint id, ref bool open, ImRect rect, ReadOnlySpan label) { - var clicked = gui.Button(id, in rect, out var state); - var content = ImButton.Style.GetContentRect(rect); - var left = content.SplitLeft(GetArrowSize(gui), ImControls.Style.InnerSpacing, out contentRect); - var style = ImButton.Style.GetStyle(state); + var arrowRect = ImButton.GetContentRect(rect); + var arrowSize = (arrowRect.H - ImTheme.Active.Controls.ExtraRowHeight) * ImTheme.Active.Foldout.ArrowOuterScale; + arrowRect.W = arrowSize; + + using var _ = new ImStyleScope(ref ImTheme.Active.Button); + + ImTheme.Active.Button.BorderWidth = ImTheme.Active.Foldout.BorderWidth; + ImTheme.Active.Button.Alignment = ImTheme.Active.Foldout.TextAlignment; + ImTheme.Active.Button.Padding.Left += arrowRect.W + ImTheme.Active.Controls.InnerSpacing; + + var clicked = gui.Button(id, label, rect, out var state); + var frontColor = ImButton.GetStateFontColor(state); if (open) { - DrawOpenArrow(gui.Canvas, in left, style.FrontColor); + DrawOpenArrow(gui.Canvas, arrowRect, frontColor); } else { - DrawClosedArrow(gui.Canvas, in left, style.FrontColor); + DrawClosedArrow(gui.Canvas, arrowRect, frontColor); } if (clicked) @@ -91,10 +62,10 @@ public static void Foldout(this ImGui gui, uint id, ref bool open, in ImRect rec open = !open; } } - - public static void DrawClosedArrow(ImCanvas canvas, in ImRect rect, Color32 color) + + public static void DrawClosedArrow(ImCanvas canvas, ImRect rect, Color32 color) { - var arrowRect = rect.WithAspect(1.0f).ScaleFromCenter(Style.ArrowScale).WithAspect(1.0f / ARROW_ASPECT_RATIO); + var arrowRect = rect.WithAspect(1.0f).ScaleFromCenter(ImTheme.Active.Foldout.ArrowInnerScale).WithAspect(1.0f / ARROW_ASPECT_RATIO); Span points = stackalloc Vector2[3] { @@ -106,9 +77,9 @@ public static void DrawClosedArrow(ImCanvas canvas, in ImRect rect, Color32 colo canvas.ConvexFill(points, color); } - public static void DrawOpenArrow(ImCanvas canvas, in ImRect rect, Color32 color) + public static void DrawOpenArrow(ImCanvas canvas, ImRect rect, Color32 color) { - var arrowRect = rect.WithAspect(1.0f).ScaleFromCenter(Style.ArrowScale).WithAspect(ARROW_ASPECT_RATIO); + var arrowRect = rect.WithAspect(1.0f).ScaleFromCenter(ImTheme.Active.Foldout.ArrowInnerScale).WithAspect(ARROW_ASPECT_RATIO); Span points = stackalloc Vector2[3] { @@ -119,41 +90,14 @@ public static void DrawOpenArrow(ImCanvas canvas, in ImRect rect, Color32 color) canvas.ConvexFill(points, color); } - - public static ImTextSettings GetTextSettings() - { - return new ImTextSettings(ImControls.Style.TextSize, Style.Button.Alignment); - } - - public static float GetArrowSize(ImGui gui) - { - return gui.GetRowHeight(); - } } + [Serializable] public struct ImFoldoutStyle { - public static readonly ImFoldoutStyle Default = CreateDefaultStyle(); - - private static ImFoldoutStyle CreateDefaultStyle() - { - var style = new ImFoldoutStyle() - { - ArrowScale = 0.5f, - Button = ImButtonStyle.Default - }; - - style.Button.Alignment.X = 0.0f; - style.Button.SetBorderWidth(0); - return style; - } - - public float ArrowScale; - public ImButtonStyle Button; - - public float GetHeight(float contentHeight) - { - return contentHeight + Button.Padding.Vertical; - } + public float ArrowInnerScale; + public float ArrowOuterScale; + public float BorderWidth; + public ImTextAlignment TextAlignment; } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Layout/ImGrid.cs b/Runtime/Scripts/Controls/ImGrid.cs similarity index 87% rename from Runtime/Scripts/Controls/Layout/ImGrid.cs rename to Runtime/Scripts/Controls/ImGrid.cs index d2a8f4e..cd0f514 100644 --- a/Runtime/Scripts/Controls/Layout/ImGrid.cs +++ b/Runtime/Scripts/Controls/ImGrid.cs @@ -1,7 +1,8 @@ +using Imui.Controls.Styling; using Imui.Core; using UnityEngine; -namespace Imui.Controls.Layout +namespace Imui.Controls { public struct ImGridState { @@ -17,7 +18,7 @@ public static class ImGrid { public static ImGridState BeginGrid(this ImGui gui, int columns, float cellHeight = 0) { - var width = gui.GetAvailableWidth(); + var width = gui.GetLayoutWidth(); var spacing = GetDefaultSpacing(); var cellWidth = (width + spacing.x) / columns - spacing.x; cellHeight = cellHeight <= 0 ? cellWidth : cellHeight; @@ -27,7 +28,7 @@ public static ImGridState BeginGrid(this ImGui gui, int columns, float cellHeigh public static ImGridState BeginGrid(this ImGui gui, Vector2 cellSize) { - return BeginGrid(gui, cellSize, new Vector2(ImControls.Style.InnerSpacing, ImControls.Style.InnerSpacing)); + return BeginGrid(gui, cellSize, new Vector2(ImTheme.Active.Controls.InnerSpacing, ImTheme.Active.Controls.InnerSpacing)); } public static ImGridState BeginGrid(this ImGui gui, Vector2 cellSize, Vector2 spacing) @@ -72,7 +73,7 @@ public static ImRect GridNextCell(this ImGui gui, ref ImGridState state) public static Vector2 GetDefaultSpacing() { - return new Vector2(ImControls.Style.InnerSpacing, ImControls.Style.InnerSpacing); + return new Vector2(ImTheme.Active.Controls.InnerSpacing, ImTheme.Active.Controls.InnerSpacing); } } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Layout/ImGrid.cs.meta b/Runtime/Scripts/Controls/ImGrid.cs.meta similarity index 100% rename from Runtime/Scripts/Controls/Layout/ImGrid.cs.meta rename to Runtime/Scripts/Controls/ImGrid.cs.meta diff --git a/Runtime/Scripts/Controls/ImImage.cs b/Runtime/Scripts/Controls/ImImage.cs index 1f1b516..ee9af9a 100644 --- a/Runtime/Scripts/Controls/ImImage.cs +++ b/Runtime/Scripts/Controls/ImImage.cs @@ -1,15 +1,26 @@ using Imui.Core; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls { public static class ImImage { - private static Vector4 ScaleOffset = new Vector4(1, 1, 0, 0); + public static ImRect GetRect(ImGui gui, Texture texture, ImSize size) + { + return size.Type switch + { + ImSizeType.Fixed => gui.Layout.AddRect(size.Width, size.Height), + _ => gui.Layout.AddRect(texture.width, texture.height) + }; + } + + public static void Image(this ImGui gui, Texture texture, ImSize size = default, bool preserveAspect = false) + { + Image(gui, texture, GetRect(gui, texture, size), preserveAspect); + } - public static void Image(this ImGui gui, ImRect rect, Texture texture, bool preserveAspect = false) + public static void Image(this ImGui gui, Texture texture, ImRect rect, bool preserveAspect = false) { if (gui.Canvas.Cull(rect)) { @@ -22,7 +33,7 @@ public static void Image(this ImGui gui, ImRect rect, Texture texture, bool pres } gui.Canvas.PushTexture(texture); - gui.Canvas.Rect(rect, ImColors.White, ScaleOffset); + gui.Canvas.Rect(rect, ImColors.White); gui.Canvas.PopTexture(); } } diff --git a/Runtime/Scripts/Controls/ImPanel.cs b/Runtime/Scripts/Controls/ImPanel.cs index 1f6de5c..13770e3 100644 --- a/Runtime/Scripts/Controls/ImPanel.cs +++ b/Runtime/Scripts/Controls/ImPanel.cs @@ -1,57 +1,42 @@ +using System; using Imui.Core; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; namespace Imui.Controls { // TODO (artem-s): allow to add title bar to panel public static class ImPanel { - public static ImPanelStyle Style = ImPanelStyle.Default; - - public static void BeginPanel(this ImGui gui, in ImRect rect) + public static void BeginPanel(this ImGui gui, ImRect rect) { - gui.DrawBox(in rect, Style.Box); + gui.Box(rect, ImTheme.Active.Panel.Box); gui.RegisterRaycastTarget(rect); + + var layoutRect = rect.WithPadding(ImTheme.Active.Panel.Padding); + var maskRect = rect.WithPadding(ImTheme.Active.Panel.Box.BorderWidth); - gui.Layout.Push(ImAxis.Vertical, rect.WithPadding(Style.Padding)); - gui.Canvas.PushRectMask(rect.WithPadding(Style.Box.BorderWidth), Style.Box.BorderRadius.GetMax()); - gui.BeginScrollable(); + gui.Layout.Push(ImAxis.Vertical, layoutRect); + gui.Canvas.PushRectMask(maskRect, ImTheme.Active.Panel.Box.BorderRadius); + gui.Canvas.PushClipRect(maskRect); // need this to properly handle clicking outside drawing area } public static void EndPanel(this ImGui gui) { - gui.EndScrollable(); + gui.Canvas.PopClipRect(); gui.Canvas.PopRectMask(); gui.Layout.Pop(); } + + public static float GetEnclosingHeight(float contentHeight) + { + return contentHeight + ImTheme.Active.Panel.Padding.Vertical; + } } + [Serializable] public struct ImPanelStyle { - public static readonly ImPanelStyle Default = new ImPanelStyle() - { - Box = new ImBoxStyle - { - BackColor = ImColors.White, - BorderColor = ImColors.Black, - BorderWidth = 1, - BorderRadius = 3 - }, - Padding = 4f - }; - - public ImBoxStyle Box; public ImPadding Padding; - - public float GetHeight(float contentHeight) - { - return contentHeight + Padding.Vertical; - } - - public ImRect GetContentRect(ImRect popupRect) - { - return popupRect.WithPadding(Padding); - } + public ImBoxStyle Box; } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImPopup.cs b/Runtime/Scripts/Controls/ImPopup.cs index 40aac8c..b9f2bab 100644 --- a/Runtime/Scripts/Controls/ImPopup.cs +++ b/Runtime/Scripts/Controls/ImPopup.cs @@ -24,6 +24,7 @@ public static bool CloseButton(ImGui gui) { gui.Canvas.PushNoClipRect(); gui.Canvas.PushOrder(ORDER_CLOSE_BUTTON); + gui.RegisterRaycastTarget(gui.Canvas.ScreenRect); var id = gui.GetNextControlId(); diff --git a/Runtime/Scripts/Controls/ImScroll.cs b/Runtime/Scripts/Controls/ImScroll.cs index 25dc63a..10828fc 100644 --- a/Runtime/Scripts/Controls/ImScroll.cs +++ b/Runtime/Scripts/Controls/ImScroll.cs @@ -1,8 +1,7 @@ using System; using Imui.Core; using Imui.IO.Events; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls @@ -32,51 +31,56 @@ public struct ImScrollState public static class ImScroll { - public static ImScrollStyle Style = ImScrollStyle.Default; - public static void BeginScrollable(this ImGui gui) { var id = gui.GetNextControlId(); var state = gui.Storage.Get(id); ref readonly var frame = ref gui.Layout.GetFrame(); - var visibleRect = GetVisibleRect(frame.Bounds, in state); + var visibleRect = GetVisibleRect(frame.Bounds, state); gui.Layout.Push(frame.Axis, visibleRect, ImLayoutFlag.None); gui.Layout.SetOffset(state.Offset); - gui.BeginScope(id); + + ref var scrollRectStack = ref gui.GetScrollRectStack(); + scrollRectStack.Push(id); } public static void EndScrollable(this ImGui gui, ImScrollFlag flags = ImScrollFlag.None) { - gui.EndScope(out var id); + ref var scrollRectStack = ref gui.GetScrollRectStack(); + var id = scrollRectStack.Pop(); + gui.Layout.Pop(out var contentFrame); var bounds = gui.Layout.GetBoundsRect(); - Scroll(gui, id, in bounds, contentFrame.Size, flags); + Scroll(gui, id, bounds, contentFrame.Size, flags); } public static Vector2 GetScrollOffset(this ImGui gui) { - var id = gui.GetScope(); + ref var scrollRectStack = ref gui.GetScrollRectStack(); + var id = scrollRectStack.Peek(); return gui.Storage.Get(id).Offset; } public static void SetScrollOffset(this ImGui gui, Vector2 offset) { - var id = gui.GetScope(); + ref var scrollRectStack = ref gui.GetScrollRectStack(); + var id = scrollRectStack.Peek(); + ref var state = ref gui.Storage.Get(id); state.Offset = offset; } - public static void Scroll(ImGui gui, uint id, in ImRect view, Vector2 size, ImScrollFlag flags) + public static void Scroll(ImGui gui, uint id, ImRect view, Vector2 size, ImScrollFlag flags) { ref var state = ref gui.Storage.Get(id); - Layout(ref state, in view, size, out var adjust, flags); + Layout(ref state, view, size, out var adjust, flags); var dx = 0f; var dy = 0f; @@ -106,6 +110,15 @@ public static void Scroll(ImGui gui, uint id, in ImRect view, Vector2 size, ImSc dx -= normalDelta * size.x; } + + var groupRect = GetVisibleRect(view, state); + gui.RegisterGroup(id, groupRect); + + // Scroll bars should probably work even in read only mode + // if (gui.IsReadOnly) + // { + // return; + // } var deferredUseMouseEvent = false; var groupHovered = gui.IsGroupHovered(id); @@ -115,7 +128,7 @@ public static void Scroll(ImGui gui, uint id, in ImRect view, Vector2 size, ImSc switch (evt.Type) { case ImMouseEventType.Scroll when groupHovered: - var scale = ImControls.Style.ScrollSpeedScale; + var scale = ImTheme.Active.Controls.ScrollSpeedScale; dx += evt.Delta.x * scale; dy += evt.Delta.y * scale; deferredUseMouseEvent = true; @@ -135,8 +148,7 @@ public static void Scroll(ImGui gui, uint id, in ImRect view, Vector2 size, ImSc gui.ResetActiveControl(); break; } - - var groupRect = GetVisibleRect(view, in state); + var prevOffset = state.Offset; state.Offset.x = Mathf.Clamp(state.Offset.x + dx, Mathf.Min(0, view.W - size.x), 0); @@ -147,8 +159,6 @@ public static void Scroll(ImGui gui, uint id, in ImRect view, Vector2 size, ImSc { gui.Input.UseMouseEvent(); } - - gui.RegisterGroup(id, groupRect); } public static float Bar( @@ -159,7 +169,9 @@ public static float Bar( float normalPosition, int axis) { - rect = rect.WithPadding(Style.Margin); + ref readonly var style = ref ImTheme.Active.Scroll; + + rect = rect.WithPadding(style.Margin); var delta = 0f; var absoluteSize = axis == 0 ? rect.W : rect.H; @@ -169,14 +181,14 @@ public static float Bar( var handleRect = axis == 0 ? new ImRect(rect.X + position, rect.Y, size, rect.H) : new ImRect(rect.X, rect.Y + (rect.H - size) - position, rect.W, size); - handleRect = handleRect.WithPadding(Style.Padding); + handleRect = handleRect.WithPadding(style.Padding); var hovered = gui.IsControlHovered(id); var pressed = gui.IsControlActive(id); - var barStyle = pressed ? Style.PressedState : hovered ? Style.HoveredState : Style.NormalState; - gui.Canvas.Rect(rect, barStyle.BackColor, Style.BorderRadius); - gui.Canvas.Rect(handleRect, barStyle.FrontColor, Style.BorderRadius - Style.Padding); + var barStyle = pressed ? style.PressedState : hovered ? style.HoveredState : style.NormalState; + gui.Canvas.Rect(rect, barStyle.BackColor, style.BorderRadius); + gui.Canvas.Rect(handleRect, barStyle.FrontColor, style.BorderRadius - style.Padding); ref readonly var evt = ref gui.Input.MouseEvent; switch (evt.Type) @@ -201,17 +213,17 @@ public static float Bar( return delta; } - public static ImRect GetVisibleRect(ImRect view, in ImScrollState state) + public static ImRect GetVisibleRect(ImRect view, ImScrollState state) { if ((state.Layout & ImScrollLayoutFlag.HorBarVisible) != 0) { - view.Y += Style.Size; - view.H -= Style.Size; + view.Y += ImTheme.Active.Scroll.Size; + view.H -= ImTheme.Active.Scroll.Size; } if ((state.Layout & ImScrollLayoutFlag.VerBarVisible) != 0) { - view.W -= Style.Size; + view.W -= ImTheme.Active.Scroll.Size; } return view; @@ -219,11 +231,11 @@ public static ImRect GetVisibleRect(ImRect view, in ImScrollState state) public static ImRect GetHorizontalBarRect(ImRect view, bool verBarVisible) { - view.H = Style.Size; + view.H = ImTheme.Active.Scroll.Size; if (verBarVisible) { - view.W -= Style.Size; + view.W -= ImTheme.Active.Scroll.Size; } return view; @@ -231,33 +243,35 @@ public static ImRect GetHorizontalBarRect(ImRect view, bool verBarVisible) public static ImRect GetVerticalBarRect(ImRect view) { - view.X += view.W - Style.Size; - view.W = Style.Size; + view.X += view.W - ImTheme.Active.Scroll.Size; + view.W = ImTheme.Active.Scroll.Size; return view; } - private static void Layout(ref ImScrollState state, in ImRect view, in Vector2 size, out Vector2 adjust, ImScrollFlag flags) + private static void Layout(ref ImScrollState state, ImRect view, Vector2 size, out Vector2 adjust, ImScrollFlag flags) { + var styleSize = ImTheme.Active.Scroll.Size; + state.Layout = default; // doing calculations twice because showing one bar may require showing another for (int i = 0; i < 2; ++i) { state.Layout = - (flags & ImScrollFlag.NoVerticalBar) == 0 && size.y > (view.H - ((state.Layout & ImScrollLayoutFlag.HorBarVisible) != 0 ? Style.Size : 0f)) + (flags & ImScrollFlag.NoVerticalBar) == 0 && size.y > (view.H - ((state.Layout & ImScrollLayoutFlag.HorBarVisible) != 0 ? styleSize : 0f)) ? (state.Layout | ImScrollLayoutFlag.VerBarVisible) : (state.Layout & ~ImScrollLayoutFlag.VerBarVisible); state.Layout = - (flags & ImScrollFlag.NoHorizontalBar) == 0 && size.x > (view.W - ((state.Layout & ImScrollLayoutFlag.VerBarVisible) != 0 ? Style.Size : 0f)) + (flags & ImScrollFlag.NoHorizontalBar) == 0 && size.x > (view.W - ((state.Layout & ImScrollLayoutFlag.VerBarVisible) != 0 ? styleSize : 0f)) ? (state.Layout | ImScrollLayoutFlag.HorBarVisible) : (state.Layout & ~ImScrollLayoutFlag.HorBarVisible); } adjust = new Vector2( - (state.Layout & ImScrollLayoutFlag.VerBarVisible) != 0 ? Style.Size : 0f, - (state.Layout & ImScrollLayoutFlag.HorBarVisible) != 0 ? Style.Size : 0f); + (state.Layout & ImScrollLayoutFlag.VerBarVisible) != 0 ? styleSize : 0f, + (state.Layout & ImScrollLayoutFlag.HorBarVisible) != 0 ? styleSize : 0f); } } @@ -272,29 +286,6 @@ public struct ImScrollBarStateStyle [Serializable] public struct ImScrollStyle { - public static readonly ImScrollStyle Default = new() - { - Size = 20, - Margin = 1, - Padding = 1, - BorderRadius = 3, - NormalState = new ImScrollBarStateStyle() - { - BackColor = ImColors.Black, - FrontColor = ImColors.Gray7 - }, - HoveredState = new ImScrollBarStateStyle() - { - BackColor = ImColors.Black, - FrontColor = ImColors.Gray8 - }, - PressedState = new ImScrollBarStateStyle() - { - BackColor = ImColors.Black, - FrontColor = ImColors.Gray6 - } - }; - public float Size; public float Margin; public float Padding; diff --git a/Runtime/Scripts/Controls/ImSelect.cs b/Runtime/Scripts/Controls/ImSelect.cs deleted file mode 100644 index 9980e64..0000000 --- a/Runtime/Scripts/Controls/ImSelect.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using Imui.Core; -using Imui.Styling; -using Imui.Utility; -using UnityEngine; - -namespace Imui.Controls -{ - public static class ImSelect - { - private const float ARROW_ASPECT_RATIO = 1.1547f; // ~ 2/sqrt(3) - - public static ImSelectStyle Style = ImSelectStyle.Default; - - public static bool Select(this ImGui gui, in ReadOnlySpan label) - { - gui.AddControlSpacing(); - - var textSettings = GetTextSettings(); - var contentSize = gui.MeasureTextSize(label, in textSettings); - contentSize.x += ImControls.Style.InnerSpacing + GetArrowSize(gui); - var rect = gui.Layout.AddRect(Style.Button.GetButtonSize(contentSize)); - return Select(gui, label, in rect); - } - - public static bool Select(this ImGui gui, in ReadOnlySpan label, float width, float height) - { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(width, height); - return Select(gui, label, in rect); - } - - public static bool Select(this ImGui gui, in ReadOnlySpan label, Vector2 size) - { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(size); - return Select(gui, label, in rect); - } - - public static bool Select(this ImGui gui, in ReadOnlySpan label, in ImRect rect) - { - var id = gui.GetNextControlId(); - return Select(gui, id, in label, in rect); - } - - public static bool Select(this ImGui gui, uint id, in ReadOnlySpan label, in ImRect rect) - { - using var _ = new ImStyleScope(ref ImButton.Style, Style.Button); - - var arrowSize = GetArrowSize(gui); - var clicked = gui.Button(id, rect, out var state); - var style = Style.Button.GetStyle(state); - var content = Style.Button.GetContentRect(rect); - content = content.SplitLeft(content.W - arrowSize - ImControls.Style.InnerSpacing, out var arrowRect); - var textSettings = GetTextSettings(); - - gui.Canvas.Text(in label, style.FrontColor, content, in textSettings); - - arrowRect.X += ImControls.Style.InnerSpacing; - arrowRect.W -= ImControls.Style.InnerSpacing; - arrowRect = arrowRect.WithAspect(1f).ScaleFromCenter(Style.ArrowScale).WithAspect(ARROW_ASPECT_RATIO); - - Span points = stackalloc Vector2[3] - { - new Vector2(arrowRect.X + arrowRect.W * 0.5f, arrowRect.Y), - new Vector2(arrowRect.X + arrowRect.W, arrowRect.Y + arrowRect.H), - new Vector2(arrowRect.X, arrowRect.Y + arrowRect.H), - }; - - gui.Canvas.ConvexFill(points, style.FrontColor); - - return clicked; - } - - public static ImTextSettings GetTextSettings() - { - return new ImTextSettings(ImControls.Style.TextSize, Style.Button.Alignment); - } - - public static float GetArrowSize(ImGui gui) - { - return gui.GetRowHeight(); - } - } - - public struct ImSelectStyle - { - public static readonly ImSelectStyle Default = CreateDefaultStyle(); - - public static ImSelectStyle CreateDefaultStyle() - { - var style = new ImSelectStyle() - { - ArrowScale = 0.5f, - Button = ImButtonStyle.Default - }; - - style.Button.Alignment.X = 0; - return style; - } - - public float ArrowScale; - public ImButtonStyle Button; - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImSelect.cs.meta b/Runtime/Scripts/Controls/ImSelect.cs.meta deleted file mode 100644 index 445ea1f..0000000 --- a/Runtime/Scripts/Controls/ImSelect.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 8a353358e1324f0c87d867c456095c5e -timeCreated: 1709418845 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImSlider.cs b/Runtime/Scripts/Controls/ImSlider.cs index 92bb08c..091abeb 100644 --- a/Runtime/Scripts/Controls/ImSlider.cs +++ b/Runtime/Scripts/Controls/ImSlider.cs @@ -1,57 +1,47 @@ +using System; using Imui.Core; using Imui.IO.Events; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls { public static class ImSlider { - public static ImSliderStyle Style = ImSliderStyle.Default; - - public static bool Slider(this ImGui gui, ref float value, float min, float max) + public static ImRect GetRect(ImGui gui, ImSize size) { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(gui.Layout.GetAvailableWidth(), gui.GetRowHeight()); - return Slider(gui, ref value, min, max, in rect); - } - - public static bool Slider(this ImGui gui, ref float value, float min, float max, float width, float height) - { - gui.AddControlSpacing(); - - var rect = gui.Layout.AddRect(width, height); - return Slider(gui, ref value, min, max, in rect); + return size.Type switch + { + ImSizeType.Fixed => gui.Layout.AddRect(size.Width, size.Height), + _ => gui.Layout.AddRect(gui.Layout.GetAvailableWidth(), gui.GetRowHeight()) + }; } - public static bool Slider(this ImGui gui, ref float value, float min, float max, Vector2 size) + public static bool Slider(this ImGui gui, ref float value, float min, float max, ImSize size = default) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); - var rect = gui.Layout.AddRect(size); - return Slider(gui, ref value, min, max, in rect); + var rect = GetRect(gui, size); + return Slider(gui, ref value, min, max, rect); } - public static bool Slider(this ImGui gui, ref float value, float min, float max, in ImRect rect) + public static bool Slider(this ImGui gui, ref float value, float min, float max, ImRect rect) { const float EPSILON = 0.000001f; - var prevValue = value; - value = Mathf.InverseLerp(min, max, value); + var normValue = Mathf.InverseLerp(min, max, value); - gui.DrawBox(in rect, in Style.Box); + gui.Box(rect, in ImTheme.Active.Slider.Box); - var rectPadded = rect.WithPadding(Style.Padding); + var rectPadded = rect.WithPadding(ImTheme.Active.Slider.Padding); - var handleW = rectPadded.H * Style.HandleAspectRatio; + var handleW = rectPadded.H * ImTheme.Active.Slider.HandleAspectRatio; var handleH = rectPadded.H; var xmin = rectPadded.X + handleW / 2.0f; var xmax = rectPadded.X + rectPadded.W - handleW / 2.0f; - var handleX = Mathf.Lerp(xmin, xmax, value) - (handleW / 2.0f); + var handleX = Mathf.Lerp(xmin, xmax, normValue) - (handleW / 2.0f); var handleY = rectPadded.Y + (rectPadded.H / 2.0f) - (handleH / 2.0f); var handleRect = new ImRect(handleX, handleY, handleW, handleH); @@ -59,9 +49,16 @@ public static bool Slider(this ImGui gui, ref float value, float min, float max, var hovered = gui.IsControlHovered(id); var active = gui.IsControlActive(id); - using (new ImStyleScope(ref ImButton.Style, Style.Handle)) + using (new ImStyleScope(ref ImTheme.Active.Button, ImTheme.Active.Slider.Handle)) { - gui.Button(id, in handleRect, out _); + gui.Button(id, handleRect, out _); + } + + gui.RegisterControl(id, rect); + + if (gui.IsReadOnly) + { + return false; } ref readonly var evt = ref gui.Input.MouseEvent; @@ -73,7 +70,7 @@ public static bool Slider(this ImGui gui, ref float value, float min, float max, break; case ImMouseEventType.Drag when active: - value = Mathf.InverseLerp(xmin, xmax, Mathf.Lerp(xmin, xmax, value) + evt.Delta.x); + normValue = Mathf.InverseLerp(xmin, xmax, Mathf.Lerp(xmin, xmax, normValue) + evt.Delta.x); gui.Input.UseMouseEvent(); break; @@ -82,43 +79,20 @@ public static bool Slider(this ImGui gui, ref float value, float min, float max, break; } - gui.RegisterControl(id, rect); - - value = Mathf.Lerp(min, max, value); - - return Mathf.Abs(value - prevValue) > EPSILON; + var newValue = Mathf.Lerp(min, max, normValue); + if (Mathf.Abs(newValue - value) > EPSILON) + { + value = newValue; + return true; + } + + return false; } } + [Serializable] public struct ImSliderStyle { - public static readonly ImSliderStyle Default = CreateDefaultStyle(); - - public static ImSliderStyle CreateDefaultStyle() - { - var style = new ImSliderStyle() - { - Box = new ImBoxStyle() - { - BackColor = ImColors.White, - BorderWidth = 1, - BorderColor = ImColors.Black, - BorderRadius = 4 - }, - Handle = ImButtonStyle.Default, - Padding = 1, - HandleAspectRatio = 1.5f - }; - - style.Handle.Normal.BackColor = ImColors.Black; - style.Handle.Hovered.BackColor = ImColors.Gray1; - style.Handle.Pressed.BackColor = ImColors.Black; - style.Handle.SetBorderRadius(style.Box.BorderRadius - style.Box.BorderWidth); - style.Handle.SetBorderWidth(0); - - return style; - } - public ImBoxStyle Box; public ImButtonStyle Handle; public ImPadding Padding; diff --git a/Runtime/Scripts/Controls/ImText.cs b/Runtime/Scripts/Controls/ImText.cs index a3c77d3..4401f52 100644 --- a/Runtime/Scripts/Controls/ImText.cs +++ b/Runtime/Scripts/Controls/ImText.cs @@ -1,7 +1,6 @@ using System; using Imui.Core; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls @@ -11,19 +10,17 @@ public static class ImText public const float MIN_WIDTH = 1; public const float MIN_HEIGHT = 1; - public static ImTextStyle Style = ImTextStyle.Default; - - public static void Text(this ImGui gui, in ReadOnlySpan text) + public static void Text(this ImGui gui, ReadOnlySpan text, bool wrap = false) { - Text(gui, in text, GetTextSettings()); + Text(gui, text, GetTextSettings(wrap)); } - public static void Text(this ImGui gui, in ReadOnlySpan text, in ImRect rect) + public static void Text(this ImGui gui, ReadOnlySpan text, ImRect rect, bool wrap = false) { - Text(gui, in text, GetTextSettings(), rect); + Text(gui, text, GetTextSettings(wrap), rect); } - public static void TextFittedSlow(this ImGui gui, in ReadOnlySpan text, in ImRect rect) + public static void TextAutoSize(this ImGui gui, ReadOnlySpan text, ImRect rect, bool wrap = false) { // (artem-s): at least try to skip costly auto-sizing if (gui.Canvas.Cull(rect)) @@ -31,39 +28,39 @@ public static void TextFittedSlow(this ImGui gui, in ReadOnlySpan text, in return; } - var settings = GetTextSettings(); - settings.Size = AutoSizeTextSlow(gui, in text, settings, rect.Size); - Text(gui, in text, settings, rect); + var settings = GetTextSettings(wrap); + settings.Size = AutoSizeTextSlow(gui, text, settings, rect.Size); + Text(gui, text, settings, rect); } - public static void Text(this ImGui gui, in ReadOnlySpan text, in ImTextSettings settings) + public static void Text(this ImGui gui, ReadOnlySpan text, in ImTextSettings settings) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); var space = gui.Layout.GetAvailableSize().Max(MIN_WIDTH, MIN_HEIGHT); var rect = gui.Layout.GetRect(space); - gui.Canvas.Text(in text, Style.Color, rect, in settings, out var textRect); + gui.Canvas.Text(text, ImTheme.Active.Text.Color, rect, in settings, out var textRect); gui.Layout.AddRect(textRect); } - public static void Text(this ImGui gui, in ReadOnlySpan text, in ImTextSettings settings, ImRect rect) + public static void Text(this ImGui gui, ReadOnlySpan text, in ImTextSettings settings, ImRect rect) { - gui.Canvas.Text(in text, Style.Color, rect, in settings); + gui.Canvas.Text(text, ImTheme.Active.Text.Color, rect, in settings); } - public static ImTextSettings GetTextSettings() + public static ImTextSettings GetTextSettings(bool wrap) { - return new ImTextSettings(ImControls.Style.TextSize, Style.Alignment); + return new ImTextSettings(ImTheme.Active.Controls.TextSize, ImTheme.Active.Text.Alignment, wrap); } // TODO (artem-s): Got to come up with better solution instead of just brute forcing the fuck of it every time - public static float AutoSizeTextSlow(this ImGui gui, in ReadOnlySpan text, ImTextSettings settings, Vector2 bounds, float minSize = 1) + public static float AutoSizeTextSlow(this ImGui gui, ReadOnlySpan text, ImTextSettings settings, Vector2 bounds, float minSize = 1) { - var textSize = gui.MeasureTextSize(in text, in settings, bounds); + var textSize = gui.MeasureTextSize(text, in settings, bounds); while (settings.Size > minSize && (textSize.x > bounds.x || textSize.y > bounds.y)) { settings.Size -= 1; - textSize = gui.MeasureTextSize(in text, in settings, bounds); + textSize = gui.MeasureTextSize(text, in settings, bounds); } return settings.Size; @@ -75,14 +72,15 @@ public static float GetFontSizeForContainerHeight(this ImGui gui, float containe return scale * gui.TextDrawer.FontRenderSize; } - public static Vector2 MeasureTextSize(this ImGui gui, in ReadOnlySpan text, in ImTextSettings textSettings, Vector2 bounds = default) + public static Vector2 MeasureTextSize(this ImGui gui, ReadOnlySpan text, in ImTextSettings textSettings, Vector2 bounds = default) { - ref readonly var textLayout = ref gui.TextDrawer.BuildTempLayout(in text, + ref readonly var textLayout = ref gui.TextDrawer.BuildTempLayout(text, bounds.x, bounds.y, textSettings.Align.X, textSettings.Align.Y, - textSettings.Size); + textSettings.Size, + textSettings.Wrap); return new Vector2(textLayout.Width, textLayout.Height); } @@ -91,12 +89,6 @@ public static Vector2 MeasureTextSize(this ImGui gui, in ReadOnlySpan text [Serializable] public struct ImTextStyle { - public static readonly ImTextStyle Default = new ImTextStyle() - { - Color = ImColors.Black, - Alignment = new ImTextAlignment(0.0f, 0.0f) - }; - public Color32 Color; public ImTextAlignment Alignment; } diff --git a/Runtime/Scripts/Controls/ImTextEdit.cs b/Runtime/Scripts/Controls/ImTextEdit.cs index ee9f3cd..fa6110b 100644 --- a/Runtime/Scripts/Controls/ImTextEdit.cs +++ b/Runtime/Scripts/Controls/ImTextEdit.cs @@ -1,10 +1,11 @@ using System; using System.Globalization; +using System.Runtime.InteropServices; using Imui.Core; using Imui.IO.Events; using Imui.Rendering; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; +using Imui.IO.Utility; using UnityEngine; namespace Imui.Controls @@ -14,87 +15,211 @@ public struct ImTextEditState public int Caret; public int Selection; } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ImTextTempFilterBuffer + { + public const int BUFFER_LENGTH = 64; + + public fixed char Buffer[BUFFER_LENGTH]; + public byte Length; + + public void Populate(ReadOnlySpan buffer) + { + fixed (char* buf = Buffer) + { + var span = new Span(buf, BUFFER_LENGTH); + var len = buffer.Length > BUFFER_LENGTH ? BUFFER_LENGTH : buffer.Length; + + buffer[..len].CopyTo(span); + Length = (byte)len; + } + } + + public ReadOnlySpan AsSpan() + { + fixed (char* buf = Buffer) + { + return new Span(buf, Length); + } + } + } // TODO (artem-s): text input with dropdown selection + // TODO (artem-s): do not handle drag events if control is not active + // TODO (artem-s): handle double click to select words public static class ImTextEdit { public const float CARET_BLINKING_TIME = 0.3f; public const float MIN_WIDTH = 1; public const float MIN_HEIGHT = 1; - public static ImTextEditStyle Style = ImTextEditStyle.Default; - + private const string TEMP_BUFFER_TAG = "temp_buffer"; + public static readonly ImTextEditIntegerFilter IntegerFilter = new(); public static readonly ImTextEditFloatFilter FloatFilter = new(); - - public static void TextEdit(this ImGui gui, ref string text, ImTextEditFilter filter = null) + public static readonly ImTextEditIntegerFilter IntegerFilterAllowEmptyString = new(true); + public static readonly ImTextEditFloatFilter FloatFilterAllowEmptyString = new(true); + + public static ImRect GetRect(ImGui gui, ImSize size) { - gui.AddControlSpacing(); - - var width = Mathf.Max(MIN_WIDTH, gui.Layout.GetAvailableWidth()); - var height = Mathf.Max(MIN_HEIGHT, Style.GetControlHeight(gui.GetRowHeight())); - var rect = gui.Layout.AddRect(width, height); - TextEdit(gui, ref text, in rect, filter, false); + return size.Type switch + { + ImSizeType.Fixed => gui.Layout.AddRect(size.Width, size.Height), + _ => gui.Layout.AddRect( + Mathf.Max(MIN_WIDTH, gui.GetLayoutWidth()), + Mathf.Max(MIN_HEIGHT, gui.GetRowHeight())) + }; + } + + public static void TextEdit(this ImGui gui, ref int value, ImSize size = default, ReadOnlySpan format = default) + { + TextEditNumeric(gui, ref value, IntegerFilterAllowEmptyString, format, size); + } + + public static void TextEdit(this ImGui gui, ref float value, ImSize size = default, ReadOnlySpan format = default) + { + TextEditNumeric(gui, ref value, FloatFilterAllowEmptyString, format, size); } - public static void TextEdit(this ImGui gui, ref string text, float width, float height, ImTextEditFilter filter = null, bool multiline = true) + private static void TextEditNumeric(ImGui gui, ref T value, ImTextEditFilterNumeric filter, ReadOnlySpan format, ImSize size) + { + var buffer = new ImTextEditBuffer(); + buffer.MakeMutable(); + + if (filter.TryFormat(buffer.Buffer, value, out var length, format)) + { + buffer.Length = length; + } + else + { + buffer.Insert(0, filter.GetFallbackString()); + } + + gui.AddSpacingIfLayoutFrameNotEmpty(); + + var rect = GetRect(gui, size); + var changed = TextEdit(gui, ref buffer, rect, filter, multiline: false); + if (changed && filter.TryParse(buffer, out var newValue)) + { + value = newValue; + } + } + + public static void TextEdit(this ImGui gui, ref string text, ImSize size = default, ImTextEditFilter filter = null, bool? multiline = null) { - gui.AddControlSpacing(); + gui.AddSpacingIfLayoutFrameNotEmpty(); + + var rect = GetRect(gui, size); + + if (multiline == null) + { + multiline = rect.H > gui.GetRowHeight(); + } - var rect = gui.Layout.AddRect(width, height); - TextEdit(gui, ref text, in rect, filter, multiline); + TextEdit(gui, ref text, rect, filter, multiline.Value); } - public static void TextEdit(this ImGui gui, ref string text, Vector2 size, ImTextEditFilter filter = null, bool multiline = true) + public static void TextEdit(this ImGui gui, ref string text, ImRect rect, ImTextEditFilter filter = null, bool? multiline = null) { - gui.AddControlSpacing(); + var id = gui.GetNextControlId(); + ref var state = ref gui.Storage.Get(id); - var rect = gui.Layout.AddRect(size); - TextEdit(gui, ref text, in rect, filter, multiline); + if (multiline == null) + { + multiline = rect.H > gui.GetRowHeight(); + } + + TextEdit(gui, id, rect, ref text, ref state, filter, multiline.Value); } - public static void TextEdit(this ImGui gui, ref string text, in ImRect rect, ImTextEditFilter filter = null, bool multiline = true) + public static bool TextEdit(this ImGui gui, ref ImTextEditBuffer buffer, ImRect rect, ImTextEditFilter filter, bool multiline) { var id = gui.GetNextControlId(); ref var state = ref gui.Storage.Get(id); - TextEdit(gui, id, in rect, ref text, ref state, filter, multiline); + return TextEdit(gui, id, rect, ref buffer, ref state, filter, multiline); } - public static void TextEdit(this ImGui gui, uint id, in ImRect rect, ref string text, ref ImTextEditState state, ImTextEditFilter filter = null, bool multiline = true) + public static void TextEdit(this ImGui gui, uint id, ImRect rect, ref string text, ref ImTextEditState state, ImTextEditFilter filter, bool multiline) { var buffer = new ImTextEditBuffer(text); - var changed = TextEdit(gui, id, in rect, ref buffer, ref state, filter, multiline); + var changed = TextEdit(gui, id, rect, ref buffer, ref state, filter, multiline); if (changed) { text = buffer.GetString(); } } - public static bool TextEdit( + public static unsafe bool TextEdit( ImGui gui, uint id, - in ImRect rect, + ImRect rect, ref ImTextEditBuffer buffer, ref ImTextEditState state, ImTextEditFilter filter, bool multiline) { + ref readonly var style = ref ImTheme.Active.TextEdit; + var selected = gui.IsControlActive(id); var hovered = gui.IsControlHovered(id); - var stateStyle = selected ? Style.Selected : Style.Normal; + var stateStyle = selected ? style.Selected : style.Normal; var textChanged = false; + var editable = !gui.IsReadOnly; + + ImTextTempFilterBuffer* tempBuffer = null; + + if (filter != null && !filter.IsValid(buffer)) + { + var fallbackString = filter.GetFallbackString(); + buffer.Clear(fallbackString.Length); + buffer.Insert(0, fallbackString); + textChanged = true; + } + + if (selected && filter != null) + { + gui.PushId(id); + + var tempBufferId = gui.GetControlId(TEMP_BUFFER_TAG); + if (!gui.Storage.TryGetRef(tempBufferId, out tempBuffer)) + { + tempBuffer = gui.Storage.GetRef(tempBufferId); + tempBuffer->Populate(buffer); + } + // else relying on collecting garbage on every frame to clean up filter state + // TODO: if storage gc mechanism is somehow changed - return back here + + gui.PopId(); + + buffer.Clear(tempBuffer->Length); + buffer.Insert(0, tempBuffer->AsSpan()); + } - gui.DrawBox(in rect, in stateStyle.Box); - var textRect = Style.GetContentRect(rect); + gui.Box(rect, in stateStyle.Box); + + var textSize = ImTheme.Active.Controls.TextSize; + var textPadding = ImTheme.Active.TextEdit.Padding; + var textAlignment = ImTheme.Active.TextEdit.Alignment; + + if (!multiline) + { + // single-line text is always drawn at vertical center + var halfVertPadding = Mathf.Max(rect.H - gui.TextDrawer.GetLineHeight(textSize), 0.0f) / 2.0f; + + textPadding.Top = halfVertPadding; + textPadding.Bottom = halfVertPadding; + } - var textSize = ImControls.Style.TextSize; + var textRect = rect.WithPadding(textPadding); + var layout = gui.TextDrawer.BuildTempLayout( buffer, textRect.W, textRect.H, - Style.Alignment.X, Style.Alignment.Y, textSize); + textAlignment.X, textAlignment.Y, textSize, style.TextWrap); - gui.Canvas.PushRectMask(rect, stateStyle.Box.BorderRadius.GetMax()); + gui.Canvas.PushRectMask(rect, stateStyle.Box.BorderRadius); gui.Layout.Push(ImAxis.Vertical, textRect, ImLayoutFlag.Root); gui.BeginScrollable(); @@ -113,18 +238,18 @@ public static bool TextEdit( } state.Selection = 0; - state.Caret = ViewToCaretPosition(gui.Input.MousePosition, gui.TextDrawer, in textRect, in layout, in buffer); + state.Caret = ViewToCaretPosition(gui.Input.MousePosition, gui.TextDrawer, textRect, in layout, in buffer); gui.Input.UseMouseEvent(); break; case ImMouseEventType.Drag when selected: - var newCaretPosition = ViewToCaretPosition(gui.Input.MousePosition, gui.TextDrawer, in textRect, in layout, in buffer); + var newCaretPosition = ViewToCaretPosition(gui.Input.MousePosition, gui.TextDrawer, textRect, in layout, in buffer); state.Selection -= newCaretPosition - state.Caret; state.Caret = newCaretPosition; gui.Input.UseMouseEvent(); - ScrollToCaret(gui, in state, in textRect, in layout, in buffer); + ScrollToCaret(gui, state, textRect, in layout, in buffer); break; case ImMouseEventType.Down when selected && !hovered: @@ -134,10 +259,8 @@ public static bool TextEdit( if (selected) { - gui.Input.RequestTouchKeyboard(buffer); - - DrawCaret(gui, state.Caret, in textRect, in layout, in stateStyle, in buffer); - DrawSelection(gui, state.Caret, state.Selection, in textRect, in layout, in stateStyle, in buffer); + DrawCaret(gui, state.Caret, textRect, in layout, in stateStyle, in buffer); + DrawSelection(gui, state.Caret, state.Selection, textRect, in layout, in stateStyle, in buffer); for (int i = 0; i < gui.Input.KeyboardEventsCount; ++i) { @@ -148,16 +271,16 @@ public static bool TextEdit( in keyboardEvent, ref state, ref buffer, - in textRect, + textRect, in layout, - filter, multiline, + editable, out var isTextChanged)) { textChanged |= isTextChanged; gui.Input.UseKeyboardEvent(i); - ScrollToCaret(gui, in state, in textRect, in layout, in buffer); + ScrollToCaret(gui, state, textRect, in layout, in buffer); } } @@ -169,8 +292,22 @@ public static bool TextEdit( break; case ImTextEventType.Submit: gui.ResetActiveControl(); - buffer = new ImTextEditBuffer(textEvent.Text); - textChanged = true; + textChanged = buffer.Length != 0 || textEvent.Text.Length != 0; + buffer.Clear(textEvent.Text.Length); + Insert(ref state, ref buffer, textEvent.Text); + break; + default: + if (editable) + { + var settings = new ImTouchKeyboardSettings() + { + Muiltiline = multiline, + Type = filter?.KeyboardType ?? ImTouchKeyboardType.Default, + CharactersLimit = filter == null ? 0 : ImTextTempFilterBuffer.BUFFER_LENGTH + }; + + gui.Input.RequestTouchKeyboard(id, buffer, settings); + } break; } } @@ -180,6 +317,16 @@ public static bool TextEdit( gui.EndScrollable(multiline ? ImScrollFlag.None : ImScrollFlag.NoHorizontalBar | ImScrollFlag.NoVerticalBar); gui.Layout.Pop(); gui.Canvas.PopRectMask(); + + if (filter != null && !filter.IsValid(buffer)) + { + textChanged = false; + } + + if (tempBuffer != null) + { + tempBuffer->Populate(buffer); + } return textChanged; } @@ -188,10 +335,10 @@ public static bool HandleKeyboardEvent(ImGui gui, in ImKeyboardEvent evt, ref ImTextEditState state, ref ImTextEditBuffer buffer, - in ImRect textRect, - in TextDrawer.Layout layout, - ImTextEditFilter filter, + ImRect textRect, + in ImTextLayout layout, bool multiline, + bool editable, out bool textChanged) { var stateChanged = false; @@ -214,18 +361,18 @@ public static bool HandleKeyboardEvent(ImGui gui, break; case KeyCode.UpArrow: - stateChanged |= MoveCaretVertical(gui, in textRect, in layout, ref state, in buffer, +1, evt.Command); + stateChanged |= MoveCaretVertical(gui, textRect, in layout, ref state, in buffer, +1, evt.Command); break; case KeyCode.DownArrow: - stateChanged |= MoveCaretVertical(gui, in textRect, in layout, ref state, in buffer, -1, evt.Command); + stateChanged |= MoveCaretVertical(gui, textRect, in layout, ref state, in buffer, -1, evt.Command); break; - case KeyCode.Delete: + case KeyCode.Delete when editable: textChanged |= DeleteForward(ref state, ref buffer); break; - case KeyCode.Backspace: + case KeyCode.Backspace when editable: textChanged |= DeleteBackward(ref state, ref buffer); break; @@ -244,8 +391,8 @@ public static bool HandleKeyboardEvent(ImGui gui, stateChanged = true; break; - case ImKeyboardCommandFlag.Paste: - textChanged |= PasteFromClipboard(gui, ref state, ref buffer, filter); + case ImKeyboardCommandFlag.Paste when editable: + textChanged |= PasteFromClipboard(gui, ref state, ref buffer); break; default: @@ -261,8 +408,13 @@ public static bool HandleKeyboardEvent(ImGui gui, break; } + if (!editable) + { + break; + } + textChanged |= DeleteSelection(ref state, ref buffer); - textChanged |= TryInsert(ref state, ref buffer, evt.Char, filter); + textChanged |= Insert(ref state, ref buffer, evt.Char); break; } } @@ -274,7 +426,7 @@ public static bool HandleKeyboardEvent(ImGui gui, return stateChanged || textChanged; } - public static bool PasteFromClipboard(ImGui gui, ref ImTextEditState state, ref ImTextEditBuffer buffer, ImTextEditFilter filter) + public static bool PasteFromClipboard(ImGui gui, ref ImTextEditState state, ref ImTextEditBuffer buffer) { var clipboardText = gui.Input.Clipboard; if (clipboardText.Length == 0) @@ -282,44 +434,20 @@ public static bool PasteFromClipboard(ImGui gui, ref ImTextEditState state, ref return false; } - var textChanged = DeleteSelection(ref state, ref buffer); - if (TryInsert(ref state, ref buffer, clipboardText, filter)) - { - textChanged = true; - state.Caret += clipboardText.Length; - } + DeleteSelection(ref state, ref buffer); + Insert(ref state, ref buffer, clipboardText); - return textChanged; + return true; } - public static unsafe bool TryInsert(ref ImTextEditState state, ref ImTextEditBuffer buffer, char chr, ImTextEditFilter filter) + public static unsafe bool Insert(ref ImTextEditState state, ref ImTextEditBuffer buffer, char chr) { - return TryInsert(ref state, ref buffer, new ReadOnlySpan(&chr, 1), filter); + return Insert(ref state, ref buffer, new ReadOnlySpan(&chr, 1)); } - public static bool TryInsert(ref ImTextEditState state, ref ImTextEditBuffer buffer, ReadOnlySpan text, ImTextEditFilter filter) + public static bool Insert(ref ImTextEditState state, ref ImTextEditBuffer buffer, ReadOnlySpan text) { - const int MAX_STACK_ALLOC_SIZE_IN_BYTES = 2048; - - if (filter == null) - { - buffer.Insert(state.Caret, in text); - state.Caret += text.Length; - return true; - } - - var length = buffer.Length + text.Length; - var tempBuffer = length > (MAX_STACK_ALLOC_SIZE_IN_BYTES / sizeof(char)) ? new char[length] : stackalloc char[length]; - - ((ReadOnlySpan)buffer).CopyTo(tempBuffer); - if (state.Caret < buffer.Length) - { - tempBuffer[state.Caret..].CopyTo(tempBuffer[(state.Caret + text.Length)..]); - } - - text.CopyTo((tempBuffer)[state.Caret..]); - - if (!filter.IsValid(tempBuffer)) + if (text.Length == 0) { return false; } @@ -426,8 +554,8 @@ public static int FindEndOfWordOrSpacesSequence(int caret, int dir, ReadOnlySpan public static bool MoveCaretVertical( ImGui gui, - in ImRect textRect, - in TextDrawer.Layout layout, + ImRect textRect, + in ImTextLayout layout, ref ImTextEditState state, in ImTextEditBuffer buffer, int dir, @@ -440,9 +568,9 @@ public static bool MoveCaretVertical( var prevCaret = state.Caret; var prevSelection = state.Selection; - var viewPosition = CaretToViewPosition(state.Caret, gui.TextDrawer, in textRect, in layout, in buffer); + var viewPosition = CaretToViewPosition(state.Caret, gui.TextDrawer, textRect, in layout, in buffer); viewPosition.y += (-layout.LineHeight * 0.5f) + (dir * layout.LineHeight); - state.Caret = ViewToCaretPosition(viewPosition, gui.TextDrawer, in textRect, in layout, in buffer); + state.Caret = ViewToCaretPosition(viewPosition, gui.TextDrawer, textRect, in layout, in buffer); if (cmd.HasFlag(ImKeyboardCommandFlag.Selection)) { @@ -495,14 +623,15 @@ public static bool MoveCaretHorizontal( return state.Caret != prevCaret || state.Selection != prevSelection; } + // TODO: doesn't work when caret is horizontally outside of the scope public static void ScrollToCaret( ImGui gui, - in ImTextEditState state, - in ImRect textRect, - in TextDrawer.Layout layout, + ImTextEditState state, + ImRect textRect, + in ImTextLayout layout, in ImTextEditBuffer buffer) { - var viewPosition = CaretToViewPosition(state.Caret, gui.TextDrawer, in textRect, in layout, in buffer); + var viewPosition = CaretToViewPosition(state.Caret, gui.TextDrawer, textRect, in layout, in buffer); ref readonly var frame = ref gui.Layout.GetFrame(); var scrollOffset = gui.GetScrollOffset(); @@ -553,7 +682,7 @@ public static ReadOnlySpan GetSelectedText(in ImTextEditState state, in Im return ((ReadOnlySpan)buffer).Slice(begin, end - begin); } - public static int ViewToCaretPosition(Vector2 position, TextDrawer drawer, in ImRect rect, in TextDrawer.Layout layout, in ImTextEditBuffer buffer) + public static int ViewToCaretPosition(Vector2 position, ImTextDrawer drawer, ImRect rect, in ImTextLayout layout, in ImTextEditBuffer buffer) { var origin = rect.TopLeft; var line = 0; @@ -618,12 +747,12 @@ public static int ViewToCaretPosition(Vector2 position, TextDrawer drawer, in Im return caret; } - public static Vector2 CaretToViewPosition(int caret, TextDrawer drawer, in ImRect rect, in TextDrawer.Layout layout, in ImTextEditBuffer buffer) + public static Vector2 CaretToViewPosition(int caret, ImTextDrawer drawer, ImRect rect, in ImTextLayout layout, in ImTextEditBuffer buffer) { - return LineOffsetToViewPosition(FindLineAtCaretPosition(caret, in layout, out var linePosition), linePosition, buffer, in rect, drawer, in layout); + return LineOffsetToViewPosition(FindLineAtCaretPosition(caret, in layout, out var linePosition), linePosition, buffer, rect, drawer, in layout); } - public static Vector2 LineOffsetToViewPosition(int line, int offset, ReadOnlySpan buffer, in ImRect rect, TextDrawer drawer, in TextDrawer.Layout layout) + public static Vector2 LineOffsetToViewPosition(int line, int offset, ReadOnlySpan buffer, ImRect rect, ImTextDrawer drawer, in ImTextLayout layout) { var yOffset = line * -layout.LineHeight + layout.OffsetY; var xOffset = line >= layout.LinesCount ? layout.OffsetX : layout.Lines[line].OffsetX; @@ -645,7 +774,7 @@ public static Vector2 LineOffsetToViewPosition(int line, int offset, ReadOnlySpa return rect.TopLeft + new Vector2(xOffset, yOffset); } - public static int FindLineAtCaretPosition(int caret, in TextDrawer.Layout layout, out int linePosition) + public static int FindLineAtCaretPosition(int caret, in ImTextLayout layout, out int linePosition) { var line = 0; while (layout.LinesCount - 1 > line && layout.Lines[line].Count <= caret) @@ -660,16 +789,16 @@ public static int FindLineAtCaretPosition(int caret, in TextDrawer.Layout layout public static void DrawCaret(ImGui gui, int position, - in ImRect textRect, - in TextDrawer.Layout layout, + ImRect textRect, + in ImTextLayout layout, in ImTextEditStateStyle style, in ImTextEditBuffer buffer) { - var viewPosition = CaretToViewPosition(position, gui.TextDrawer, in textRect, in layout, in buffer); + var viewPosition = CaretToViewPosition(position, gui.TextDrawer, textRect, in layout, in buffer); var caretViewRect = new ImRect( viewPosition.x, viewPosition.y - layout.LineHeight, - Style.CaretWidth, + ImTheme.Active.TextEdit.CaretWidth, layout.LineHeight); if ((long)(Time.unscaledTime / CARET_BLINKING_TIME) % 2 == 0) @@ -681,8 +810,8 @@ public static void DrawCaret(ImGui gui, public static void DrawSelection(ImGui gui, int position, int size, - in ImRect textRect, - in TextDrawer.Layout layout, + ImRect textRect, + in ImTextLayout layout, in ImTextEditStateStyle style, in ImTextEditBuffer buffer) { @@ -704,8 +833,8 @@ public static void DrawSelection(ImGui gui, var lineRelativeBegin = Mathf.Max(0, begin - line.Start); var lineRelativeEnd = Mathf.Min(line.Count, end - line.Start); - var p0 = LineOffsetToViewPosition(i, lineRelativeBegin, buffer, in textRect, gui.TextDrawer, in layout); - var p1 = LineOffsetToViewPosition(i, lineRelativeEnd, buffer, in textRect, gui.TextDrawer, in layout); + var p0 = LineOffsetToViewPosition(i, lineRelativeBegin, buffer, textRect, gui.TextDrawer, in layout); + var p1 = LineOffsetToViewPosition(i, lineRelativeEnd, buffer, textRect, gui.TextDrawer, in layout); var lineSelectionRect = new ImRect( p0.x, @@ -720,7 +849,9 @@ public static void DrawSelection(ImGui gui, public ref struct ImTextEditBuffer { - private static char[] StaticBuffer = new char[1024]; + public const int DEFAULT_MUTABLE_BUFFER_CAPACITY = 1024; + + private static char[] StaticBuffer = new char[DEFAULT_MUTABLE_BUFFER_CAPACITY]; public int Length; public string InitText; @@ -748,11 +879,11 @@ public string GetString() return InitText; } - public void MakeMutable(int length) + public void MakeMutable(int capacity = DEFAULT_MUTABLE_BUFFER_CAPACITY) { if (InitText != null) { - var nextLength = Mathf.NextPowerOfTwo(Mathf.Max(InitText.Length, length)); + var nextLength = Mathf.NextPowerOfTwo(Mathf.Max(InitText.Length, capacity)); if (nextLength > StaticBuffer.Length) { Array.Resize(ref StaticBuffer, nextLength); @@ -763,12 +894,33 @@ public void MakeMutable(int length) Length = InitText.Length; InitText = null; } - else if (Buffer.Length < length) + else { - Array.Resize(ref Buffer, Mathf.NextPowerOfTwo(length)); + if (Buffer == null) + { + Buffer = StaticBuffer; + Length = 0; + } + + if (Buffer.Length < capacity) + { + Array.Resize(ref Buffer, Mathf.NextPowerOfTwo(capacity)); + } } } + public void Clear(int length) + { + MakeMutable(length); + Length = 0; + } + + public void Clear() + { + MakeMutable(Length); + Length = 0; + } + public void Delete(int position, int count) { MakeMutable(Length); @@ -791,7 +943,7 @@ public unsafe void Insert(int position, char c) Insert(position, new ReadOnlySpan(&c, 1)); } - public void Insert(int position, in ReadOnlySpan text) + public void Insert(int position, ReadOnlySpan text) { MakeMutable(Length + text.Length); @@ -816,83 +968,89 @@ public static implicit operator ReadOnlySpan(ImTextEditBuffer buffer) => public abstract class ImTextEditFilter { - public abstract bool IsValid(in ReadOnlySpan buffer); + public virtual ImTouchKeyboardType KeyboardType => ImTouchKeyboardType.Default; + + public abstract bool IsValid(ReadOnlySpan buffer); + public abstract string GetFallbackString(); } - public sealed class ImTextEditIntegerFilter : ImTextEditFilter + public abstract class ImTextEditFilterNumeric : ImTextEditFilter { - public override bool IsValid(in ReadOnlySpan buffer) - { - return long.TryParse(buffer, NumberStyles.Integer, CultureInfo.InvariantCulture, out _); - } - } - - public sealed class ImTextEditFloatFilter : ImTextEditFilter - { - public override bool IsValid(in ReadOnlySpan buffer) + public override ImTouchKeyboardType KeyboardType => ImTouchKeyboardType.Numeric; + + protected bool emptyStringIsValid; + + public ImTextEditFilterNumeric(bool emptyStringIsValid) { - return double.TryParse(buffer, NumberStyles.Float, CultureInfo.InvariantCulture, out _); + this.emptyStringIsValid = emptyStringIsValid; } + + public abstract bool TryParse(ReadOnlySpan buffer, out T value); + public abstract bool TryFormat(Span buffer, T value, out int length, ReadOnlySpan format); } - public class ImTextEditStyle + public sealed class ImTextEditIntegerFilter : ImTextEditFilterNumeric { - public static readonly ImTextEditStyle Default = new ImTextEditStyle() - { - Normal = new ImTextEditStateStyle() - { - Box = new ImBoxStyle() - { - BackColor = ImColors.Gray7, - FrontColor = ImColors.Black, - BorderColor = ImColors.Gray1, - BorderRadius = 3.0f, - BorderWidth = 1.0f - }, - SelectionColor = ImColors.Black.WithAlpha(32) - }, - Selected = new ImTextEditStateStyle() - { - Box = new ImBoxStyle() - { - BackColor = ImColors.White, - FrontColor = ImColors.Black, - BorderColor = ImColors.Black, - BorderRadius = 3.0f, - BorderWidth = 1.0f - }, - SelectionColor = ImColors.Black.WithAlpha(64) - }, - CaretWidth = 2.0f, - Padding = 2.0f, - Alignment = new ImTextAlignment(0.0f, 0.0f) - }; + public ImTextEditIntegerFilter(bool emptyStringIsValid = false) : base(emptyStringIsValid) { } - public ImTextEditStateStyle Normal; - public ImTextEditStateStyle Selected; - public float CaretWidth; - public ImPadding Padding; - public ImTextAlignment Alignment; - - public ImRect GetContentRect(ImRect rect) - { - return rect.WithPadding(Padding); - } + public override bool IsValid(ReadOnlySpan buffer) => TryParse(buffer, out _); + public override string GetFallbackString() => "0"; - public float GetControlHeight(float contentHeight) + public override bool TryParse(ReadOnlySpan buffer, out int value) { - return contentHeight + Padding.Vertical; + if (emptyStringIsValid && buffer.IsEmpty) + { + value = 0; + return true; + } + + return int.TryParse(buffer, NumberStyles.Integer, CultureInfo.InvariantCulture, out value); } + + public override bool TryFormat(Span buffer, int value, out int length, ReadOnlySpan format) => value.TryFormat(buffer, out length, format); + } + + public sealed class ImTextEditFloatFilter : ImTextEditFilterNumeric + { + // allow to use comma as decimal separator + private static readonly CultureInfo DeCulture = new("de"); + + public ImTextEditFloatFilter(bool emptyStringIsValid = false) : base(emptyStringIsValid) { } + + public override bool IsValid(ReadOnlySpan buffer) => TryParse(buffer, out _); + public override string GetFallbackString() => "0.0"; - public ImTextSettings GetTextSettings() + public override bool TryParse(ReadOnlySpan buffer, out float value) { - return new ImTextSettings(ImControls.Style.TextSize, Alignment); + if (emptyStringIsValid && buffer.IsEmpty) + { + value = 0.0f; + return true; + } + + return + float.TryParse(buffer, NumberStyles.Float, CultureInfo.InvariantCulture, out value) || + float.TryParse(buffer, NumberStyles.Float, DeCulture, out value); } + + public override bool TryFormat(Span buffer, float value, out int length, ReadOnlySpan format) => value.TryFormat(buffer, out length, format); } - public class ImTextEditStateStyle + [Serializable] + public struct ImTextEditStateStyle { public ImBoxStyle Box; public Color32 SelectionColor; } + + [Serializable] + public struct ImTextEditStyle + { + public ImTextEditStateStyle Normal; + public ImTextEditStateStyle Selected; + public float CaretWidth; + public ImTextAlignment Alignment; + public ImPadding Padding; + public bool TextWrap; + } } \ No newline at end of file diff --git a/Runtime/Scripts/Controls/ImWindow.cs b/Runtime/Scripts/Controls/ImWindow.cs index 8df0b87..09cdcbc 100644 --- a/Runtime/Scripts/Controls/ImWindow.cs +++ b/Runtime/Scripts/Controls/ImWindow.cs @@ -1,9 +1,7 @@ using System; using Imui.Core; using Imui.IO.Events; -using Imui.Rendering; -using Imui.Styling; -using Imui.Utility; +using Imui.Controls.Styling; using UnityEngine; namespace Imui.Controls @@ -13,8 +11,6 @@ public static class ImWindow private const int WINDOW_ORDER_OFFSET = 128; private const int WINDOW_FRONT_ORDER_OFFSET = 64; - public static ImWindowStyle Style = ImWindowStyle.Default; - public static void BeginWindow(this ImGui gui, string title, float width = ImWindowManager.DEFAULT_WIDTH, @@ -23,14 +19,25 @@ public static void BeginWindow(this ImGui gui, { var id = gui.PushId(title); + if (gui.IsGroupHovered(id)) + { + ref readonly var evt = ref gui.Input.MouseEvent; + if (evt.Type is ImMouseEventType.Down) + { + gui.WindowManager.RequestFocus(id); + } + } + + ref readonly var style = ref ImTheme.Active.Window; ref var state = ref gui.WindowManager.BeginWindow(id, title, width, height, flags); gui.Canvas.PushOrder(state.Order * WINDOW_ORDER_OFFSET); - gui.Canvas.PushRectMask(state.Rect, Style.Box.BorderRadius.GetMax()); + gui.Canvas.PushRectMask(state.Rect, style.Box.BorderRadius); gui.Canvas.PushClipRect(state.Rect); Back(gui, in state, out var contentRect); gui.RegisterControl(id, state.Rect); + gui.RegisterGroup(id, state.Rect); gui.Layout.Push(ImAxis.Vertical, contentRect); gui.BeginScrollable(); @@ -44,7 +51,7 @@ public static void EndWindow(this ImGui gui) var id = gui.WindowManager.EndWindow(); ref var state = ref gui.WindowManager.GetWindowState(id); - gui.Canvas.PushOrder(gui.Canvas.GetCurrentOrder() + WINDOW_FRONT_ORDER_OFFSET); + gui.Canvas.PushOrder(gui.Canvas.GetOrder() + WINDOW_FRONT_ORDER_OFFSET); Front(gui, state.Rect); @@ -58,7 +65,7 @@ public static void EndWindow(this ImGui gui) if ((state.Flags & ImWindowFlag.DisableTitleBar) == 0) { - clicked |= TitleBar(gui, state.Title, ref state, in activeRect); + clicked |= TitleBar(gui, state.Title, ref state, activeRect); } if (clicked) @@ -77,7 +84,9 @@ public static void EndWindow(this ImGui gui) public static void Back(ImGui gui, in ImWindowState state, out ImRect content) { - gui.Canvas.Rect(state.Rect, Style.Box.BackColor, Style.Box.BorderRadius); + ref readonly var style = ref ImTheme.Active.Window; + + gui.Canvas.Rect(state.Rect, style.Box.BackColor, style.Box.BorderRadius); if ((state.Flags & ImWindowFlag.DisableTitleBar) != 0) { @@ -85,27 +94,30 @@ public static void Back(ImGui gui, in ImWindowState state, out ImRect content) return; } - var titleBarRect = GetTitleBarRect(gui, in state.Rect, out _); + var titleBarRect = GetTitleBarRect(gui, state.Rect, out _); state.Rect.SplitTop(titleBarRect.H, out content); - content.AddPadding(Style.Padding); + content.AddPadding(style.ContentPadding); } - public static void Front(ImGui gui, in ImRect rect) + public static void Front(ImGui gui, ImRect rect) { - gui.Canvas.RectOutline(rect, Style.Box.BorderColor, Style.Box.BorderWidth, Style.Box.BorderRadius); + ref readonly var style = ref ImTheme.Active.Window; + gui.Canvas.RectOutline(rect, style.Box.BorderColor, style.Box.BorderWidth, style.Box.BorderRadius); } - public static bool TitleBar(ImGui gui, in ReadOnlySpan text, ref ImWindowState state, in ImRect windowRect) + public static bool TitleBar(ImGui gui, ReadOnlySpan text, ref ImWindowState state, ImRect windowRect) { + ref readonly var style = ref ImTheme.Active.Window; + var id = gui.GetNextControlId(); var hovered = gui.IsControlHovered(id); var active = gui.IsControlActive(id); - var rect = GetTitleBarRect(gui, in windowRect, out var radius); - var textSettings = GetTitleBarTextSettings(); + var rect = GetTitleBarRect(gui, windowRect, out var radius); + var textSettings = new ImTextSettings(ImTheme.Active.Controls.TextSize, style.TitleBar.Alignment); var movable = (state.Flags & ImWindowFlag.DisableMoving) == 0; - gui.Canvas.Rect(rect, Style.TitleBar.BackColor, radius); - gui.Canvas.Text(text, Style.TitleBar.FrontColor, rect, in textSettings); + gui.Canvas.Rect(rect, style.TitleBar.BackColor, radius); + gui.Canvas.Text(text, style.TitleBar.FrontColor, rect, in textSettings); var clicked = false; ref readonly var evt = ref gui.Input.MouseEvent; @@ -139,10 +151,11 @@ public static bool ResizeHandle(ImGui gui, ref ImRect rect) var id = gui.GetNextControlId(); var hovered = gui.IsControlHovered(id); - var handleRect = GetResizeHandleRect(in rect, out var radius); + var handleRect = GetResizeHandleRect(rect, out var radius); var active = gui.IsControlActive(id); + ref readonly var style = ref ImTheme.Active.Window; - var segments = MeshDrawer.CalculateSegmentsCount(radius); + var segments = ImShapes.SegmentCountForRadius(radius); var step = (1f / segments) * HALF_PI; Span buffer = stackalloc Vector2[segments + 1 + 2]; @@ -160,7 +173,7 @@ public static bool ResizeHandle(ImGui gui, ref ImRect rect) buffer[i + 1].y = cy + Mathf.Sin(a) * radius; } - gui.Canvas.ConvexFill(buffer, Style.ResizeHandleColor); + gui.Canvas.ConvexFill(buffer, style.ResizeHandleColor); var clicked = false; ref readonly var evt = ref gui.Input.MouseEvent; @@ -188,11 +201,13 @@ public static bool ResizeHandle(ImGui gui, ref ImRect rect) return clicked; } - public static ImRect GetResizeHandleRect(in ImRect rect, out float cornerRadius) + public static ImRect GetResizeHandleRect(ImRect rect, out float cornerRadius) { - cornerRadius = Mathf.Max(Style.Box.BorderRadius.BottomRight, 0); + ref readonly var style = ref ImTheme.Active.Window; + + cornerRadius = Mathf.Max(style.Box.BorderRadius.BottomRight, 0); - var handleSize = Mathf.Max(Style.ResizeHandleSize, cornerRadius); + var handleSize = Mathf.Max(style.ResizeHandleSize, cornerRadius); var handleRect = rect; handleRect.X += handleRect.W - handleSize; handleRect.W = handleSize; @@ -201,19 +216,16 @@ public static ImRect GetResizeHandleRect(in ImRect rect, out float cornerRadius) return handleRect; } - public static ImRect GetTitleBarRect(ImGui gui, in ImRect rect, out ImRectRadius cornerRadius) + public static ImRect GetTitleBarRect(ImGui gui, ImRect rect, out ImRectRadius cornerRadius) { - var height = Style.TitleBar.GetHeight(gui.GetRowHeight()); - var radiusTopLeft = Style.Box.BorderRadius.TopLeft - Style.Box.BorderWidth; - var radiusTopRight = Style.Box.BorderRadius.TopRight - Style.Box.BorderWidth; + ref readonly var style = ref ImTheme.Active.Window; + + var height = style.TitleBar.AdditionalPadding.Vertical + gui.GetRowHeight(); + var radiusTopLeft = style.Box.BorderRadius.TopLeft - style.Box.BorderWidth; + var radiusTopRight = style.Box.BorderRadius.TopRight - style.Box.BorderWidth; cornerRadius = new ImRectRadius(radiusTopLeft, radiusTopRight); - return rect.WithPadding(Style.Box.BorderWidth).SplitTop(height); - } - - public static ImTextSettings GetTitleBarTextSettings() - { - return new ImTextSettings(ImControls.Style.TextSize, Style.TitleBar.Alignment); + return rect.WithPadding(style.Box.BorderWidth).SplitTop(height); } } @@ -223,42 +235,16 @@ public struct ImWindowTitleBarStyle public Color32 BackColor; public Color32 FrontColor; public ImTextAlignment Alignment; - public ImPadding Padding; - - public float GetHeight(float contentHeight) - { - return contentHeight + Padding.Vertical; - } + public ImPadding AdditionalPadding; } [Serializable] public struct ImWindowStyle { - public static readonly ImWindowStyle Default = new ImWindowStyle() - { - Box = new ImBoxStyle() - { - BackColor = ImColors.White, - BorderColor = ImColors.Black, - BorderWidth = 1.0f, - BorderRadius = 4.0f - }, - ResizeHandleColor = ImColors.Gray2.WithAlpha(196), - ResizeHandleSize = 24, - Padding = 4, - TitleBar = new ImWindowTitleBarStyle() - { - BackColor = ImColors.Gray3, - FrontColor = ImColors.White, - Padding = 8, - Alignment = new ImTextAlignment(0.5f, 0.5f) - } - }; - public ImBoxStyle Box; public Color32 ResizeHandleColor; public float ResizeHandleSize; - public ImPadding Padding; + public ImPadding ContentPadding; public ImWindowTitleBarStyle TitleBar; } } diff --git a/Runtime/Scripts/Controls/Layout.meta b/Runtime/Scripts/Controls/Layout.meta deleted file mode 100644 index 3282a57..0000000 --- a/Runtime/Scripts/Controls/Layout.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 424a7752ebd14ba78dbca22d497adf8c -timeCreated: 1717537967 \ No newline at end of file diff --git a/Runtime/Scripts/Styling.meta b/Runtime/Scripts/Controls/Styling.meta similarity index 100% rename from Runtime/Scripts/Styling.meta rename to Runtime/Scripts/Controls/Styling.meta diff --git a/Runtime/Scripts/Controls/Styling/ImColors.cs b/Runtime/Scripts/Controls/Styling/ImColors.cs new file mode 100644 index 0000000..5ba8c68 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/ImColors.cs @@ -0,0 +1,10 @@ +using UnityEngine; + +namespace Imui.Controls.Styling +{ + public static class ImColors + { + public static readonly Color32 Black = new Color32(0, 0, 0, 255); + public static readonly Color32 White = new Color32(255, 255, 255, 255); + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Styling/ImColors.cs.meta b/Runtime/Scripts/Controls/Styling/ImColors.cs.meta similarity index 100% rename from Runtime/Scripts/Styling/ImColors.cs.meta rename to Runtime/Scripts/Controls/Styling/ImColors.cs.meta diff --git a/Runtime/Scripts/Styling/ImPadding.cs b/Runtime/Scripts/Controls/Styling/ImPadding.cs similarity index 97% rename from Runtime/Scripts/Styling/ImPadding.cs rename to Runtime/Scripts/Controls/Styling/ImPadding.cs index 9ed7637..8b0cf99 100644 --- a/Runtime/Scripts/Styling/ImPadding.cs +++ b/Runtime/Scripts/Controls/Styling/ImPadding.cs @@ -1,6 +1,6 @@ using System; -namespace Imui.Styling +namespace Imui.Controls.Styling { [Serializable] public struct ImPadding diff --git a/Runtime/Scripts/Styling/ImPadding.cs.meta b/Runtime/Scripts/Controls/Styling/ImPadding.cs.meta similarity index 100% rename from Runtime/Scripts/Styling/ImPadding.cs.meta rename to Runtime/Scripts/Controls/Styling/ImPadding.cs.meta diff --git a/Runtime/Scripts/Controls/Styling/ImSize.cs b/Runtime/Scripts/Controls/Styling/ImSize.cs new file mode 100644 index 0000000..2b8e1fe --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/ImSize.cs @@ -0,0 +1,54 @@ +using UnityEngine; + +namespace Imui.Controls.Styling +{ + public enum ImSizeType + { + Auto = 0, + Fixed = 1, + Fit = 2 + } + + public struct ImSize + { + public float Width; + public float Height; + public ImSizeType Type; + + public ImSize(float width, float height, ImSizeType type) + { + Width = width; + Height = height; + Type = type; + } + + public ImSize(float width, float height) + { + Width = width; + Height = height; + Type = ImSizeType.Fixed; + } + + public ImSize(ImSizeType type) + { + Width = 0; + Height = 0; + Type = type; + } + + public static implicit operator ImSize(Vector2 size) + { + return new ImSize(size.x, size.y); + } + + public static implicit operator ImSize((float width, float height) size) + { + return new ImSize(size.width, size.height); + } + + public static implicit operator ImSize(ImSizeType type) + { + return new ImSize(type); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/ImSize.cs.meta b/Runtime/Scripts/Controls/Styling/ImSize.cs.meta new file mode 100644 index 0000000..c739609 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/ImSize.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7820cefded654c2f90c350068d947350 +timeCreated: 1718236755 \ No newline at end of file diff --git a/Runtime/Scripts/Styling/ImStyleScope.cs b/Runtime/Scripts/Controls/Styling/ImStyleScope.cs similarity index 95% rename from Runtime/Scripts/Styling/ImStyleScope.cs rename to Runtime/Scripts/Controls/Styling/ImStyleScope.cs index 3d01c02..71f7386 100644 --- a/Runtime/Scripts/Styling/ImStyleScope.cs +++ b/Runtime/Scripts/Controls/Styling/ImStyleScope.cs @@ -1,6 +1,4 @@ -using System; - -namespace Imui.Styling +namespace Imui.Controls.Styling { public unsafe ref struct ImStyleScope where T : unmanaged { diff --git a/Runtime/Scripts/Styling/ImStyleScope.cs.meta b/Runtime/Scripts/Controls/Styling/ImStyleScope.cs.meta similarity index 100% rename from Runtime/Scripts/Styling/ImStyleScope.cs.meta rename to Runtime/Scripts/Controls/Styling/ImStyleScope.cs.meta diff --git a/Runtime/Scripts/Controls/Styling/ImTheme.cs b/Runtime/Scripts/Controls/Styling/ImTheme.cs new file mode 100644 index 0000000..905df1e --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/ImTheme.cs @@ -0,0 +1,24 @@ +using System; +using Imui.Controls.Styling.Themes; + +namespace Imui.Controls.Styling +{ + [Serializable] + public struct ImTheme + { + public static ImTheme Active = ImLightTheme.Create(); + + public string Name; + public ImControlsStyle Controls; + public ImWindowStyle Window; + public ImTextStyle Text; + public ImButtonStyle Button; + public ImCheckboxStyle Checkbox; + public ImFoldoutStyle Foldout; + public ImPanelStyle Panel; + public ImScrollStyle Scroll; + public ImTextEditStyle TextEdit; + public ImDropdownStyle Dropdown; + public ImSliderStyle Slider; + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/ImTheme.cs.meta b/Runtime/Scripts/Controls/Styling/ImTheme.cs.meta new file mode 100644 index 0000000..ef35333 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/ImTheme.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9add386b093945968790751f77479677 +timeCreated: 1720733664 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/Themes.meta b/Runtime/Scripts/Controls/Styling/Themes.meta new file mode 100644 index 0000000..74b9a52 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/Themes.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 91aa7df404fc46f1a203bcb22f9bcf17 +timeCreated: 1720733749 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs b/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs new file mode 100644 index 0000000..492b6c0 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs @@ -0,0 +1,79 @@ +using UnityEngine; + +namespace Imui.Controls.Styling.Themes +{ + public static class ImDarkTheme + { + public const string NAME = "Dark"; + + public static ImTheme Create() + { + var theme = ImLightTheme.Create(); + + theme.Name = NAME; + theme.Window.Box.BackColor = new Color32(41, 41, 41, 255); + theme.Window.Box.BorderColor = new Color32(31, 31, 31, 255); + theme.Window.Box.BorderWidth = 2.0f; + theme.Window.ResizeHandleColor = new Color32(255, 255, 255, 128); + theme.Window.TitleBar.BackColor = new Color32(51, 51, 51, 255); + theme.Window.TitleBar.FrontColor = new Color32(204, 204, 204, 255); + theme.Text.Color = new Color32(204, 204, 204, 255); + theme.Button.Normal.BackColor = new Color32(71, 71, 71, 255); + theme.Button.Normal.FrontColor = new Color32(204, 204, 204, 255); + theme.Button.Normal.BorderColor = new Color32(20, 20, 20, 255); + theme.Button.Hovered.BackColor = new Color32(77, 77, 77, 255); + theme.Button.Hovered.FrontColor = new Color32(230, 230, 230, 255); + theme.Button.Hovered.BorderColor = new Color32(21, 21, 21, 255); + theme.Button.Pressed.BackColor = new Color32(56, 56, 56, 255); + theme.Button.Pressed.FrontColor = new Color32(179, 179, 179, 255); + theme.Button.Pressed.BorderColor = new Color32(10, 10, 10, 255); + theme.Panel.Box.BackColor = new Color32(31, 31, 31, 255); + theme.Panel.Box.BorderColor = new Color32(20, 20, 20, 255); + theme.Scroll.NormalState.BackColor = new Color32(20, 20, 20, 255); + theme.Scroll.NormalState.FrontColor = new Color32(76, 76, 76, 255); + theme.Scroll.HoveredState.BackColor = new Color32(20, 20, 20, 255); + theme.Scroll.HoveredState.FrontColor = new Color32(102, 102, 102, 255); + theme.Scroll.PressedState.BackColor = new Color32(20, 20, 20, 255); + theme.Scroll.PressedState.FrontColor = new Color32(89, 89, 89, 255); + theme.TextEdit.Normal.Box.BackColor = new Color32(31, 31, 31, 255); + theme.TextEdit.Normal.Box.FrontColor = new Color32(153, 153, 153, 255); + theme.TextEdit.Normal.Box.BorderColor = new Color32(20, 20, 20, 255); + theme.TextEdit.Normal.SelectionColor = new Color32(0, 0, 0, 0); + theme.TextEdit.Selected.Box.BackColor = new Color32(36, 36, 36, 255); + theme.TextEdit.Selected.Box.FrontColor = new Color32(230, 230, 230, 255); + theme.TextEdit.Selected.Box.BorderColor = new Color32(82, 82, 82, 255); + theme.TextEdit.Selected.SelectionColor = new Color32(0, 153, 255, 82); + theme.Dropdown.OptionButton.Normal.BackColor = new Color32(255, 255, 255, 10); + theme.Dropdown.OptionButton.Normal.FrontColor = new Color32(179, 179, 179, 255); + theme.Dropdown.OptionButton.Normal.BorderColor = new Color32(0, 0, 0, 0); + theme.Dropdown.OptionButton.Hovered.BackColor = new Color32(255, 255, 255, 20); + theme.Dropdown.OptionButton.Hovered.FrontColor = new Color32(204, 204, 204, 255); + theme.Dropdown.OptionButton.Hovered.BorderColor = new Color32(0, 0, 0, 0); + theme.Dropdown.OptionButton.Pressed.BackColor = new Color32(255, 255, 255, 5); + theme.Dropdown.OptionButton.Pressed.FrontColor = new Color32(204, 204, 204, 255); + theme.Dropdown.OptionButton.Pressed.BorderColor = new Color32(0, 0, 0, 0); + theme.Dropdown.OptionButtonSelected.Normal.BackColor = new Color32(0, 107, 179, 255); + theme.Dropdown.OptionButtonSelected.Normal.FrontColor = new Color32(204, 204, 204, 255); + theme.Dropdown.OptionButtonSelected.Normal.BorderColor = new Color32(0, 0, 0, 0); + theme.Dropdown.OptionButtonSelected.Hovered.BackColor = new Color32(1, 114, 191, 255); + theme.Dropdown.OptionButtonSelected.Hovered.FrontColor = new Color32(230, 230, 230, 255); + theme.Dropdown.OptionButtonSelected.Hovered.BorderColor = new Color32(0, 0, 0, 0); + theme.Dropdown.OptionButtonSelected.Pressed.BackColor = new Color32(0, 91, 153, 255); + theme.Dropdown.OptionButtonSelected.Pressed.FrontColor = new Color32(204, 204, 204, 255); + theme.Dropdown.OptionButtonSelected.Pressed.BorderColor = new Color32(0, 0, 0, 0); + theme.Slider.Box.BackColor = new Color32(33, 33, 33, 255); + theme.Slider.Box.BorderColor = new Color32(20, 20, 20, 255); + theme.Slider.Handle.Normal.BackColor = new Color32(71, 71, 71, 255); + theme.Slider.Handle.Normal.FrontColor = new Color32(137, 146, 155, 255); + theme.Slider.Handle.Normal.BorderColor = new Color32(0, 0, 0, 0); + theme.Slider.Handle.Hovered.BackColor = new Color32(77, 77, 77, 255); + theme.Slider.Handle.Hovered.FrontColor = new Color32(151, 161, 171, 255); + theme.Slider.Handle.Hovered.BorderColor = new Color32(0, 0, 0, 0); + theme.Slider.Handle.Pressed.BackColor = new Color32(102, 102, 102, 255); + theme.Slider.Handle.Pressed.FrontColor = new Color32(123, 131, 140, 255); + theme.Slider.Handle.Pressed.BorderColor = new Color32(0, 0, 0, 0); + + return theme; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs.meta b/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs.meta new file mode 100644 index 0000000..64a2c19 --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/Themes/ImDarkTheme.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 95bf44188f5848c4972eaeda23765430 +timeCreated: 1720734316 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs b/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs new file mode 100644 index 0000000..25c2d8f --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs @@ -0,0 +1,272 @@ +using Imui.Core; +using UnityEngine; + +namespace Imui.Controls.Styling.Themes +{ + public static class ImLightTheme + { + public const string NAME = "Light"; + + public static ImTheme Create() + { + return new ImTheme() + { + Name = NAME, + Window = CreateWindowStyle(), + Text = CreateTextStyle(), + Button = CreateButtonStyle(), + Checkbox = CreateCheckboxStyle(), + Foldout = CreateFoldoutStyle(), + Panel = CreatePanelStyle(), + Scroll = CreateScrollStyle(), + TextEdit = CreateTextEditStyle(), + Dropdown = CreateDropdownStyle(), + Slider = CreateSliderStyle(), + Controls = CreateControlsStyle() + }; + } + + public static ImWindowStyle CreateWindowStyle() + { + return new ImWindowStyle() + { + Box = new ImBoxStyle() + { + BackColor = new Color32(232, 232, 232, 255), + BorderColor = new Color32(51, 51, 51, 255), + BorderWidth = 1.0f, + BorderRadius = 8.0f + }, + ResizeHandleColor = new Color32(51, 51, 51, 128), + ResizeHandleSize = 30.0f, + ContentPadding = 4.0f, + TitleBar = new ImWindowTitleBarStyle() + { + BackColor = new Color32(209, 209, 209, 255), + FrontColor = new Color32(46, 46, 46, 255), + AdditionalPadding = 2.0f, + Alignment = new ImTextAlignment(0.5f, 0.5f) + } + }; + } + + public static ImTextStyle CreateTextStyle() + { + return new ImTextStyle() + { + Color = new Color32(13, 13, 13, 255), + Alignment = new ImTextAlignment(0.0f, 0.0f) + }; + } + + public static ImButtonStyle CreateButtonStyle() + { + return new ImButtonStyle() + { + Padding = new ImPadding(4.0f, 4.0f, 0.0f, 0.0f), + Alignment = new ImTextAlignment(0.5f, 0.5f), + TextWrap = false, + BorderRadius = 4.0f, + BorderWidth = 1.0f, + Normal = new ImButtonStateStyle() + { + BackColor = new Color32(215, 215, 215, 255), + FrontColor = new Color32(13, 13, 13, 255), + BorderColor = new Color32(153, 153, 153, 255), + }, + Hovered = new ImButtonStateStyle() + { + BackColor = new Color32(219, 219, 219, 255), + FrontColor = new Color32(64, 64, 64, 255), + BorderColor = new Color32(166, 166, 166, 255), + }, + Pressed = new ImButtonStateStyle() + { + BackColor = new Color32(202, 202, 202, 255), + FrontColor = new Color32(13, 13, 13, 255), + BorderColor = new Color32(128, 128, 128, 255), + } + }; + } + + public static ImCheckboxStyle CreateCheckboxStyle() + { + return new ImCheckboxStyle() + { + CheckmarkScale = 0.6f, + TextAlignment = new ImTextAlignment(0.0f, 0.5f), + WrapText = false + }; + } + + public static ImFoldoutStyle CreateFoldoutStyle() + { + var style = new ImFoldoutStyle() + { + ArrowInnerScale = 0.6f, + ArrowOuterScale = 0.7f, + BorderWidth = 0.0f, + TextAlignment = new ImTextAlignment(0.0f, 0.5f) + }; + + return style; + } + + public static ImPanelStyle CreatePanelStyle() + { + return new ImPanelStyle() + { + Box = new ImBoxStyle + { + BackColor = new Color32(202, 202, 202, 255), + BorderColor = new Color32(128, 128, 128, 255), + BorderWidth = 1.0f, + BorderRadius = 4.0f + }, + Padding = 4.0f + }; + } + + public static ImScrollStyle CreateScrollStyle() + { + return new ImScrollStyle() + { + Size = 20.0f, + Margin = 2.0f, + Padding = 1.0f, + BorderRadius = 4.0f, + NormalState = new ImScrollBarStateStyle() + { + BackColor = new Color32(51, 51, 51, 255), + FrontColor = new Color32(255, 255, 255, 192), + }, + HoveredState = new ImScrollBarStateStyle() + { + BackColor = new Color32(51, 51, 51, 255), + FrontColor = new Color32(255, 255, 255, 230), + }, + PressedState = new ImScrollBarStateStyle() + { + BackColor = new Color32(51, 51, 51, 255), + FrontColor = new Color32(255, 255, 255, 205), + } + }; + } + + public static ImTextEditStyle CreateTextEditStyle() + { + return new ImTextEditStyle() + { + Normal = new ImTextEditStateStyle() + { + Box = new ImBoxStyle() + { + BackColor = new Color32(202, 202, 202, 255), + FrontColor = new Color32(13, 13, 13, 255), + BorderColor = new Color32(128, 128, 128, 255), + BorderRadius = 4.0f, + BorderWidth = 1.0f + }, + SelectionColor = new Color32(0, 115, 190, 102), + }, + Selected = new ImTextEditStateStyle() + { + Box = new ImBoxStyle() + { + BackColor = new Color32(230, 230, 230, 255), + FrontColor = new Color32(13, 13, 13, 255), + BorderColor = new Color32(53, 53, 53, 255), + BorderRadius = 4.0f, + BorderWidth = 1.0f + }, + SelectionColor = new Color32(0, 115, 190, 102), + }, + Padding = 4.0f, + CaretWidth = 2.0f, + Alignment = new ImTextAlignment(0.0f, 0.0f), + TextWrap = false + }; + } + + private static ImDropdownStyle CreateDropdownStyle() + { + var style = new ImDropdownStyle() + { + Alignment = new ImTextAlignment(0.0f, 0.5f), + ArrowInnerScale = 0.6f, + ArrowOuterScale = 0.7f, + MaxPanelHeight = 300.0f, + OptionsButtonsSpacing = 2.0f + }; + + style.OptionButton = CreateButtonStyle(); + style.OptionButton.Padding = 0.0f; + style.OptionButton.Padding.Left = 4.0f; + style.OptionButton.Alignment.X = 0.0f; + style.OptionButton.BorderWidth = 0.0f; + + style.OptionButtonSelected = style.OptionButton; + + style.OptionButton.Normal.BackColor = new Color32(255, 255, 255, 64); + style.OptionButton.Normal.FrontColor = new Color32(38, 38, 38, 255); + style.OptionButton.Normal.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButton.Hovered.BackColor = new Color32(255, 255, 255, 102); + style.OptionButton.Hovered.FrontColor = new Color32(51, 51, 51, 255); + style.OptionButton.Hovered.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButton.Pressed.BackColor = new Color32(255, 255, 255, 153); + style.OptionButton.Pressed.FrontColor = new Color32(13, 13, 13, 255); + style.OptionButton.Pressed.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButtonSelected.Normal.BackColor = new Color32(0, 122, 204, 255); + style.OptionButtonSelected.Normal.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButtonSelected.Normal.FrontColor = new Color32(255, 255, 255, 255); + style.OptionButtonSelected.Hovered.BackColor = new Color32(0, 137, 230, 255); + style.OptionButtonSelected.Hovered.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButtonSelected.Hovered.FrontColor = new Color32(255, 255, 255, 255); + style.OptionButtonSelected.Pressed.BackColor = new Color32(0, 107, 179, 255); + style.OptionButtonSelected.Pressed.BorderColor = new Color32(0, 0, 0, 0); + style.OptionButtonSelected.Pressed.FrontColor = new Color32(255, 255, 255, 255); + + + return style; + } + + private static ImSliderStyle CreateSliderStyle() + { + var style = new ImSliderStyle() + { + Box = new ImBoxStyle() + { + BackColor = new Color32(202, 202, 202, 255), + BorderColor = new Color32(128, 128, 128, 255), + BorderWidth = 1.0f, + BorderRadius = 4.0f + }, + Handle = CreateButtonStyle(), + Padding = 1.0f, + HandleAspectRatio = 1.0f + }; + + style.Handle.Normal.BackColor = new Color32(51, 51, 51, 255); + style.Handle.Normal.BorderColor = new Color32(0, 0, 0, 0); + style.Handle.Hovered.BackColor = new Color32(63, 63, 63, 255); + style.Handle.Hovered.BorderColor = new Color32(0, 0, 0, 0); + style.Handle.Pressed.BackColor = new Color32(25, 25, 25, 255); + style.Handle.Pressed.BorderColor = new Color32(0, 0, 0, 0); + + return style; + } + + private static ImControlsStyle CreateControlsStyle() + { + return new ImControlsStyle() + { + ExtraRowHeight = 8.0f, + TextSize = 22.0f, + ControlsSpacing = 2.0f, + InnerSpacing = 2.0f, + ScrollSpeedScale = 6.0f, + Indent = 8.0f + }; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs.meta b/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs.meta new file mode 100644 index 0000000..fe7d82e --- /dev/null +++ b/Runtime/Scripts/Controls/Styling/Themes/ImLightTheme.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3e323a1dfbc94e509df2c5ae4f98999e +timeCreated: 1720733756 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Utilities/ImMask.cs b/Runtime/Scripts/Controls/Utilities/ImMask.cs deleted file mode 100644 index 443bd25..0000000 --- a/Runtime/Scripts/Controls/Utilities/ImMask.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Imui.Core; - -namespace Imui.Controls.Utilities -{ - public static class ImMask - { - public static void BeginMaskedLayout(this ImGui gui) - { - var rect = gui.Layout.GetBoundsRect(); - gui.Canvas.PushClipRect(rect); - } - - public static void EndMaskedLayout(this ImGui gui) - { - gui.Canvas.PopClipRect(); - } - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Utilities/ImMask.cs.meta b/Runtime/Scripts/Controls/Utilities/ImMask.cs.meta deleted file mode 100644 index e9a26af..0000000 --- a/Runtime/Scripts/Controls/Utilities/ImMask.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 7fffadd7fd5d4a1091f8e1b218613659 -timeCreated: 1717541286 \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Windows.meta b/Runtime/Scripts/Controls/Windows.meta new file mode 100644 index 0000000..cc9a11c --- /dev/null +++ b/Runtime/Scripts/Controls/Windows.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ca9179f6a91b4fe49dc20a7298f62523 +timeCreated: 1718224726 \ No newline at end of file diff --git a/Runtime/Scripts/Debugging/ImDebug.cs b/Runtime/Scripts/Controls/Windows/ImDebugWindow.cs similarity index 60% rename from Runtime/Scripts/Debugging/ImDebug.cs rename to Runtime/Scripts/Controls/Windows/ImDebugWindow.cs index 1f8a881..8f0f173 100644 --- a/Runtime/Scripts/Debugging/ImDebug.cs +++ b/Runtime/Scripts/Controls/Windows/ImDebugWindow.cs @@ -1,34 +1,37 @@ using System; -using Imui.Controls; -using Imui.Controls.Layout; using Imui.Core; -using Imui.Styling; +using Imui.Controls.Styling; using Imui.Utility; using UnityEngine; -namespace Imui.Debugging +namespace Imui.Controls.Windows { - public static class ImDebug + public static class ImDebugWindow { + private const int MOVING_AVERAGE_INTERVAL = 90; + private const int FRAME_TIMES_BUFFER_SIZE = 120; + private static readonly Color32 groupColor = new Color32(255, 0, 0, 32); private static readonly Color32 controlColor = new Color32(0, 255, 0, 64); private static bool debugOverlay = false; private static char[] formatBuffer = new char[256]; - private static CircularBuffer frameTimes = new CircularBuffer(128); + private static ImCircularBuffer frameTimes = new ImCircularBuffer(FRAME_TIMES_BUFFER_SIZE); + private static float maxFrameTime; + private static float avgFrameTime; - public static void Window(ImGui gui) + public static void Draw(ImGui gui) { - gui.BeginWindow("Imui Debug"); + gui.BeginWindow("Imui Debug", width: 350, 383); var buffer = new Span(formatBuffer); var length = 0; - Append(buffer, "Hovered Control: ", ref length); + Append(buffer, "Hovered: ", ref length); Append(buffer, gui.frameData.HoveredControl.Id, ref length); Flush(gui, buffer, ref length); - Append(buffer, "Hot Control: ", ref length); + Append(buffer, "Active: ", ref length); Append(buffer, gui.GetActiveControl(), ref length); Flush(gui, buffer, ref length); @@ -36,27 +39,32 @@ public static void Window(ImGui gui) Append(buffer, gui.Storage.OccupiedSize, ref length); Append(buffer, "/", ref length); Append(buffer, gui.Storage.Capacity, ref length); + Append(buffer, " bytes", ref length); Flush(gui, buffer, ref length); - - Append(buffer, "Frametime: ", ref length); - Append(buffer, Time.deltaTime * 1000, ref length, "0.0"); - Append(buffer, "ms", ref length); + + Append(buffer, "Vertices: ", ref length); + Append(buffer, gui.frameData.VerticesCount, ref length); Flush(gui, buffer, ref length); - - Append(buffer, "FPS:", ref length); - Append(buffer, 1 / Time.deltaTime, ref length, "0"); + + Append(buffer, "Indices: ", ref length); + Append(buffer, gui.frameData.IndicesCount, ref length); + Flush(gui, buffer, ref length); + + Append(buffer, "FPS: ", ref length); + Append(buffer, avgFrameTime <= 0 ? 0 : 1 / avgFrameTime, ref length, "0"); + Append(buffer, " (", ref length); + Append(buffer, avgFrameTime * 1000, ref length, "0.0"); + Append(buffer, "ms", ref length); + Append(buffer, ")", ref length); Flush(gui, buffer, ref length); - frameTimes.PushFront(Time.deltaTime); + AppendFrameTime(); DrawFrametimeGraph(gui); - if (gui.ButtonFitted(debugOverlay ? "Disable Overlay" : "Enable Overlay")) - { - debugOverlay = !debugOverlay; - } + gui.Checkbox(ref debugOverlay, "Highlight Controls/Groups"); #if IMUI_DEBUG - gui.Checkmark(ref gui.MeshRenderer.Wireframe, "Wireframe"); + gui.Checkbox(ref gui.MeshRenderer.Wireframe, "Wireframe"); #endif gui.EndWindow(); @@ -76,26 +84,43 @@ public static void Window(ImGui gui) } } - private static void DrawFrametimeGraph(ImGui gui) + private static void AppendFrameTime() { - gui.AddSpacing(); + frameTimes.PushFront(Time.deltaTime); - var width = gui.GetAvailableWidth(); - var height = 200.0f; - var rect = gui.Layout.AddRect(width, height); + maxFrameTime = 0.0f; + avgFrameTime = 0.0f; - var min = 0; - var max = Application.targetFrameRate * 2.0f; - - for (int i = 0; i < frameTimes.Count; ++i) + var avgCount = 0; + + for (int i = frameTimes.Count - 1; i >= 0; --i) { - var value = frameTimes.Array[i] * 2; - if (value > max) + var value = frameTimes.Get(i); + if (value > maxFrameTime) + { + maxFrameTime = value; + } + + if (avgCount < MOVING_AVERAGE_INTERVAL) { - max = value; + avgFrameTime += value; + avgCount++; } } + avgFrameTime /= avgCount; + } + + private static void DrawFrametimeGraph(ImGui gui) + { + gui.AddSpacing(); + + var width = gui.GetLayoutWidth(); + var height = 100.0f; + var rect = gui.Layout.AddRect(width, height); + var min = 0.0f; + var max = Mathf.Max(maxFrameTime, Mathf.RoundToInt(avgFrameTime / 0.004f) * 0.004f * 2); + Span points = stackalloc Vector2[frameTimes.Count]; var nw = frameTimes.Count / (float)frameTimes.Capacity; for (int i = 0; i < frameTimes.Count; ++i) @@ -106,9 +131,10 @@ private static void DrawFrametimeGraph(ImGui gui) points[i] = new Vector2(rect.X + xn * rect.W * nw, rect.Y + yn * rect.H); } - - gui.Canvas.RectWithOutline(rect, ImColors.Gray6, ImColors.Black, 1.0f); - gui.Canvas.LineSimple(points, ImColors.Black, false, 1); + + gui.BeginPanel(rect); + gui.Canvas.Line(points, ImTheme.Active.Text.Color, false, 1); + gui.EndPanel(); } private static void Flush(ImGui gui, Span buffer, ref int length) diff --git a/Runtime/Scripts/Debugging/ImDebug.cs.meta b/Runtime/Scripts/Controls/Windows/ImDebugWindow.cs.meta similarity index 100% rename from Runtime/Scripts/Debugging/ImDebug.cs.meta rename to Runtime/Scripts/Controls/Windows/ImDebugWindow.cs.meta diff --git a/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs b/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs new file mode 100644 index 0000000..ab84736 --- /dev/null +++ b/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs @@ -0,0 +1,285 @@ +using System; +using System.Linq; +using Imui.Controls.Styling; +using Imui.Controls.Styling.Themes; +using Imui.Core; + +namespace Imui.Controls.Windows +{ + public static class ImDemoWindow + { + private static char[] formatBuffer = new char[256]; + + private static bool checkmarkValue; + private static int selectedValue = -1; + private static float sliderValue; + private static int selectedTheme; + private static string[] themes = { ImLightTheme.NAME, ImDarkTheme.NAME }; + private static string[] values = + { + "Value 1", "Value 2", "Value 3", "Value 4", "Value 5", "Value 6", + "Value 7", "Value 8", "Value 9", "Value 10", "Value 11", "Value 12" + }; + private static string singleLineText = "Single line text edit"; + private static string multiLineText = "Multiline text\nedit"; + private static float floatValue; + private static int intValue; + private static bool isReadOnly; + private static bool[] checkboxes = new bool[4]; + private static bool showDebugWindow = false; + private static int clicks; + private static int nestedFoldouts; + + public static void Draw(ImGui gui) + { + gui.BeginWindow("Demo", width: 700, height: 700); + + gui.BeginFoldout("Widgets", out var widgetsOpen); + gui.BeginIndent(); + if (widgetsOpen) + { + DrawWidgetsPage(gui); + } + gui.EndIndent(); + gui.EndFoldout(); + + gui.BeginReadOnly(isReadOnly); + + gui.BeginFoldout("Layout", out var layoutOpen); + gui.BeginIndent(); + if (layoutOpen) + { + DrawLayoutPage(gui); + } + gui.EndIndent(); + gui.EndFoldout(); + + gui.BeginFoldout("Style", out var styleOpen); + gui.BeginIndent(); + if (styleOpen) + { + DrawStylePage(gui); + } + gui.EndIndent(); + gui.EndFoldout(); + + gui.BeginFoldout("Other", out var otherOpen); + gui.BeginIndent(); + if (otherOpen) + { + DrawOtherPage(gui); + } + gui.EndIndent(); + gui.EndFoldout(); + + gui.EndReadOnly(); + + gui.EndWindow(); + + if (showDebugWindow) + { + gui.PushId("DemoDebugWindow"); + ImDebugWindow.Draw(gui); + gui.PopId(); + } + } + + private static void DrawWidgetsPage(ImGui gui) + { + void DrawNestedFoldout(ImGui gui, int current, ref int total) + { + const int MAX = 8; + + var label = current == 0 ? "Nested Foldout" : Format("Nested Foldout ", current, "0"); + + gui.BeginFoldout(label, out var nestedFoldoutOpen); + gui.BeginIndent(); + if (nestedFoldoutOpen) + { + if (current < total) + { + DrawNestedFoldout(gui, current + 1, ref total); + } + else if (current == total) + { + if (total == MAX) + { + gui.Text("Let's just stop here"); + if (gui.Button("Reset")) + { + total = 0; + } + } + else if (gui.Button("Add one more")) + { + total++; + } + } + } + gui.EndIndent(); + gui.EndFoldout(); + } + + gui.Checkbox(ref isReadOnly, "Read Only"); + + gui.BeginReadOnly(isReadOnly); + gui.BeginDropdown("Custom Dropdown", out var open); + gui.BeginIndent(); + if (open) + { + var allTrue = true; + + gui.AddSpacing(); + gui.BeginHorizontal(); + for (int i = 0; i < checkboxes.Length; ++i) + { + gui.Checkbox(ref checkboxes[i]); + allTrue &= checkboxes[i]; + } + gui.EndHorizontal(); + + if (allTrue) + { + gui.Text("Bingo!"); + } + } + gui.EndIndent(); + gui.EndDropdown(); + + DrawNestedFoldout(gui, 0, ref nestedFoldouts); + + if (gui.Button(Format("Clicks ", clicks, "0"), ImSizeType.Fit)) + { + clicks++; + } + + if (gui.Button("Reset Clicks")) + { + clicks = 0; + } + + gui.Checkbox(ref checkmarkValue, "Checkmark"); + gui.Dropdown(ref selectedValue, values, defaultLabel: "Not Selected"); + gui.Slider(ref sliderValue, 0.0f, 1.0f); + gui.Text(Format("Slider value: ", sliderValue, "0.00")); + gui.TextEdit(ref singleLineText, multiline: false); + gui.TextEdit(ref multiLineText, (gui.GetLayoutWidth(), 200)); + + gui.Text("Float TextEdit"); + gui.AddSpacing(); + gui.BeginHorizontal(); + gui.BeginHorizontal(width: gui.GetLayoutWidth() * 0.7f); + gui.TextEdit(ref floatValue); + gui.EndHorizontal(); + gui.Text(Format(" = ", floatValue)); + gui.EndHorizontal(); + + gui.Text("Integer TextEdit"); + gui.AddSpacing(); + gui.BeginHorizontal(); + gui.BeginHorizontal(width: gui.GetLayoutWidth() * 0.7f); + gui.TextEdit(ref intValue); + gui.EndHorizontal(); + gui.Text(Format(" = ", intValue)); + gui.EndHorizontal(); + + gui.AddSpacing(); + + gui.EndReadOnly(); + } + + private static void DrawLayoutPage(ImGui gui) + { + gui.AddSpacing(); + + gui.BeginHorizontal(); + for (int i = 0; i < 3; ++i) + { + gui.Button("Horizontal", ImSizeType.Fit); + } + gui.EndHorizontal(); + + gui.AddSpacing(); + + gui.BeginVertical(); + for (int i = 0; i < 3; ++i) + { + gui.Button("Vertical", ImSizeType.Fit); + } + gui.EndVertical(); + + gui.AddSpacing(); + + var grid = gui.BeginGrid(5, gui.GetRowHeight()); + for (int i = 0; i < 12; ++i) + { + var cell = gui.GridNextCell(ref grid); + gui.TextAutoSize(Format("Grid cell ", i, "0"), cell); + } + gui.EndGrid(in grid); + } + + private static void DrawStylePage(ImGui gui) + { + selectedTheme = GetThemeIndex(ImTheme.Active.Name); + + gui.Text("Theme"); + if (gui.Dropdown(ref selectedTheme, themes, defaultLabel: "Unknown")) + { + ImTheme.Active = CreateTheme(selectedTheme); + } + + gui.Text(Format("Text Size: ", ImTheme.Active.Controls.TextSize)); + gui.Slider(ref ImTheme.Active.Controls.TextSize, 6, 128); + + gui.Text(Format("Spacing: ", ImTheme.Active.Controls.ControlsSpacing)); + gui.Slider(ref ImTheme.Active.Controls.ControlsSpacing, 0, 32); + + gui.Text( Format("Extra Row Size: ", ImTheme.Active.Controls.ExtraRowHeight)); + gui.Slider(ref ImTheme.Active.Controls.ExtraRowHeight, 0, 32); + + gui.Text( Format("Indent: ", ImTheme.Active.Controls.Indent)); + gui.Slider(ref ImTheme.Active.Controls.Indent, 0, 32); + + if (gui.Button("Reset")) + { + ImTheme.Active = CreateTheme(selectedTheme); + } + } + + private static void DrawOtherPage(ImGui gui) + { + gui.Checkbox(ref showDebugWindow, "Show Debug Window"); + } + + private static int GetThemeIndex(string name) + { + for (int i = 0; i < themes.Length; ++i) + { + if (themes[i] == name) + { + return i; + } + } + + return -1; + } + + private static ImTheme CreateTheme(int index) + { + return index switch + { + 1 => ImDarkTheme.Create(), + _ => ImLightTheme.Create() + }; + } + + private static ReadOnlySpan Format(ReadOnlySpan prefix, float value, ReadOnlySpan format = default) + { + var dst = new Span(formatBuffer); + prefix.CopyTo(dst); + value.TryFormat(dst[prefix.Length..], out var written, format); + return dst[..(prefix.Length + written)]; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs.meta b/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs.meta new file mode 100644 index 0000000..f6a445f --- /dev/null +++ b/Runtime/Scripts/Controls/Windows/ImDemoWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bd69492548a54fe291f8106935d44ae2 +timeCreated: 1718321917 \ No newline at end of file diff --git a/Runtime/Scripts/Core/ImCanvas.Utility.cs b/Runtime/Scripts/Core/ImCanvas.Utility.cs index b8e406d..6e34f11 100644 --- a/Runtime/Scripts/Core/ImCanvas.Utility.cs +++ b/Runtime/Scripts/Core/ImCanvas.Utility.cs @@ -1,71 +1,95 @@ -using Imui.Utility; using UnityEngine; namespace Imui.Core { public partial class ImCanvas { - public int GetCurrentOrder() => GetActiveMeshSettings().Order; - - public void PopTexture() => PopMeshSettings(); + public void PopTexture() => PopSettings(); public void PushTexture(Texture texture) { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.MainTex = texture; - PushMeshSettings(in prop); + PushSettings(in prop); + } + + public int GetOrder() + { + ref readonly var settings = ref GetActiveSettings(); + return settings.Order; } - public void PopOrder() => PopMeshSettings(); + public void PopOrder() => PopSettings(); public void PushOrder(int order) { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.Order = order; - PushMeshSettings(in prop); + PushSettings(in prop); } - public void PopMaterial() => PopMeshSettings(); + public void PopMaterial() => PopSettings(); public void PushMaterial(Material mat) { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.Material = mat; - PushMeshSettings(in prop); + PushSettings(in prop); } - public void PopClipRect() => PopMeshSettings(); + public void PopContrast() => PopSettings(); + public void PushDefaultContrast() => PushContrast(0.0f); + public void PushContrast(float contrast) + { + var prop = GetActiveSettingsCopy(); + prop.Contrast = contrast; + PushSettings(in prop); + } + + public void PopClipRect() => PopSettings(); public void PushClipRect(ImRect rect) { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); var clipRect = (Rect)rect; if (prop.ClipRect.Enabled) { - clipRect = prop.ClipRect.Rect.Intersection(clipRect); + clipRect = GetIntersectingRect(prop.ClipRect.Rect, clipRect); } prop.ClipRect.Enabled = true; prop.ClipRect.Rect = clipRect; - PushMeshSettings(in prop); + PushSettings(in prop); } public void PushNoClipRect() { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.ClipRect.Enabled = false; - PushMeshSettings(in prop); + PushSettings(in prop); } - public void PopRectMask() => PopMeshSettings(); + public void PopRectMask() => PopSettings(); + // TODO (artem-s): probably should implement masking with different radii, if I'm making this API... + public void PushRectMask(ImRect rect, ImRectRadius radius) => PushRectMask(rect, radius.RadiusForMask()); public void PushRectMask(ImRect rect, float radius) { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.MaskRect.Enabled = true; prop.MaskRect.Rect = (Rect)rect; prop.MaskRect.Radius = radius; - PushMeshSettings(in prop); + PushSettings(in prop); } public void PushNoRectMask() { - var prop = GetActiveMeshSettings(); + var prop = GetActiveSettingsCopy(); prop.MaskRect.Enabled = false; - PushMeshSettings(prop); + PushSettings(prop); + } + + private static Rect GetIntersectingRect(in Rect r1, in Rect r2) + { + var x1 = Mathf.Max(r1.x, r2.x); + var y1 = Mathf.Max(r1.y, r2.y); + var x2 = Mathf.Min(r1.x + r1.width, r2.x + r2.width); + var y2 = Mathf.Min(r1.y + r1.height, r2.y + r2.height); + + return new Rect(x1, y1, x2 - x1, y2 - y1); } } } \ No newline at end of file diff --git a/Runtime/Scripts/Core/ImCanvas.cs b/Runtime/Scripts/Core/ImCanvas.cs index 489d804..b2d8410 100644 --- a/Runtime/Scripts/Core/ImCanvas.cs +++ b/Runtime/Scripts/Core/ImCanvas.cs @@ -2,44 +2,33 @@ using Imui.Rendering; using Imui.Utility; using UnityEngine; -using UnityEngine.Profiling; namespace Imui.Core { + public struct ImCanvasSettings + { + public Material Material; + public ImMeshClipRect ClipRect; + public ImMeshMaskRect MaskRect; + public Texture MainTex; + public Texture FontTex; + public int Order; + public float Contrast; + } + public partial class ImCanvas : IDisposable { public const int DEFAULT_ORDER = 0; - // TODO (artem-s): allow to change drawing depth - private const int DEFAULT_DEPTH = 0; - private const int DEFAULT_TEX_W = 4; private const int DEFAULT_TEX_H = 4; private const int MESH_SETTINGS_CAPACITY = 32; - private const int TEMP_POINTS_BUFFER_CAPACITY = 1024; private const float LINE_THICKNESS_THRESHOLD = 0.01f; private const string SHADER_NAME = "imui_default"; - private struct TextureInfo - { - public int Id; - public Texture2D Texture; - public Vector4 ScaleOffset; - } - - public struct MeshSettings - { - public Material Material; - public MeshClipRect ClipRect; - public MeshMaskRect MaskRect; - public int Order; - public Texture MainTex; - public Texture FontTex; - } - private static Texture2D CreateDefaultTexture() { var pixels = new Color32[DEFAULT_TEX_W * DEFAULT_TEX_H]; @@ -54,22 +43,21 @@ private static Texture2D CreateDefaultTexture() public ImRect ScreenRect => new ImRect(Vector2.zero, ScreenSize); public Vector2 ScreenSize => screenSize; - public Vector4 DefaultTexScaleOffset => defaultTexScaleOffset; + + public int DrawingDepth = 0; + public Vector4 TexScaleOffset = new(1, 1, 0, 0); private Shader shader; private Material material; private Texture2D defaultTexture; - private DynamicArray texturesInfo; - private DynamicArray meshSettingsStack; + private ImDynamicArray settingsStack; private Vector2 screenSize; - private ResizeableBuffer tempPointsBuffer; private bool disposed; - private readonly Vector4 defaultTexScaleOffset; - private readonly MeshDrawer meshDrawer; - private readonly TextDrawer textDrawer; + private readonly ImMeshDrawer meshDrawer; + private readonly ImTextDrawer textDrawer; - public ImCanvas(MeshDrawer meshDrawer, TextDrawer textDrawer) + public ImCanvas(ImMeshDrawer meshDrawer, ImTextDrawer textDrawer) { this.meshDrawer = meshDrawer; this.textDrawer = textDrawer; @@ -77,10 +65,7 @@ public ImCanvas(MeshDrawer meshDrawer, TextDrawer textDrawer) shader = Resources.Load(SHADER_NAME); material = new Material(shader); defaultTexture = CreateDefaultTexture(); - defaultTexScaleOffset = new Vector4(1, 1, 0, 0); - texturesInfo = new DynamicArray(capacity: 64); - meshSettingsStack = new DynamicArray(MESH_SETTINGS_CAPACITY); - tempPointsBuffer = new ResizeableBuffer(TEMP_POINTS_BUFFER_CAPACITY); + settingsStack = new ImDynamicArray(MESH_SETTINGS_CAPACITY); } public void SetScreen(Vector2 screenSize) @@ -93,41 +78,41 @@ public void Clear() meshDrawer.Clear(); } - public void PushMeshSettings(in MeshSettings settings) + public void PushSettings(in ImCanvasSettings settings) { - meshSettingsStack.Push(in settings); + settingsStack.Push(in settings); meshDrawer.NextMesh(); - ApplyMeshSettings(); + ApplySettings(); } - public void PopMeshSettings() + public void PopSettings() { - meshSettingsStack.Pop(); + settingsStack.Pop(); - if (meshSettingsStack.Count > 0) + if (settingsStack.Count > 0) { meshDrawer.NextMesh(); - ApplyMeshSettings(); + ApplySettings(); } } - internal ref readonly MeshSettings GetActiveMeshSettingsRef() + internal ref readonly ImCanvasSettings GetActiveSettings() { - return ref meshSettingsStack.Peek(); + return ref settingsStack.Peek(); } - public MeshSettings GetActiveMeshSettings() + public ImCanvasSettings GetActiveSettingsCopy() { - return meshSettingsStack.Peek(); + return settingsStack.Peek(); } - public MeshSettings CreateDefaultMeshSettings() + public ImCanvasSettings CreateDefaultSettings() { - return new MeshSettings() + return new ImCanvasSettings() { Order = DEFAULT_ORDER, - ClipRect = new MeshClipRect() + ClipRect = new ImMeshClipRect() { Enabled = true, Rect = new Rect(Vector2.zero, screenSize) @@ -138,10 +123,10 @@ public MeshSettings CreateDefaultMeshSettings() }; } - private void ApplyMeshSettings() + private void ApplySettings() { ref var mesh = ref meshDrawer.GetMesh(); - ref var settings = ref meshSettingsStack.Peek(); + ref var settings = ref settingsStack.Peek(); mesh.FontTex = settings.FontTex; mesh.MainTex = settings.MainTex; @@ -149,13 +134,14 @@ private void ApplyMeshSettings() mesh.Order = settings.Order; mesh.ClipRect = settings.ClipRect; mesh.MaskRect = settings.MaskRect; + mesh.Contrast = settings.Contrast; } public bool Cull(ImRect rect) { var r = (Rect)rect; - ref var settings = ref meshSettingsStack.Peek(); + ref var settings = ref settingsStack.Peek(); if (settings.ClipRect.Enabled && !settings.ClipRect.Rect.Overlaps(r)) { return true; @@ -168,8 +154,25 @@ public bool Cull(ImRect rect) return !ScreenRect.Overlaps(rect); } + + public void Circle(Vector2 position, float radius, Color32 color) + { + var rect = new ImRect(position.x - radius, position.y - radius, radius * 2, radius * 2); + Ellipse(rect, color); + } + + public void Ellipse(ImRect rect, Color32 color) + { + if (Cull(rect)) + { + return; + } + + var path = ImShapes.Ellipse(rect); + ConvexFill(path, color); + } - public void Rect(ImRect rect, Color32 color, Vector4 texScaleOffset) + public void Rect(ImRect rect, Color32 color) { if (Cull(rect)) { @@ -177,20 +180,20 @@ public void Rect(ImRect rect, Color32 color, Vector4 texScaleOffset) } meshDrawer.Color = color; - meshDrawer.ScaleOffset = texScaleOffset; - meshDrawer.Atlas = MeshDrawer.MAIN_TEX_ID; - meshDrawer.Depth = DEFAULT_DEPTH; + meshDrawer.ScaleOffset = TexScaleOffset; + meshDrawer.Atlas = ImMeshDrawer.MAIN_TEX_ID; + meshDrawer.Depth = DrawingDepth; meshDrawer.AddQuad(rect.X, rect.Y, rect.W, rect.H); } - public void Rect(ImRect rect, Color32 color, ImRectRadius radius = default) + public void Rect(ImRect rect, Color32 color, ImRectRadius radius) { if (Cull(rect)) { return; } - var path = GenerateRectOutline(rect, radius); + var path = ImShapes.Rect(rect, radius); ConvexFill(path, color); } @@ -201,19 +204,19 @@ public void RectOutline(ImRect rect, Color32 color, float thickness, ImRectRadiu return; } - var path = GenerateRectOutline(rect, radius); + var path = ImShapes.Rect(rect, radius); LineMiter(path, color, true, thickness, bias); } - public void RectWithOutline(ImRect rect, Color32 backColor, Color32 outlineColor, float thickness, ImRectRadius radius = default, float bias = 0.0f) + public void RectWithOutline(ImRect rect, Color32 color, Color32 outlineColor, float thickness, ImRectRadius radius = default, float bias = 0.0f) { if (Cull(rect)) { return; } - var path = GenerateRectOutline(rect, radius); - ConvexFill(path, backColor); + var path = ImShapes.Rect(rect, radius); + ConvexFill(path, color); if (thickness >= LINE_THICKNESS_THRESHOLD) { @@ -221,14 +224,14 @@ public void RectWithOutline(ImRect rect, Color32 backColor, Color32 outlineColor } } - public void Text(in ReadOnlySpan text, Color32 color, Vector2 position, float size) + public void Text(ReadOnlySpan text, Color32 color, Vector2 position, float size) { textDrawer.Color = color; - textDrawer.Depth = DEFAULT_DEPTH; + textDrawer.Depth = DrawingDepth; textDrawer.AddText(text, size / textDrawer.FontRenderSize, position.x, position.y); } - public void Text(in ReadOnlySpan text, Color32 color, Vector2 position, in TextDrawer.Layout layout) + public void Text(ReadOnlySpan text, Color32 color, Vector2 position, in ImTextLayout layout) { var rect = new ImRect(position.x + layout.OffsetX, position.y - layout.Height + layout.OffsetY, layout.Width, layout.Height); if (Cull(rect)) @@ -237,19 +240,19 @@ public void Text(in ReadOnlySpan text, Color32 color, Vector2 position, in } textDrawer.Color = color; - textDrawer.Depth = DEFAULT_DEPTH; + textDrawer.Depth = DrawingDepth; textDrawer.AddTextWithLayout(text, in layout, position.x, position.y); } - public void Text(in ReadOnlySpan text, Color32 color, ImRect rect, in ImTextSettings settings) + public void Text(ReadOnlySpan text, Color32 color, ImRect rect, in ImTextSettings settings) { - ref readonly var layout = ref textDrawer.BuildTempLayout(text, rect.W, rect.H, settings.Align.X, settings.Align.Y, settings.Size); + ref readonly var layout = ref textDrawer.BuildTempLayout(text, rect.W, rect.H, settings.Align.X, settings.Align.Y, settings.Size, settings.Wrap); Text(text, color, rect.TopLeft, in layout); } - public void Text(in ReadOnlySpan text, Color32 color, ImRect rect, in ImTextSettings settings, out ImRect textRect) + public void Text(ReadOnlySpan text, Color32 color, ImRect rect, in ImTextSettings settings, out ImRect textRect) { - ref readonly var layout = ref textDrawer.BuildTempLayout(text, rect.W, rect.H, settings.Align.X, settings.Align.Y, settings.Size); + ref readonly var layout = ref textDrawer.BuildTempLayout(text, rect.W, rect.H, settings.Align.X, settings.Align.Y, settings.Size, settings.Wrap); textRect = new ImRect( rect.X + layout.OffsetX, @@ -260,7 +263,7 @@ public void Text(in ReadOnlySpan text, Color32 color, ImRect rect, in ImTe Text(text, color, rect.TopLeft, in layout); } - public void LineSimple(in ReadOnlySpan path, Color32 color, bool closed, float thickness, float bias = 0.5f) + public void Line(ReadOnlySpan path, Color32 color, bool closed, float thickness, float bias = 0.5f) { if (thickness <= 0) { @@ -270,13 +273,13 @@ public void LineSimple(in ReadOnlySpan path, Color32 color, bool closed bias = Mathf.Clamp01(bias); meshDrawer.Color = color; - meshDrawer.ScaleOffset = defaultTexScaleOffset; - meshDrawer.Atlas = MeshDrawer.MAIN_TEX_ID; - meshDrawer.Depth = DEFAULT_DEPTH; - meshDrawer.AddLine(in path, closed, thickness, bias, 1.0f - bias); + meshDrawer.ScaleOffset = TexScaleOffset; + meshDrawer.Atlas = ImMeshDrawer.MAIN_TEX_ID; + meshDrawer.Depth = DrawingDepth; + meshDrawer.AddLine(path, closed, thickness, bias, 1.0f - bias); } - public void LineMiter(in ReadOnlySpan path, Color32 color, bool closed, float thickness, float bias = 0.5f) + public void LineMiter(ReadOnlySpan path, Color32 color, bool closed, float thickness, float bias = 0.5f) { if (thickness <= 0) { @@ -286,100 +289,19 @@ public void LineMiter(in ReadOnlySpan path, Color32 color, bool closed, bias = Mathf.Clamp01(bias); meshDrawer.Color = color; - meshDrawer.ScaleOffset = defaultTexScaleOffset; - meshDrawer.Atlas = MeshDrawer.MAIN_TEX_ID; - meshDrawer.Depth = DEFAULT_DEPTH; - meshDrawer.AddLineMiter(in path, closed, thickness, bias, 1.0f - bias); + meshDrawer.ScaleOffset = TexScaleOffset; + meshDrawer.Atlas = ImMeshDrawer.MAIN_TEX_ID; + meshDrawer.Depth = DrawingDepth; + meshDrawer.AddLineMiter(path, closed, thickness, bias, 1.0f - bias); } - public void ConvexFill(in ReadOnlySpan points, Color32 color) + public void ConvexFill(ReadOnlySpan points, Color32 color) { meshDrawer.Color = color; - meshDrawer.ScaleOffset = defaultTexScaleOffset; - meshDrawer.Atlas = MeshDrawer.MAIN_TEX_ID; - meshDrawer.Depth = DEFAULT_DEPTH; - meshDrawer.AddFilledConvexMesh(in points); - } - - public Span GenerateRectOutline(ImRect rect, ImRectRadius radius) - { - radius.Clamp(Mathf.Min(rect.W, rect.H) / 2.0f); - - var segments = MeshDrawer.CalculateSegmentsCount(radius.GetMax()); - var span = tempPointsBuffer.AsSpan((segments + 1) * 4); - - GenerateRectOutline(span, rect, radius, segments); - - return span; - } - - public void GenerateRectOutline(Span buffer, ImRect rect, ImRectRadius radius, int segments) - { - const float PI = Mathf.PI; - const float HALF_PI = PI / 2; - - Profiler.BeginSample("ImCanvas.GenerateRectOutlinePath"); - - var p = 0; - var step = (1f / segments) * HALF_PI; - - var cx = rect.X + rect.W - radius.BottomRight; - var cy = rect.Y + radius.BottomRight; - buffer[p].x = cx + Mathf.Cos(PI + HALF_PI) * radius.BottomRight; - buffer[p].y = cy + Mathf.Sin(PI + HALF_PI) * radius.BottomRight; - p++; - - for (int i = 0; i < segments; ++i) - { - var a = PI + HALF_PI + step * (i + 1); - buffer[p].x = cx + Mathf.Cos(a) * radius.BottomRight; - buffer[p].y = cy + Mathf.Sin(a) * radius.BottomRight; - p++; - } - - cx = rect.X + rect.W - radius.TopRight; - cy = rect.Y + rect.H - radius.TopRight; - buffer[p].x = cx + Mathf.Cos(0) * radius.TopRight; - buffer[p].y = cy + Mathf.Sin(0) * radius.TopRight; - p++; - - for (int i = 0; i < segments; ++i) - { - var a = 0 + step * (i + 1); - buffer[p].x = cx + Mathf.Cos(a) * radius.TopRight; - buffer[p].y = cy + Mathf.Sin(a) * radius.TopRight; - p++; - } - - cx = rect.X + radius.TopLeft; - cy = rect.Y + rect.H - radius.TopLeft; - buffer[p].x = cx + Mathf.Cos(HALF_PI) * radius.TopLeft; - buffer[p].y = cy + Mathf.Sin(HALF_PI) * radius.TopLeft; - p++; - - for (int i = 0; i < segments; ++i) - { - var a = HALF_PI + step * (i + 1); - buffer[p].x = cx + Mathf.Cos(a) * radius.TopLeft; - buffer[p].y = cy + Mathf.Sin(a) * radius.TopLeft; - p++; - } - - cx = rect.X + radius.BottomLeft; - cy = rect.Y + radius.BottomLeft; - buffer[p].x = cx + Mathf.Cos(PI) * radius.BottomLeft; - buffer[p].y = cy + Mathf.Sin(PI) * radius.BottomLeft; - p++; - - for (int i = 0; i < segments; ++i) - { - var a = PI + step * (i + 1); - buffer[p].x = cx + Mathf.Cos(a) * radius.BottomLeft; - buffer[p].y = cy + Mathf.Sin(a) * radius.BottomLeft; - p++; - } - - Profiler.EndSample(); + meshDrawer.ScaleOffset = TexScaleOffset; + meshDrawer.Atlas = ImMeshDrawer.MAIN_TEX_ID; + meshDrawer.Depth = DrawingDepth; + meshDrawer.AddFilledConvexMesh(points); } public void Dispose() diff --git a/Runtime/Scripts/Core/ImGui.cs b/Runtime/Scripts/Core/ImGui.cs index 8374153..e7d2966 100644 --- a/Runtime/Scripts/Core/ImGui.cs +++ b/Runtime/Scripts/Core/ImGui.cs @@ -3,13 +3,10 @@ using Imui.Rendering; using Imui.Utility; using UnityEngine; -using MeshRenderer = Imui.Rendering.MeshRenderer; namespace Imui.Core { - // TODO (artem-s): - // * Check for per-frame allocations and overall optimization - + // TODO (artem-s): its time to add dark and light themes public class ImGui : IDisposable { private const int CONTROL_IDS_CAPACITY = 32; @@ -21,9 +18,12 @@ public class ImGui : IDisposable private const float UI_SCALE_MIN = 0.05f; private const float UI_SCALE_MAX = 16.0f; + private const float READONLY_SCOPE_CONTRAST = 0.3f; + private const int FLOATING_CONTROLS_CAPACITY = 128; private const int HOVERED_GROUPS_CAPACITY = 16; - private const int SCOPES_STACK_CAPACITY = 32; + private const int SCROLL_RECT_STACK_CAPACITY = 8; + private const int READONLY_STACK_CAPACITY = 4; private const int DEFAULT_STORAGE_CAPACITY = 2048; @@ -49,14 +49,18 @@ internal struct ControlData internal struct FrameData { public ControlData HoveredControl; - public DynamicArray HoveredGroups; - public DynamicArray FloatingControls; + public ImDynamicArray HoveredGroups; + public ImDynamicArray FloatingControls; + public int VerticesCount; + public int IndicesCount; public FrameData(int hoveredGroupsCapacity, int floatingControlsCapacity) { HoveredControl = default; - HoveredGroups = new DynamicArray(hoveredGroupsCapacity); - FloatingControls = new DynamicArray(floatingControlsCapacity); + HoveredGroups = new ImDynamicArray(hoveredGroupsCapacity); + FloatingControls = new ImDynamicArray(floatingControlsCapacity); + IndicesCount = 0; + VerticesCount = 0; } public void Clear() @@ -79,36 +83,45 @@ public float UiScale uiScale = Mathf.Clamp(value, UI_SCALE_MIN, UI_SCALE_MAX); } } + + public bool IsReadOnly + { + get + { + return readOnlyStack.TryPeek(@default: false); + } + } - public readonly MeshBuffer MeshBuffer; - public readonly MeshRenderer MeshRenderer; - public readonly MeshDrawer MeshDrawer; - public readonly TextDrawer TextDrawer; + public readonly ImMeshBuffer MeshBuffer; + public readonly ImMeshRenderer MeshRenderer; + public readonly ImMeshDrawer MeshDrawer; + public readonly ImTextDrawer TextDrawer; public readonly ImCanvas Canvas; public readonly ImLayout Layout; public readonly ImStorage Storage; public readonly ImWindowManager WindowManager; - public readonly IInputBackend Input; - public readonly IRenderingBackend Renderer; - + public readonly IImInputBackend Input; + public readonly IImRenderingBackend Renderer; + internal FrameData nextFrameData; internal FrameData frameData; private float uiScale = 1.0f; - private DynamicArray idsStack; - private DynamicArray scopes; + private ImDynamicArray idsStack; + private ImDynamicArray scrollRectsStack; + private ImDynamicArray readOnlyStack; private uint activeControl; private ImControlFlag activeControlFlag; private bool disposed; - public ImGui(IRenderingBackend renderer, IInputBackend input) + public ImGui(IImRenderingBackend renderer, IImInputBackend input) { - MeshBuffer = new MeshBuffer(INIT_MESHES_COUNT, INIT_VERTICES_COUNT, INIT_INDICES_COUNT); - MeshDrawer = new MeshDrawer(MeshBuffer); - TextDrawer = new TextDrawer(MeshBuffer); + MeshBuffer = new ImMeshBuffer(INIT_MESHES_COUNT, INIT_VERTICES_COUNT, INIT_INDICES_COUNT); + MeshDrawer = new ImMeshDrawer(MeshBuffer); + TextDrawer = new ImTextDrawer(MeshBuffer); Canvas = new ImCanvas(MeshDrawer, TextDrawer); - MeshRenderer = new MeshRenderer(); + MeshRenderer = new ImMeshRenderer(); Layout = new ImLayout(); Storage = new ImStorage(DEFAULT_STORAGE_CAPACITY); WindowManager = new ImWindowManager(); @@ -117,8 +130,9 @@ public ImGui(IRenderingBackend renderer, IInputBackend input) frameData = new FrameData(HOVERED_GROUPS_CAPACITY, FLOATING_CONTROLS_CAPACITY); nextFrameData = new FrameData(HOVERED_GROUPS_CAPACITY, FLOATING_CONTROLS_CAPACITY); - idsStack = new DynamicArray(CONTROL_IDS_CAPACITY); - scopes = new DynamicArray(SCOPES_STACK_CAPACITY); + idsStack = new ImDynamicArray(CONTROL_IDS_CAPACITY); + scrollRectsStack = new ImDynamicArray(SCROLL_RECT_STACK_CAPACITY); + readOnlyStack = new ImDynamicArray(READONLY_STACK_CAPACITY); Input.SetRaycaster(Raycast); } @@ -138,7 +152,7 @@ public void BeginFrame() Canvas.SetScreen(scaledScreenSize); Canvas.Clear(); - Canvas.PushMeshSettings(Canvas.CreateDefaultMeshSettings()); + Canvas.PushSettings(Canvas.CreateDefaultSettings()); WindowManager.SetScreenSize(scaledScreenSize); @@ -153,24 +167,34 @@ public void EndFrame() Layout.Pop(); - Canvas.PopMeshSettings(); + Canvas.PopSettings(); Storage.CollectAndCompact(); } - public void BeginScope(uint id) + public void BeginReadOnly(bool isReadOnly) { - scopes.Push(id); + readOnlyStack.Push(isReadOnly); + + if (isReadOnly) + { + Canvas.PushContrast(READONLY_SCOPE_CONTRAST); + } + else + { + Canvas.PushDefaultContrast(); + } } - public uint GetScope() + public void EndReadOnly() { - return scopes.Peek(); + readOnlyStack.Pop(); + Canvas.PopContrast(); } - - public void EndScope(out uint id) + + internal ref ImDynamicArray GetScrollRectStack() { - id = scopes.Pop(); + return ref scrollRectsStack; } public uint PushId() @@ -179,6 +203,13 @@ public uint PushId() idsStack.Push(new ControlId(id)); return id; } + + public uint PushId(uint id) + { + var newId = GetControlId(id); + idsStack.Push(new ControlId(newId)); + return newId; + } public uint PushId(ReadOnlySpan name) { @@ -198,12 +229,18 @@ public uint GetNextControlId() return ImHash.Get(++parent.Gen, parent.Id); } - public uint GetControlId(in ReadOnlySpan name) + public uint GetControlId(ReadOnlySpan name) { ref var parent = ref idsStack.Peek(); return ImHash.Get(name, parent.Id); } + public uint GetControlId(uint id) + { + ref var parent = ref idsStack.Peek(); + return ImHash.Get(id, parent.Id); + } + public uint GetHoveredControl() { return frameData.HoveredControl.Id; @@ -266,7 +303,7 @@ public void RegisterRaycastTarget(ImRect rect) public void RegisterControl(uint controlId, ImRect rect) { - ref readonly var meshProperties = ref Canvas.GetActiveMeshSettingsRef(); + ref readonly var meshProperties = ref Canvas.GetActiveSettings(); if (meshProperties.ClipRect.Enabled && !meshProperties.ClipRect.Rect.Contains(Input.MousePosition)) { @@ -288,7 +325,7 @@ public void RegisterControl(uint controlId, ImRect rect) public void RegisterGroup(uint controlId, ImRect rect) { - ref readonly var meshProperties = ref Canvas.GetActiveMeshSettingsRef(); + ref readonly var meshProperties = ref Canvas.GetActiveSettings(); if (meshProperties.ClipRect.Enabled && !meshProperties.ClipRect.Rect.Contains(Input.MousePosition)) { @@ -298,13 +335,18 @@ public void RegisterGroup(uint controlId, ImRect rect) if (rect.Contains(Input.MousePosition)) { var currentOrder = meshProperties.Order; - + for (int i = nextFrameData.HoveredGroups.Count - 1; i >= 0; --i) { - if (nextFrameData.HoveredGroups.Array[i].Order < currentOrder) + var order = nextFrameData.HoveredGroups.Array[i].Order; + if (order < currentOrder) { nextFrameData.HoveredGroups.RemoveAtFast(i); } + else if (order > currentOrder) + { + return; + } } nextFrameData.HoveredGroups.Add(new ControlData() @@ -318,6 +360,9 @@ public void RegisterGroup(uint controlId, ImRect rect) public void Render() { + nextFrameData.VerticesCount = MeshDrawer.buffer.VerticesCount; + nextFrameData.IndicesCount = MeshDrawer.buffer.IndicesCount; + var renderCmd = Renderer.CreateCommandBuffer(); var screenSize = Renderer.GetScreenRect().size; Renderer.SetupRenderTarget(renderCmd); diff --git a/Runtime/Scripts/Core/ImLayout.cs b/Runtime/Scripts/Core/ImLayout.cs index 4612605..2c3653b 100644 --- a/Runtime/Scripts/Core/ImLayout.cs +++ b/Runtime/Scripts/Core/ImLayout.cs @@ -20,7 +20,7 @@ public struct ImLayoutFrame public Vector2 Size; public ImRect Bounds; public Vector2 Offset; - public float Ident; + public float Indent; public ImLayoutFlag Flags; public void Append(Vector2 size) @@ -33,7 +33,7 @@ public void Append(float width, float height) switch (Axis) { case ImAxis.Vertical: - Size.x = Mathf.Max(Size.x, width); + Size.x = Mathf.Max(Size.x, width + Indent); Size.y += height; break; case ImAxis.Horizontal: @@ -63,7 +63,7 @@ public class ImLayout { private const int FRAME_STACK_CAPACITY = 32; - private DynamicArray frames = new(FRAME_STACK_CAPACITY); + private ImDynamicArray frames = new(FRAME_STACK_CAPACITY); public void Push(ImAxis axis, float width = 0, float height = 0) { @@ -151,7 +151,7 @@ public ref readonly ImLayoutFrame GetFrame() public float GetAvailableWidth() { ref readonly var frame = ref GetFrame(); - return frame.Axis == ImAxis.Horizontal ? frame.Bounds.W - frame.Size.x : frame.Bounds.W - frame.Ident; + return frame.Axis == ImAxis.Horizontal ? frame.Bounds.W - frame.Size.x : frame.Bounds.W - frame.Indent; } public float GetAvailableHeight() @@ -169,11 +169,11 @@ public Vector2 GetAvailableSize() switch (frame.Axis) { case ImAxis.Vertical: - w -= frame.Ident; h -= frame.Size.y; + w -= frame.Indent; break; case ImAxis.Horizontal: - w -= frame.Size.x - frame.Ident; + w -= frame.Size.x; break; default: throw new NotImplementedException(); @@ -199,7 +199,7 @@ public ImRect GetBoundsRect() public ImRect GetContentRect() { ref readonly var frame = ref frames.Peek(); - var x = frame.Bounds.X + frame.Ident; + var x = frame.Bounds.X; var y = frame.Bounds.Y + frame.Bounds.H - frame.Size.y; var w = frame.Size.x; var h = frame.Size.y; @@ -245,10 +245,10 @@ public void AddSpace(float space) frame.Append(space); } - public void AddIdent(float space) + public void AddIndent(float space) { ref var frame = ref frames.Peek(); - frame.Ident += space; + frame.Indent += space; } public static Vector2 GetNextPosition(in ImLayoutFrame frame, float height) @@ -256,7 +256,7 @@ public static Vector2 GetNextPosition(in ImLayoutFrame frame, float height) var hm = frame.Axis == ImAxis.Horizontal ? 1 : 0; var vm = frame.Axis == ImAxis.Vertical ? 1 : 0; - var x = frame.Bounds.X + frame.Offset.x + (frame.Size.x * hm) + (frame.Ident * vm); + var x = frame.Bounds.X + frame.Offset.x + (frame.Size.x * hm) + (frame.Indent * vm); var y = frame.Bounds.Y + frame.Offset.y + frame.Bounds.H + - (frame.Size.y * vm) - height; return new Vector2(x, y); diff --git a/Runtime/Scripts/Core/ImRect.cs b/Runtime/Scripts/Core/ImRect.cs index 327da96..75db528 100644 --- a/Runtime/Scripts/Core/ImRect.cs +++ b/Runtime/Scripts/Core/ImRect.cs @@ -98,7 +98,7 @@ public ImRect Intersection(ImRect other) return new ImRect(x1, y1, x2 - x1, y2 - y1); } - public void Encapsulate(in ImRect other) + public void Encapsulate(ImRect other) { var l = Mathf.Min(other.X, X); var b = Mathf.Min(other.Y, Y); @@ -154,12 +154,12 @@ public override int GetHashCode() return HashCode.Combine(X, Y, W, H); } - public static explicit operator Rect(in ImRect rect) + public static explicit operator Rect(ImRect rect) { return new Rect(rect.X, rect.Y, rect.W, rect.H); } - public static explicit operator Vector4(in ImRect rect) + public static explicit operator Vector4(ImRect rect) { return new Vector4(rect.X, rect.Y, rect.W, rect.H); } @@ -205,7 +205,7 @@ public void Clamp(float size) BottomLeft = Mathf.Min(size, BottomLeft); } - public float GetMax() + public float RadiusForMask() { var max = TopLeft; max = TopRight > max ? TopRight : max; diff --git a/Runtime/Scripts/Core/ImShapes.cs b/Runtime/Scripts/Core/ImShapes.cs new file mode 100644 index 0000000..b897c8e --- /dev/null +++ b/Runtime/Scripts/Core/ImShapes.cs @@ -0,0 +1,135 @@ +using System; +using Imui.Utility; +using UnityEngine; +using UnityEngine.Profiling; + +namespace Imui.Core +{ + public static class ImShapes + { + private const int MIN_SEGMENTS = 2; + private const int MAX_SEGMENTS = 16; + + private const float PI = Mathf.PI; + private const float HALF_PI = PI / 2; + + private static ImResizeableBuffer TempBuffer = new (4096); + + public static int SegmentCountForRadius(float radius, float maxError = 2) + { + if (radius <= maxError) + { + return MIN_SEGMENTS; + } + + var segments = Mathf.Clamp(Mathf.CeilToInt(Mathf.PI / Mathf.Acos(1 - Mathf.Min(maxError, radius) / radius)), MIN_SEGMENTS, MAX_SEGMENTS); + return ((segments + 1) / 2) * 2; + } + + public static Span Ellipse(ImRect rect) + { + var segments = SegmentCountForRadius(rect.W / 2f) * 4; + var span = TempBuffer.AsSpan(segments); + + Ellipse(rect, span, segments); + + return span; + } + + public static void Ellipse(ImRect rect, Span buffer, int segments) + { + var step = (1f / segments) * PI * 2; + var rx = rect.W / 2.0f; + var ry = rect.H / 2.0f; + var cx = rect.X + rx; + var cy = rect.Y + ry; + + for (int i = 0; i < segments; ++i) + { + var a = step * (i + 1); + buffer[i].x = cx + Mathf.Cos(a) * rx; + buffer[i].y = cy + Mathf.Sin(a) * ry; + } + } + + public static Span Rect(ImRect rect, ImRectRadius radius) + { + radius.Clamp(Mathf.Min(rect.W, rect.H) / 2.0f); + + var segments = SegmentCountForRadius(radius.RadiusForMask()); + var span = TempBuffer.AsSpan((segments + 1) * 4); + + Rect(rect, radius, span, segments); + + return span; + } + + public static int Rect(ImRect rect, ImRectRadius radius, Span buffer, int segments) + { + Profiler.BeginSample("ImShapes.RectOutline"); + + var p = 0; + var step = (1f / segments) * HALF_PI; + + var cx = rect.X + rect.W - radius.BottomRight; + var cy = rect.Y + radius.BottomRight; + buffer[p].x = cx + Mathf.Cos(PI + HALF_PI) * radius.BottomRight; + buffer[p].y = cy + Mathf.Sin(PI + HALF_PI) * radius.BottomRight; + p++; + + for (int i = 0; i < segments; ++i) + { + var a = PI + HALF_PI + step * (i + 1); + buffer[p].x = cx + Mathf.Cos(a) * radius.BottomRight; + buffer[p].y = cy + Mathf.Sin(a) * radius.BottomRight; + p++; + } + + cx = rect.X + rect.W - radius.TopRight; + cy = rect.Y + rect.H - radius.TopRight; + buffer[p].x = cx + Mathf.Cos(0) * radius.TopRight; + buffer[p].y = cy + Mathf.Sin(0) * radius.TopRight; + p++; + + for (int i = 0; i < segments; ++i) + { + var a = 0 + step * (i + 1); + buffer[p].x = cx + Mathf.Cos(a) * radius.TopRight; + buffer[p].y = cy + Mathf.Sin(a) * radius.TopRight; + p++; + } + + cx = rect.X + radius.TopLeft; + cy = rect.Y + rect.H - radius.TopLeft; + buffer[p].x = cx + Mathf.Cos(HALF_PI) * radius.TopLeft; + buffer[p].y = cy + Mathf.Sin(HALF_PI) * radius.TopLeft; + p++; + + for (int i = 0; i < segments; ++i) + { + var a = HALF_PI + step * (i + 1); + buffer[p].x = cx + Mathf.Cos(a) * radius.TopLeft; + buffer[p].y = cy + Mathf.Sin(a) * radius.TopLeft; + p++; + } + + cx = rect.X + radius.BottomLeft; + cy = rect.Y + radius.BottomLeft; + buffer[p].x = cx + Mathf.Cos(PI) * radius.BottomLeft; + buffer[p].y = cy + Mathf.Sin(PI) * radius.BottomLeft; + p++; + + for (int i = 0; i < segments; ++i) + { + var a = PI + step * (i + 1); + buffer[p].x = cx + Mathf.Cos(a) * radius.BottomLeft; + buffer[p].y = cy + Mathf.Sin(a) * radius.BottomLeft; + p++; + } + + Profiler.EndSample(); + + return p; + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Core/ImShapes.cs.meta b/Runtime/Scripts/Core/ImShapes.cs.meta new file mode 100644 index 0000000..cf26749 --- /dev/null +++ b/Runtime/Scripts/Core/ImShapes.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3064f7be4d6846288255e11cb2ed00d8 +timeCreated: 1718231761 \ No newline at end of file diff --git a/Runtime/Scripts/Core/ImStorage.cs b/Runtime/Scripts/Core/ImStorage.cs index e2fe840..29dfdb9 100644 --- a/Runtime/Scripts/Core/ImStorage.cs +++ b/Runtime/Scripts/Core/ImStorage.cs @@ -39,14 +39,30 @@ public ImStorage(int capacity) } public ref T Get(uint id, in T defaultValue = default) where T : unmanaged + { + return ref *GetRef(id, in defaultValue); + } + + public T* GetRef(uint id, in T defaultValue = default) where T : unmanaged { if (!TryGet(out T* value, out Metadata* metadata, id)) { - return ref *AddValue(id, defaultValue); + return AddValue(id, defaultValue); } metadata->Flag |= Flag.Used; - return ref *value; + return value; + } + + public bool TryGetRef(uint id, out T* value) where T : unmanaged + { + var result = TryGet(out value, out var metadata, id); + if (result) + { + metadata->Flag |= Flag.Used; + } + + return result; } public void CollectAndCompact() diff --git a/Runtime/Scripts/Core/ImTextSettings.cs b/Runtime/Scripts/Core/ImTextSettings.cs index 5c062e3..765a640 100644 --- a/Runtime/Scripts/Core/ImTextSettings.cs +++ b/Runtime/Scripts/Core/ImTextSettings.cs @@ -21,18 +21,21 @@ public struct ImTextSettings { public float Size; public ImTextAlignment Align; + public bool Wrap; - public ImTextSettings(float size, float alignX = 0.0f, float alignY = 0.0f) + public ImTextSettings(float size, float alignX = 0.0f, float alignY = 0.0f, bool wrap = true) { Size = size; Align.X = alignX; Align.Y = alignY; + Wrap = wrap; } - public ImTextSettings(float size, ImTextAlignment alignment) + public ImTextSettings(float size, ImTextAlignment alignment, bool wrap = true) { Size = size; Align = alignment; + Wrap = wrap; } } } \ No newline at end of file diff --git a/Runtime/Scripts/Core/ImWindowManager.cs b/Runtime/Scripts/Core/ImWindowManager.cs index bce7806..fac7be8 100644 --- a/Runtime/Scripts/Core/ImWindowManager.cs +++ b/Runtime/Scripts/Core/ImWindowManager.cs @@ -21,8 +21,8 @@ public class ImWindowManager public const int DEFAULT_WIDTH = 500; public const int DEFAULT_HEIGHT = 500; - private DynamicArray drawingStack = new(DRAWING_STACK_CAPACITY); - private DynamicArray windows = new(WINDOWS_CAPACITY); + private ImDynamicArray drawingStack = new(DRAWING_STACK_CAPACITY); + private ImDynamicArray windows = new(WINDOWS_CAPACITY); private Vector2 screenSize; public ref ImWindowState BeginWindow(uint id, string title, float width = DEFAULT_WIDTH, float height = DEFAULT_HEIGHT, ImWindowFlag flags = ImWindowFlag.None) @@ -42,7 +42,7 @@ public ref ImWindowState BeginWindow(uint id, string title, float width = DEFAUL Id = id, Order = windows.Count, Title = title, - Rect = GetWindowRect(width, height), + Rect = GetNewWindowRect(width, height), Flags = flags }); @@ -58,6 +58,28 @@ public bool IsDrawingWindow() { return drawingStack.Count > 0; } + + public ImRect GetCurrentWindowRect() + { + if (!IsDrawingWindow()) + { + return default; + } + + ref var state = ref GetWindowState(drawingStack.Peek()); + return state.Rect; + } + + public void SetCurrentWindowRect(ImRect rect) + { + if (!IsDrawingWindow()) + { + return; + } + + ref var state = ref GetWindowState(drawingStack.Peek()); + state.Rect = rect; + } public bool Raycast(float x, float y) { @@ -118,7 +140,7 @@ private int TryFindWindow(uint id) return -1; } - private ImRect GetWindowRect(float width, float height) + private ImRect GetNewWindowRect(float width, float height) { var size = new Vector2(width, height); var position = new Vector2((screenSize.x - width) / 2f, (screenSize.y - height) / 2f); diff --git a/Runtime/Scripts/Debugging.meta b/Runtime/Scripts/Debugging.meta deleted file mode 100644 index 73d6590..0000000 --- a/Runtime/Scripts/Debugging.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f64d7d0e58ae4a02becd7922a30a55bc -timeCreated: 1709407726 \ No newline at end of file diff --git a/Runtime/Scripts/IO/IInputBackend.cs b/Runtime/Scripts/IO/IImInputBackend.cs similarity index 73% rename from Runtime/Scripts/IO/IInputBackend.cs rename to Runtime/Scripts/IO/IImInputBackend.cs index 37c14d3..50deef0 100644 --- a/Runtime/Scripts/IO/IInputBackend.cs +++ b/Runtime/Scripts/IO/IImInputBackend.cs @@ -1,12 +1,13 @@ using System; using Imui.IO.Events; +using Imui.IO.Utility; using UnityEngine; namespace Imui.IO { - public delegate bool InputRaycaster(float x, float y); + public delegate bool ImInputRaycaster(float x, float y); - public interface IInputBackend + public interface IImInputBackend { string Clipboard { @@ -31,9 +32,9 @@ string Clipboard void UseMouseEvent(); void UseTextEvent(); - void SetRaycaster(InputRaycaster raycaster); + void SetRaycaster(ImInputRaycaster raycaster); void SetScale(float scale); void Pull(); - void RequestTouchKeyboard(ReadOnlySpan text); + void RequestTouchKeyboard(uint owner, ReadOnlySpan text, ImTouchKeyboardSettings settings); } } \ No newline at end of file diff --git a/Runtime/Scripts/IO/IInputBackend.cs.meta b/Runtime/Scripts/IO/IImInputBackend.cs.meta similarity index 100% rename from Runtime/Scripts/IO/IInputBackend.cs.meta rename to Runtime/Scripts/IO/IImInputBackend.cs.meta diff --git a/Runtime/Scripts/IO/IRenderingBackend.cs b/Runtime/Scripts/IO/IImRenderingBackend.cs similarity index 88% rename from Runtime/Scripts/IO/IRenderingBackend.cs rename to Runtime/Scripts/IO/IImRenderingBackend.cs index 9e3d508..1be51cb 100644 --- a/Runtime/Scripts/IO/IRenderingBackend.cs +++ b/Runtime/Scripts/IO/IImRenderingBackend.cs @@ -3,7 +3,7 @@ namespace Imui.IO { - public interface IRenderingBackend + public interface IImRenderingBackend { CommandBuffer CreateCommandBuffer(); void ReleaseCommandBuffer(CommandBuffer cmd); diff --git a/Runtime/Scripts/IO/IRenderingBackend.cs.meta b/Runtime/Scripts/IO/IImRenderingBackend.cs.meta similarity index 100% rename from Runtime/Scripts/IO/IRenderingBackend.cs.meta rename to Runtime/Scripts/IO/IImRenderingBackend.cs.meta diff --git a/Runtime/Scripts/IO/UGUI/ImuiGraphic.cs b/Runtime/Scripts/IO/UGUI/ImCanvasBackend.cs similarity index 79% rename from Runtime/Scripts/IO/UGUI/ImuiGraphic.cs rename to Runtime/Scripts/IO/UGUI/ImCanvasBackend.cs index a295fab..7bf968a 100644 --- a/Runtime/Scripts/IO/UGUI/ImuiGraphic.cs +++ b/Runtime/Scripts/IO/UGUI/ImCanvasBackend.cs @@ -10,9 +10,9 @@ namespace Imui.IO.UGUI { [RequireComponent(typeof(CanvasRenderer))] - public class ImuiGraphic : Graphic, - IRenderingBackend, - IInputBackend, + public class ImCanvasBackend : Graphic, + IImRenderingBackend, + IImInputBackend, IPointerDownHandler, IPointerUpHandler, IDragHandler, @@ -33,17 +33,17 @@ public class ImuiGraphic : Graphic, public override Texture mainTexture => textureRenderer?.Texture == null ? ClearTexture : textureRenderer.Texture; - private InputRaycaster raycaster; - private TextureRenderer textureRenderer; - private DynamicArray commandBufferPool; + private ImInputRaycaster raycaster; + private ImTextureRenderer textureRenderer; + private ImDynamicArray commandBufferPool; private float scale; - private CircularBuffer mouseEventsQueue; - private CircularBuffer nextKeyboardEvents; - private CircularBuffer keyboardEvents; + private ImCircularBuffer mouseEventsQueue; + private ImCircularBuffer nextKeyboardEvents; + private ImCircularBuffer keyboardEvents; private Vector2 mousePosition; private ImMouseEvent mouseEvent; private ImTextEvent textEvent; - private TouchKeyboardHandler touchKeyboardHandler; + private ImTouchKeyboard touchKeyboardHandler; private bool elementHovered; protected override void Awake() @@ -56,12 +56,12 @@ protected override void Awake() } useGUILayout = false; - mouseEventsQueue = new CircularBuffer(MOUSE_EVENTS_QUEUE_SIZE); - keyboardEvents = new CircularBuffer(KEYBOARD_EVENTS_QUEUE_SIZE); - nextKeyboardEvents = new CircularBuffer(KEYBOARD_EVENTS_QUEUE_SIZE); - touchKeyboardHandler = new TouchKeyboardHandler(); - commandBufferPool = new DynamicArray(COMMAND_BUFFER_POOL_INITIAL_SIZE); - textureRenderer = new TextureRenderer(); + mouseEventsQueue = new ImCircularBuffer(MOUSE_EVENTS_QUEUE_SIZE); + keyboardEvents = new ImCircularBuffer(KEYBOARD_EVENTS_QUEUE_SIZE); + nextKeyboardEvents = new ImCircularBuffer(KEYBOARD_EVENTS_QUEUE_SIZE); + touchKeyboardHandler = new ImTouchKeyboard(); + commandBufferPool = new ImDynamicArray(COMMAND_BUFFER_POOL_INITIAL_SIZE); + textureRenderer = new ImTextureRenderer(); } protected override void OnDestroy() @@ -93,18 +93,7 @@ protected override void OnEnable() } } - protected override void OnDisable() - { - base.OnDisable(); - - if (touchKeyboardHandler != null) - { - touchKeyboardHandler.Dispose(); - touchKeyboardHandler = null; - } - } - - public void SetRaycaster(InputRaycaster raycaster) + public void SetRaycaster(ImInputRaycaster raycaster) { this.raycaster = raycaster; SetRaycastDirty(); @@ -182,9 +171,9 @@ public Vector2 GetMousePosition() return ((Vector2)Input.mousePosition - GetScreenRect().position) / scale; } - public void RequestTouchKeyboard(ReadOnlySpan text) + public void RequestTouchKeyboard(uint owner, ReadOnlySpan text, ImTouchKeyboardSettings settings) { - touchKeyboardHandler.RequestTouchKeyboard(text); + touchKeyboardHandler.RequestTouchKeyboard(owner, text, settings); } private void OnGUI() @@ -195,7 +184,7 @@ private void OnGUI() return; } - KeyboardEventsUtility.TryParse(evt, ref nextKeyboardEvents); + ImKeyboardEventsUtility.TryParse(evt, ref nextKeyboardEvents); } @@ -251,7 +240,13 @@ public Rect GetScreenRect() private bool IsTouchSupported() { - return PlatformUtility.IsEditorSimulator() || Input.touchSupported; +#if UNITY_EDITOR + var isRunningInDeviceSimulator = UnityEngine.Device.SystemInfo.deviceType != DeviceType.Desktop; +#else + var isRunningInDeviceSimulator = false; +#endif + + return isRunningInDeviceSimulator || Input.touchSupported; } private bool IsTouchBegan() @@ -270,24 +265,28 @@ private bool IsTouchBegan() return false; } - CommandBuffer IRenderingBackend.CreateCommandBuffer() + CommandBuffer IImRenderingBackend.CreateCommandBuffer() { if (commandBufferPool.Count == 0) { - var cmd = new CommandBuffer(); + var cmd = new CommandBuffer() + { + name = "Imui Canvas Backend" + }; + commandBufferPool.Add(cmd); } return commandBufferPool.Pop(); } - void IRenderingBackend.ReleaseCommandBuffer(CommandBuffer cmd) + void IImRenderingBackend.ReleaseCommandBuffer(CommandBuffer cmd) { cmd.Clear(); commandBufferPool.Add(cmd); } - void IRenderingBackend.SetupRenderTarget(CommandBuffer cmd) + void IImRenderingBackend.SetupRenderTarget(CommandBuffer cmd) { var rect = GetScreenRect(); var size = new Vector2Int((int)rect.width, (int)rect.height); @@ -300,7 +299,7 @@ void IRenderingBackend.SetupRenderTarget(CommandBuffer cmd) } } - void IRenderingBackend.Execute(CommandBuffer cmd) + void IImRenderingBackend.Execute(CommandBuffer cmd) { Graphics.ExecuteCommandBuffer(cmd); } diff --git a/Runtime/Scripts/IO/UGUI/ImuiGraphic.cs.meta b/Runtime/Scripts/IO/UGUI/ImCanvasBackend.cs.meta similarity index 100% rename from Runtime/Scripts/IO/UGUI/ImuiGraphic.cs.meta rename to Runtime/Scripts/IO/UGUI/ImCanvasBackend.cs.meta diff --git a/Runtime/Scripts/IO/Utility/KeyboardEventsUtility.cs b/Runtime/Scripts/IO/Utility/ImKeyboardEventsUtility.cs similarity index 93% rename from Runtime/Scripts/IO/Utility/KeyboardEventsUtility.cs rename to Runtime/Scripts/IO/Utility/ImKeyboardEventsUtility.cs index f50e2bf..3934a1d 100644 --- a/Runtime/Scripts/IO/Utility/KeyboardEventsUtility.cs +++ b/Runtime/Scripts/IO/Utility/ImKeyboardEventsUtility.cs @@ -4,9 +4,9 @@ namespace Imui.IO.Utility { - public static class KeyboardEventsUtility + public static class ImKeyboardEventsUtility { - public static bool TryParse(Event evt, ref CircularBuffer eventsQueue) + public static bool TryParse(Event evt, ref ImCircularBuffer eventsQueue) { if (evt == null) { diff --git a/Runtime/Scripts/IO/Utility/KeyboardEventsUtility.cs.meta b/Runtime/Scripts/IO/Utility/ImKeyboardEventsUtility.cs.meta similarity index 100% rename from Runtime/Scripts/IO/Utility/KeyboardEventsUtility.cs.meta rename to Runtime/Scripts/IO/Utility/ImKeyboardEventsUtility.cs.meta diff --git a/Runtime/Scripts/IO/Utility/TextureRenderer.cs b/Runtime/Scripts/IO/Utility/ImTextureRenderer.cs similarity index 88% rename from Runtime/Scripts/IO/Utility/TextureRenderer.cs rename to Runtime/Scripts/IO/Utility/ImTextureRenderer.cs index 5e73ba1..f7e92e6 100644 --- a/Runtime/Scripts/IO/Utility/TextureRenderer.cs +++ b/Runtime/Scripts/IO/Utility/ImTextureRenderer.cs @@ -5,7 +5,7 @@ namespace Imui.IO.Utility { - public class TextureRenderer : IDisposable + public class ImTextureRenderer : IDisposable { private const float RES_SCALE_MIN = 0.2f; private const float RES_SCALE_MAX = 4.0f; @@ -29,7 +29,7 @@ private bool SetupTexture(Vector2Int size, float scale) { if (disposed) { - throw new ObjectDisposedException(nameof(TextureRenderer)); + throw new ObjectDisposedException(nameof(ImTextureRenderer)); } scale = Mathf.Clamp(scale, RES_SCALE_MIN, RES_SCALE_MAX); @@ -50,6 +50,8 @@ private bool SetupTexture(Vector2Int size, float scale) ReleaseTexture(); Texture = new RenderTexture(w, h, GraphicsFormat.R8G8B8A8_UNorm, GraphicsFormat.None); + Texture.name = "ImuiRenderBuffer"; + return Texture.Create(); } @@ -66,7 +68,7 @@ private void AssertDisposed() { if (disposed) { - throw new ObjectDisposedException(nameof(TextureRenderer)); + throw new ObjectDisposedException(nameof(ImTextureRenderer)); } } diff --git a/Runtime/Scripts/IO/Utility/TextureRenderer.cs.meta b/Runtime/Scripts/IO/Utility/ImTextureRenderer.cs.meta similarity index 100% rename from Runtime/Scripts/IO/Utility/TextureRenderer.cs.meta rename to Runtime/Scripts/IO/Utility/ImTextureRenderer.cs.meta diff --git a/Runtime/Scripts/IO/Utility/TouchKeyboardHandler.cs b/Runtime/Scripts/IO/Utility/ImTouchKeyboard.cs similarity index 54% rename from Runtime/Scripts/IO/Utility/TouchKeyboardHandler.cs rename to Runtime/Scripts/IO/Utility/ImTouchKeyboard.cs index e4a7b9e..9e35167 100644 --- a/Runtime/Scripts/IO/Utility/TouchKeyboardHandler.cs +++ b/Runtime/Scripts/IO/Utility/ImTouchKeyboard.cs @@ -4,33 +4,56 @@ namespace Imui.IO.Utility { - public class TouchKeyboardHandler : IDisposable + public enum ImTouchKeyboardType + { + Default, + Numeric + } + + public struct ImTouchKeyboardSettings + { + public bool Muiltiline; + public ImTouchKeyboardType Type; + public int CharactersLimit; + } + + public class ImTouchKeyboard : IDisposable { private const int TOUCH_KEYBOARD_CLOSE_FRAMES_THRESHOLD = 3; public TouchScreenKeyboard TouchKeyboard; - + + private uint touchKeyboardOwner; private int touchKeyboardRequestFrame; - private bool touchKeyboardUnsupported; - public void RequestTouchKeyboard(ReadOnlySpan text) + public void RequestTouchKeyboard(uint owner, ReadOnlySpan text, ImTouchKeyboardSettings settings) { - if (!TouchScreenKeyboard.isSupported || touchKeyboardUnsupported) + #if UNITY_WEBGL + // TODO (artem-s): fix touch keyboard handling for webgl + return; + #endif + + if (!TouchScreenKeyboard.isSupported) { return; } + if (owner != touchKeyboardOwner && TouchKeyboard != null) + { + TouchKeyboard.active = false; + TouchKeyboard = null; + touchKeyboardOwner = 0; + } + if (TouchKeyboard == null) { - TouchKeyboard = TouchScreenKeyboard.Open(new string(text), TouchScreenKeyboardType.Default); - - if (TouchKeyboard.status == TouchScreenKeyboard.Status.Done) - { - touchKeyboardUnsupported = true; - TouchKeyboard.active = false; - TouchKeyboard = null; - return; - } + touchKeyboardOwner = owner; + TouchKeyboard = TouchScreenKeyboard.Open( + new string(text), + GetType(settings.Type), + false, + settings.Muiltiline); + TouchKeyboard.characterLimit = settings.CharactersLimit; } if (!TouchKeyboard.active) @@ -41,6 +64,17 @@ public void RequestTouchKeyboard(ReadOnlySpan text) touchKeyboardRequestFrame = Time.frameCount; } + private TouchScreenKeyboardType GetType(ImTouchKeyboardType type) + { + switch (type) + { + case ImTouchKeyboardType.Numeric: + return TouchScreenKeyboardType.DecimalPad; + default: + return TouchScreenKeyboardType.Default; + } + } + public void HandleTouchKeyboard(out ImTextEvent textEvent) { textEvent = default; diff --git a/Runtime/Scripts/IO/Utility/TouchKeyboardHandler.cs.meta b/Runtime/Scripts/IO/Utility/ImTouchKeyboard.cs.meta similarity index 100% rename from Runtime/Scripts/IO/Utility/TouchKeyboardHandler.cs.meta rename to Runtime/Scripts/IO/Utility/ImTouchKeyboard.cs.meta diff --git a/Runtime/Scripts/Rendering/MeshBuffer.cs b/Runtime/Scripts/Rendering/ImMeshBuffer.cs similarity index 82% rename from Runtime/Scripts/Rendering/MeshBuffer.cs rename to Runtime/Scripts/Rendering/ImMeshBuffer.cs index e47b808..947d96c 100644 --- a/Runtime/Scripts/Rendering/MeshBuffer.cs +++ b/Runtime/Scripts/Rendering/ImMeshBuffer.cs @@ -4,20 +4,20 @@ namespace Imui.Rendering { - public class MeshBuffer + public class ImMeshBuffer { public int VerticesCount; public int IndicesCount; - public Vertex[] Vertices; + public ImVertex[] Vertices; public int[] Indices; - public MeshData[] Meshes; + public ImMeshData[] Meshes; public int MeshesCount; - public MeshBuffer(int meshesCapacity, int verticesCapacity, int indicesCapacity) + public ImMeshBuffer(int meshesCapacity, int verticesCapacity, int indicesCapacity) { - Vertices = new Vertex[verticesCapacity]; + Vertices = new ImVertex[verticesCapacity]; Indices = new int[indicesCapacity]; - Meshes = new MeshData[meshesCapacity]; + Meshes = new ImMeshData[meshesCapacity]; Clear(); } @@ -38,7 +38,7 @@ public void Trim() public void Sort() { - var meshes = new Span(Meshes, 0, MeshesCount); + var meshes = new Span(Meshes, 0, MeshesCount); var i = 1; while (i < meshes.Length) { @@ -113,16 +113,16 @@ public void EnsureIndicesCapacity(int size) public void AddIndices(int count) { #if IMUI_VALIDATION - ImuiAssert.False(MeshesCount == 0, "Empty meshes array"); + ImAssert.False(MeshesCount == 0, "Empty meshes array"); var from = Meshes[MeshesCount - 1].IndicesOffset + Meshes[MeshesCount - 1].IndicesCount; var to = from + count; - ImuiAssert.False(to > Indices.Length, "Indices array is too small"); + ImAssert.False(to > Indices.Length, "Indices array is too small"); for (int i = from; i < to; ++i) { - ImuiAssert.True(Indices[i] >= 0 && Indices[i] < Vertices.Length, $"Invalid index {Indices[i]} at {i}"); + ImAssert.True(Indices[i] >= 0 && Indices[i] < Vertices.Length, $"Invalid index {Indices[i]} at {i}"); } #endif @@ -134,12 +134,12 @@ public void AddIndices(int count) public void AddVertices(int count) { #if IMUI_VALIDATION - ImuiAssert.False(MeshesCount == 0, "Empty meshes array"); + ImAssert.False(MeshesCount == 0, "Empty meshes array"); var from = Meshes[MeshesCount - 1].VerticesOffset + Meshes[MeshesCount - 1].VerticesCount; var to = from + count; - ImuiAssert.False(to > Vertices.Length, "Vertices array is too small"); + ImAssert.False(to > Vertices.Length, "Vertices array is too small"); for (int i = from; i < to; ++i) { @@ -148,7 +148,7 @@ public void AddVertices(int count) float.IsNaN(v.Position.y) || float.IsInfinity(v.Position.y) || float.IsNaN(v.Position.z) || float.IsInfinity(v.Position.z)) { - ImuiAssert.True(false, "Invalid vertex position, some of the components is either NaN of Infinity"); + ImAssert.True(false, "Invalid vertex position, some of the components is either NaN of Infinity"); } } #endif diff --git a/Runtime/Scripts/Rendering/MeshBuffer.cs.meta b/Runtime/Scripts/Rendering/ImMeshBuffer.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/MeshBuffer.cs.meta rename to Runtime/Scripts/Rendering/ImMeshBuffer.cs.meta diff --git a/Runtime/Scripts/Rendering/MeshData.cs b/Runtime/Scripts/Rendering/ImMeshData.cs similarity index 81% rename from Runtime/Scripts/Rendering/MeshData.cs rename to Runtime/Scripts/Rendering/ImMeshData.cs index 0a6f971..43aff39 100644 --- a/Runtime/Scripts/Rendering/MeshData.cs +++ b/Runtime/Scripts/Rendering/ImMeshData.cs @@ -2,20 +2,20 @@ namespace Imui.Rendering { - public struct MeshClipRect + public struct ImMeshClipRect { public bool Enabled; public Rect Rect; } - public struct MeshMaskRect + public struct ImMeshMaskRect { public bool Enabled; public Rect Rect; public float Radius; } - public struct MeshData + public struct ImMeshData { public Texture MainTex; public Texture FontTex; @@ -26,8 +26,9 @@ public struct MeshData public int IndicesCount; public MeshTopology Topology; public int Order; - public MeshClipRect ClipRect; - public MeshMaskRect MaskRect; + public ImMeshClipRect ClipRect; + public ImMeshMaskRect MaskRect; + public float Contrast; public void ClearOptions() { @@ -38,6 +39,7 @@ public void ClearOptions() Order = 0; ClipRect = default; MaskRect = default; + Contrast = default; } public void Clear() diff --git a/Runtime/Scripts/Rendering/MeshData.cs.meta b/Runtime/Scripts/Rendering/ImMeshData.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/MeshData.cs.meta rename to Runtime/Scripts/Rendering/ImMeshData.cs.meta diff --git a/Runtime/Scripts/Rendering/MeshDrawer.cs b/Runtime/Scripts/Rendering/ImMeshDrawer.cs similarity index 93% rename from Runtime/Scripts/Rendering/MeshDrawer.cs rename to Runtime/Scripts/Rendering/ImMeshDrawer.cs index 6f33702..883b52c 100644 --- a/Runtime/Scripts/Rendering/MeshDrawer.cs +++ b/Runtime/Scripts/Rendering/ImMeshDrawer.cs @@ -5,39 +5,22 @@ namespace Imui.Rendering { // TODO (artem-s); try burst for optimizations - public class MeshDrawer + public class ImMeshDrawer { public const float MAIN_TEX_ID = 0.0f; public const float FONT_TEX_ID = 1.0f; private const float PI = Mathf.PI; private const float HALF_PI = PI / 2; - - private static readonly Vector2 zero = Vector2.zero; - - public static int CalculateSegmentsCount(float radius, float maxError = 2) - { - const int MIN_SEGMENTS = 2; - const int MAX_SEGMENTS = 16; - - if (radius <= maxError) - { - return MIN_SEGMENTS; - } - - var segments = Mathf.Clamp(Mathf.CeilToInt(Mathf.PI / Mathf.Acos(1 - Mathf.Min(maxError, radius) / radius)), MIN_SEGMENTS, MAX_SEGMENTS); - return ((segments + 1) / 2) * 2; - } - // ReSharper disable once InconsistentNaming public float Depth; public float Atlas; public Color32 Color; public Vector4 ScaleOffset; - private readonly MeshBuffer buffer; + internal readonly ImMeshBuffer buffer; - public MeshDrawer(MeshBuffer buffer) + public ImMeshDrawer(ImMeshBuffer buffer) { this.buffer = buffer; } @@ -52,12 +35,13 @@ public void NextMesh() buffer.NextMesh(); } - public ref MeshData GetMesh() + public ref ImMeshData GetMesh() { return ref buffer.Meshes[buffer.MeshesCount - 1]; } - public void AddLine(in ReadOnlySpan path, bool closed, float thickness, float outerScale, float innerScale) + // TODO: closed property is not used for some reason... + public void AddLine(ReadOnlySpan path, bool closed, float thickness, float outerScale, float innerScale) { Profiler.BeginSample("MeshDrawer.AddLine"); @@ -101,7 +85,7 @@ public void AddLine(in ReadOnlySpan path, bool closed, float thickness, v1.Position.y = a.y + normalY * innerThickness; v1.Position.z = Depth; v1.Color = Color; - v1.UV = zero; + v1.UV = default; v1.Atlas = Atlas; ref var v2 = ref buffer.Vertices[vc + 2]; @@ -140,7 +124,7 @@ public void AddLine(in ReadOnlySpan path, bool closed, float thickness, } // TODO (artem-s): add proper texturing - public void AddLineMiter(in ReadOnlySpan path, bool closed, float thickness, float outerScale, float innerScale) + public void AddLineMiter(ReadOnlySpan path, bool closed, float thickness, float outerScale, float innerScale) { Profiler.BeginSample("MeshDrawer.AddLineMiter"); @@ -233,7 +217,7 @@ public void AddLineMiter(in ReadOnlySpan path, bool closed, float thick v1.Position.y = path[0].y + prevNormY * innerThickness; v1.Position.z = Depth; v1.Color = Color; - v1.UV = zero; + v1.UV = default; v1.Atlas = Atlas; for (int i = 0; i < pointsCount; ++i) @@ -338,8 +322,8 @@ public void AddLineMiter(in ReadOnlySpan path, bool closed, float thick // TODO (artem-s): calculate proper UV values public void AddTriangleFan(Vector2 center, float from, float to, float radius, int segments) { - ImuiAssert.True(segments > 0, "segments > 0"); - ImuiAssert.True(to > from, "to > from"); + ImAssert.True(segments > 0, "segments > 0"); + ImAssert.True(to > from, "to > from"); Profiler.BeginSample("MeshDrawer.AddTriangleFan"); @@ -543,9 +527,9 @@ public void AddQuad(float x, float y, float w, float h) Profiler.EndSample(); } - public void AddFilledConvexMesh(in ReadOnlySpan points) + public void AddFilledConvexMesh(ReadOnlySpan points) { - ImuiAssert.True(points.Length > 2, "points.Length > 2"); + ImAssert.True(points.Length > 2, "points.Length > 2"); Profiler.BeginSample("MeshDrawer.AddFilledConvexMesh"); diff --git a/Runtime/Scripts/Rendering/MeshDrawer.cs.meta b/Runtime/Scripts/Rendering/ImMeshDrawer.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/MeshDrawer.cs.meta rename to Runtime/Scripts/Rendering/ImMeshDrawer.cs.meta diff --git a/Runtime/Scripts/Rendering/MeshRenderer.cs b/Runtime/Scripts/Rendering/ImMeshRenderer.cs similarity index 92% rename from Runtime/Scripts/Rendering/MeshRenderer.cs rename to Runtime/Scripts/Rendering/ImMeshRenderer.cs index c6ef60e..0fb9290 100644 --- a/Runtime/Scripts/Rendering/MeshRenderer.cs +++ b/Runtime/Scripts/Rendering/ImMeshRenderer.cs @@ -4,7 +4,7 @@ namespace Imui.Rendering { - public class MeshRenderer : IDisposable + public class ImMeshRenderer : IDisposable { private const MeshUpdateFlags MESH_UPDATE_FLAGS = MeshUpdateFlags.DontNotifyMeshUsers | @@ -18,6 +18,7 @@ public class MeshRenderer : IDisposable private static readonly int MaskEnabledId = Shader.PropertyToID("_MaskEnable"); private static readonly int MaskRectId = Shader.PropertyToID("_MaskRect"); private static readonly int MaskCornerRadiusId = Shader.PropertyToID("_MaskCornerRadius"); + private static readonly int ContrastId = Shader.PropertyToID("_Contrast"); #if IMUI_DEBUG public bool Wireframe; @@ -28,7 +29,7 @@ public class MeshRenderer : IDisposable private Mesh mesh; private bool disposed; - public MeshRenderer() + public ImMeshRenderer() { properties = new MaterialPropertyBlock(); @@ -36,14 +37,14 @@ public MeshRenderer() mesh.MarkDynamic(); } - public void Render(CommandBuffer cmd, MeshBuffer buffer, Vector2 size, float scale) + public void Render(CommandBuffer cmd, ImMeshBuffer buffer, Vector2 size, float scale) { mesh.Clear(true); buffer.Trim(); mesh.SetIndexBufferParams(buffer.IndicesCount, IndexFormat.UInt32); - mesh.SetVertexBufferParams(buffer.VerticesCount, Vertex.VertexAttributes); + mesh.SetVertexBufferParams(buffer.VerticesCount, ImVertex.VertexAttributes); mesh.SetVertexBufferData(buffer.Vertices, 0, 0, buffer.VerticesCount, 0, MESH_UPDATE_FLAGS); mesh.SetIndexBufferData(buffer.Indices, 0, 0, buffer.IndicesCount, MESH_UPDATE_FLAGS); @@ -92,6 +93,7 @@ public void Render(CommandBuffer cmd, MeshBuffer buffer, Vector2 size, float sca properties.SetTexture(MainTexId, meshData.MainTex); properties.SetTexture(FontTexId, meshData.FontTex); + properties.SetFloat(ContrastId, meshData.Contrast); if (meshData.MaskRect.Enabled) { diff --git a/Runtime/Scripts/Rendering/MeshRenderer.cs.meta b/Runtime/Scripts/Rendering/ImMeshRenderer.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/MeshRenderer.cs.meta rename to Runtime/Scripts/Rendering/ImMeshRenderer.cs.meta diff --git a/Runtime/Scripts/Rendering/TextDrawer.cs b/Runtime/Scripts/Rendering/ImTextDrawer.cs similarity index 80% rename from Runtime/Scripts/Rendering/TextDrawer.cs rename to Runtime/Scripts/Rendering/ImTextDrawer.cs index 0bb2a1b..303402c 100644 --- a/Runtime/Scripts/Rendering/TextDrawer.cs +++ b/Runtime/Scripts/Rendering/ImTextDrawer.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Profiling; @@ -8,7 +9,28 @@ namespace Imui.Rendering { - public class TextDrawer : IDisposable + public struct ImTextLine + { + public int Start; + public int Count; + public float OffsetX; + public float Width; + } + + public struct ImTextLayout + { + public float Size; + public float Scale; + public float OffsetX; + public float OffsetY; + public float Width; + public float Height; + public ImTextLine[] Lines; + public int LinesCount; + public float LineHeight; + } + + public class ImTextDrawer : IDisposable { private const string ASCII = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; private const char NEW_LINE = '\n'; @@ -17,31 +39,10 @@ public class TextDrawer : IDisposable private const float FONT_ATLAS_H = 1024; private const int FONT_ATLAS_PADDING = 2; - - public struct Line - { - public int Start; - public int Count; - public float OffsetX; - public float Width; - } - - public struct Layout - { - public float Size; - public float Scale; - public float OffsetX; - public float OffsetY; - public float Width; - public float Height; - public Line[] Lines; - public int LinesCount; - public float LineHeight; - } - private static Layout SharedLayout = new() + private static ImTextLayout SharedLayout = new() { - Lines = new Line[128] + Lines = new ImTextLine[128] }; public Texture2D FontAtlas => fontAsset.atlasTexture; @@ -58,11 +59,11 @@ public struct Layout private float renderSize; private float descentLine; - private readonly MeshBuffer buffer; + private readonly ImMeshBuffer buffer; private bool disposed; - public TextDrawer(MeshBuffer buffer) + public ImTextDrawer(ImMeshBuffer buffer) { this.buffer = buffer; } @@ -71,7 +72,7 @@ public void LoadFont(Font font, float? size = null) { UnloadFont(); - fontAsset = FontAsset.CreateFontAsset(font, (int)(size ?? font.fontSize), FONT_ATLAS_PADDING, GlyphRenderMode.SMOOTH, + fontAsset = FontAsset.CreateFontAsset(font, (int)(size ?? font.fontSize), FONT_ATLAS_PADDING, GlyphRenderMode.SMOOTH_HINTED, (int)FONT_ATLAS_W, (int)FONT_ATLAS_H, enableMultiAtlasSupport: false); fontAsset.TryAddCharacters(ASCII); fontAsset.atlasTexture.Apply(); @@ -134,7 +135,7 @@ public void AddText(ReadOnlySpan text, float scale, float x, float y) Profiler.EndSample(); } - public void AddTextWithLayout(ReadOnlySpan text, in Layout layout, float x, float y) + public void AddTextWithLayout(ReadOnlySpan text, in ImTextLayout layout, float x, float y) { Profiler.BeginSample("TextDrawer.AddTextWithLayout"); @@ -194,6 +195,7 @@ private void AddControlGlyphQuad(char c, float px, float py, float scale) #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] + [SuppressMessage("ReSharper", "InconsistentNaming")] private float AddGlyphQuad(Glyph glyph, float px, float py, float scale) { var rect = glyph.glyphRect; @@ -202,50 +204,62 @@ private float AddGlyphQuad(Glyph glyph, float px, float py, float scale) var h = rect.height; var w = rect.width; var metrics = glyph.metrics; + var by = metrics.horizontalBearingY; + var bx = metrics.horizontalBearingX; + + var uv0x = x / FONT_ATLAS_W; + var uv0y = y / FONT_ATLAS_H; + var uv1x = (x + w) / FONT_ATLAS_W; + var uv1y = (y + h) / FONT_ATLAS_H; + + var gw = scale * w; + var gh = scale * h; + var ox = scale * bx; + var oy = scale * (by - h - descentLine); - var glyphWidth = w * scale; - var glyphHeight = h * scale; - var verticalOffset = (metrics.horizontalBearingY - h - descentLine) * scale; - var horizontalOffset = metrics.horizontalBearingX * scale; + var p0x = px + ox; + var p0y = py + oy; + var p1x = p0x + gw; + var p1y = p0y + gh; var vc = buffer.VerticesCount; var ic = buffer.IndicesCount; ref var v0 = ref buffer.Vertices[vc + 0]; - v0.Position.x = px + horizontalOffset; - v0.Position.y = py + verticalOffset; + v0.Position.x = p0x; + v0.Position.y = p0y; v0.Position.z = Depth; v0.Color = Color; - v0.UV.x = x / FONT_ATLAS_W; - v0.UV.y = y / FONT_ATLAS_H; - v0.Atlas = MeshDrawer.FONT_TEX_ID; + v0.UV.x = uv0x; + v0.UV.y = uv0y; + v0.Atlas = ImMeshDrawer.FONT_TEX_ID; ref var v1 = ref buffer.Vertices[vc + 1]; - v1.Position.x = px + horizontalOffset; - v1.Position.y = py + glyphHeight + verticalOffset; + v1.Position.x = p0x; + v1.Position.y = p1y; v1.Position.z = Depth; v1.Color = Color; - v1.UV.x = x / FONT_ATLAS_W; - v1.UV.y = (y + h) / FONT_ATLAS_H; - v1.Atlas = MeshDrawer.FONT_TEX_ID; + v1.UV.x = uv0x; + v1.UV.y = uv1y; + v1.Atlas = ImMeshDrawer.FONT_TEX_ID; ref var v2 = ref buffer.Vertices[vc + 2]; - v2.Position.x = px + glyphWidth + horizontalOffset; - v2.Position.y = py + glyphHeight + verticalOffset; + v2.Position.x = p1x; + v2.Position.y = p1y; v2.Position.z = Depth; v2.Color = Color; - v2.UV.x = (x + w) / FONT_ATLAS_W; - v2.UV.y = (y + h) / FONT_ATLAS_H; - v2.Atlas = MeshDrawer.FONT_TEX_ID; + v2.UV.x = uv1x; + v2.UV.y = uv1y; + v2.Atlas = ImMeshDrawer.FONT_TEX_ID; ref var v3 = ref buffer.Vertices[vc + 3]; - v3.Position.x = px + glyphWidth + horizontalOffset; - v3.Position.y = py + verticalOffset; + v3.Position.x = p1x; + v3.Position.y = p0y; v3.Position.z = Depth; v3.Color = Color; - v3.UV.x = (x + w) / FONT_ATLAS_W; - v3.UV.y = (y) / FONT_ATLAS_H; - v3.Atlas = MeshDrawer.FONT_TEX_ID; + v3.UV.x = uv1x; + v3.UV.y = uv0y; + v3.Atlas = ImMeshDrawer.FONT_TEX_ID; buffer.Indices[ic + 0] = vc + 0; buffer.Indices[ic + 1] = vc + 1; @@ -260,13 +274,13 @@ private float AddGlyphQuad(Glyph glyph, float px, float py, float scale) return metrics.horizontalAdvance * scale; } - public ref readonly Layout BuildTempLayout(in ReadOnlySpan text, float width, float height, float alignX, float alignY, float size) + public ref readonly ImTextLayout BuildTempLayout(ReadOnlySpan text, float width, float height, float alignX, float alignY, float size, bool wrap) { - FillLayout(text, width, height, alignX, alignY, size, ref SharedLayout); + FillLayout(text, width, height, alignX, alignY, size, wrap, ref SharedLayout); return ref SharedLayout; } - public void FillLayout(ReadOnlySpan text, float width, float height, float alignX, float alignY, float size, ref Layout layout) + public void FillLayout(ReadOnlySpan text, float width, float height, float alignX, float alignY, float size, bool wrap, ref ImTextLayout layout) { const float NEXT_LINE_WIDTH_THRESHOLD = 0.0001f; @@ -278,7 +292,7 @@ public void FillLayout(ReadOnlySpan text, float width, float height, float layout.Size = size; layout.LineHeight = this.lineHeight * layout.Scale; - if (text.Length == 0) + if (text.IsEmpty) { return; } @@ -310,7 +324,7 @@ public void FillLayout(ReadOnlySpan text, float width, float height, float var advance = charInfo.glyph.metrics.horizontalAdvance * layout.Scale; var newLine = c == NEW_LINE; - if (newLine || (width > 0 && lineWidth > 0 && (lineWidth + advance) > (width + NEXT_LINE_WIDTH_THRESHOLD))) + if (newLine || (wrap && width > 0 && lineWidth > 0 && (lineWidth + advance) > (width + NEXT_LINE_WIDTH_THRESHOLD))) { ref var line = ref layout.Lines[layout.LinesCount]; diff --git a/Runtime/Scripts/Rendering/TextDrawer.cs.meta b/Runtime/Scripts/Rendering/ImTextDrawer.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/TextDrawer.cs.meta rename to Runtime/Scripts/Rendering/ImTextDrawer.cs.meta diff --git a/Runtime/Scripts/Rendering/Vertex.cs b/Runtime/Scripts/Rendering/ImVertex.cs similarity index 88% rename from Runtime/Scripts/Rendering/Vertex.cs rename to Runtime/Scripts/Rendering/ImVertex.cs index 7d8297d..745a1f9 100644 --- a/Runtime/Scripts/Rendering/Vertex.cs +++ b/Runtime/Scripts/Rendering/ImVertex.cs @@ -5,7 +5,7 @@ namespace Imui.Rendering { [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct Vertex + public struct ImVertex { public static readonly VertexAttributeDescriptor[] VertexAttributes = new VertexAttributeDescriptor[] { @@ -20,7 +20,7 @@ public struct Vertex public Vector2 UV; public float Atlas; - public Vertex(Vector3 position, Color32 color, Vector2 uv, float atlas) + public ImVertex(Vector3 position, Color32 color, Vector2 uv, float atlas) { Position = position; Color = color; @@ -28,7 +28,7 @@ public Vertex(Vector3 position, Color32 color, Vector2 uv, float atlas) Atlas = atlas; } - public Vertex(Vertex vertex) + public ImVertex(ImVertex vertex) { Position = vertex.Position; Color = vertex.Color; diff --git a/Runtime/Scripts/Rendering/Vertex.cs.meta b/Runtime/Scripts/Rendering/ImVertex.cs.meta similarity index 100% rename from Runtime/Scripts/Rendering/Vertex.cs.meta rename to Runtime/Scripts/Rendering/ImVertex.cs.meta diff --git a/Runtime/Scripts/Styling/ImColors.cs b/Runtime/Scripts/Styling/ImColors.cs deleted file mode 100644 index f9304bb..0000000 --- a/Runtime/Scripts/Styling/ImColors.cs +++ /dev/null @@ -1,43 +0,0 @@ -using UnityEngine; - -namespace Imui.Styling -{ - public static class ImColors - { - public static readonly Color32 Clear = new Color32(0, 0, 0, 0); - - public static readonly Color32 Black = new Color32(0, 0, 0, 255); - public static readonly Color32 White = new Color32(255, 255, 255, 255); - - public static readonly Color32 Gray0 = Black; - public static readonly Color32 Gray1 = new Color32(32, 32, 32, 255); - public static readonly Color32 Gray2 = new Color32(64, 64, 64, 255); - public static readonly Color32 Gray3 = new Color32(96, 96, 96, 255); - public static readonly Color32 Gray4 = new Color32(128, 128, 128, 255); - public static readonly Color32 Gray5 = new Color32(160, 160, 160, 255); - public static readonly Color32 Gray6 = new Color32(192, 192, 192, 255); - public static readonly Color32 Gray7 = new Color32(224, 224, 224, 255); - public static readonly Color32 Gray8 = White; - - public static readonly Color32 DarkBlue = new Color32(26, 66, 153, 255); - public static readonly Color32 Blue = new Color32(40, 87, 189, 255); - public static readonly Color32 LightBlue = new Color32(70, 123, 240, 255); - - public static Color32 WithAlpha(this Color32 color, byte alpha) - { - color.a = alpha; - return color; - } - - public static Color32 Multiply(this Color32 color, float value) - { - value = Mathf.Clamp01(value); - - color.r = (byte)(color.r * value); - color.g = (byte)(color.g * value); - color.b = (byte)(color.b * value); - - return color; - } - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Utility/ImuiAssert.cs b/Runtime/Scripts/Utility/ImAssert.cs similarity index 96% rename from Runtime/Scripts/Utility/ImuiAssert.cs rename to Runtime/Scripts/Utility/ImAssert.cs index 7dd2cbb..544b5ea 100644 --- a/Runtime/Scripts/Utility/ImuiAssert.cs +++ b/Runtime/Scripts/Utility/ImAssert.cs @@ -8,7 +8,7 @@ internal class ImuiAssertException : Exception public ImuiAssertException(string message) : base(message) { } } -internal static class ImuiAssert +internal static class ImAssert { [Conditional("IMUI_DEBUG")] [Conditional("IMUI_VALIDATION")] diff --git a/Runtime/Scripts/Utility/ImuiAssert.cs.meta b/Runtime/Scripts/Utility/ImAssert.cs.meta similarity index 100% rename from Runtime/Scripts/Utility/ImuiAssert.cs.meta rename to Runtime/Scripts/Utility/ImAssert.cs.meta diff --git a/Runtime/Scripts/Utility/CircularBuffer.cs b/Runtime/Scripts/Utility/ImCircularBuffer.cs similarity index 95% rename from Runtime/Scripts/Utility/CircularBuffer.cs rename to Runtime/Scripts/Utility/ImCircularBuffer.cs index ee3cf59..f1b0344 100644 --- a/Runtime/Scripts/Utility/CircularBuffer.cs +++ b/Runtime/Scripts/Utility/ImCircularBuffer.cs @@ -1,6 +1,6 @@ namespace Imui.Utility { - public struct CircularBuffer + public struct ImCircularBuffer { public readonly int Capacity; @@ -8,7 +8,7 @@ public struct CircularBuffer public int Count; public T[] Array; - public CircularBuffer(int capacity) + public ImCircularBuffer(int capacity) { Capacity = capacity; diff --git a/Runtime/Scripts/Utility/CircularBuffer.cs.meta b/Runtime/Scripts/Utility/ImCircularBuffer.cs.meta similarity index 100% rename from Runtime/Scripts/Utility/CircularBuffer.cs.meta rename to Runtime/Scripts/Utility/ImCircularBuffer.cs.meta diff --git a/Runtime/Scripts/Utility/DynamicArray.cs b/Runtime/Scripts/Utility/ImDynamicArray.cs similarity index 92% rename from Runtime/Scripts/Utility/DynamicArray.cs rename to Runtime/Scripts/Utility/ImDynamicArray.cs index f14425e..7ff5d14 100644 --- a/Runtime/Scripts/Utility/DynamicArray.cs +++ b/Runtime/Scripts/Utility/ImDynamicArray.cs @@ -2,12 +2,12 @@ namespace Imui.Utility { - internal struct DynamicArray + internal struct ImDynamicArray { public int Count; public T[] Array; - public DynamicArray(int capacity) + public ImDynamicArray(int capacity) { Array = new T[capacity]; Count = 0; @@ -61,7 +61,7 @@ public bool TryPop(out T value) public T Pop() { - ImuiAssert.True(Count >= 0, "Popping empty array"); + ImAssert.True(Count >= 0, "Popping empty array"); return Array[--Count]; } @@ -78,7 +78,7 @@ public T TryPeek(T @default = default) public ref T Peek() { - ImuiAssert.True(Count >= 0, "Peeking empty array"); + ImAssert.True(Count >= 0, "Peeking empty array"); return ref Array[Count - 1]; } diff --git a/Runtime/Scripts/Utility/DynamicArray.cs.meta b/Runtime/Scripts/Utility/ImDynamicArray.cs.meta similarity index 100% rename from Runtime/Scripts/Utility/DynamicArray.cs.meta rename to Runtime/Scripts/Utility/ImDynamicArray.cs.meta diff --git a/Runtime/Scripts/Utility/ResizeableBuffer.cs b/Runtime/Scripts/Utility/ImResizeableBuffer.cs similarity index 80% rename from Runtime/Scripts/Utility/ResizeableBuffer.cs rename to Runtime/Scripts/Utility/ImResizeableBuffer.cs index 007ab4c..a03926a 100644 --- a/Runtime/Scripts/Utility/ResizeableBuffer.cs +++ b/Runtime/Scripts/Utility/ImResizeableBuffer.cs @@ -3,11 +3,11 @@ namespace Imui.Utility { - public struct ResizeableBuffer + public struct ImResizeableBuffer { public T[] Array; - public ResizeableBuffer(int capacity) + public ImResizeableBuffer(int capacity) { Array = new T[capacity]; } @@ -32,7 +32,7 @@ public Span AsSpan(int length) return new Span(Array, 0, length); } - public static implicit operator Span(ResizeableBuffer buffer) + public static implicit operator Span(ImResizeableBuffer buffer) { return buffer.Array; } diff --git a/Runtime/Scripts/Utility/ResizeableBuffer.cs.meta b/Runtime/Scripts/Utility/ImResizeableBuffer.cs.meta similarity index 100% rename from Runtime/Scripts/Utility/ResizeableBuffer.cs.meta rename to Runtime/Scripts/Utility/ImResizeableBuffer.cs.meta diff --git a/Runtime/Scripts/Utility/PlatformUtility.cs b/Runtime/Scripts/Utility/PlatformUtility.cs deleted file mode 100644 index b615f77..0000000 --- a/Runtime/Scripts/Utility/PlatformUtility.cs +++ /dev/null @@ -1,15 +0,0 @@ -using UnityEngine; - -namespace Imui.Utility -{ - public static class PlatformUtility - { - public static bool IsEditorSimulator() - { -#if UNITY_EDITOR - return UnityEngine.Device.SystemInfo.deviceType != DeviceType.Desktop; -#endif - return false; - } - } -} \ No newline at end of file diff --git a/Runtime/Scripts/Utility/PlatformUtility.cs.meta b/Runtime/Scripts/Utility/PlatformUtility.cs.meta deleted file mode 100644 index 48e2fc1..0000000 --- a/Runtime/Scripts/Utility/PlatformUtility.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 7f485710a7f2489aa842e77f85d5141b -timeCreated: 1707687681 \ No newline at end of file diff --git a/Samples~/Scripts/DemoRoot.cs b/Samples~/Scripts/DemoRoot.cs index 1c813ad..5ac0849 100644 --- a/Samples~/Scripts/DemoRoot.cs +++ b/Samples~/Scripts/DemoRoot.cs @@ -1,3 +1,4 @@ +using Imui.Controls.Windows; using Imui.Core; using Imui.IO.UGUI; using UnityEngine; @@ -7,18 +8,15 @@ namespace Imui.Demo public class DemoRoot : MonoBehaviour { [SerializeField] private Canvas canvas; - [SerializeField] private ImuiGraphic graphic; + [SerializeField] private ImCanvasBackend graphic; [SerializeField] private Font font; [SerializeField] private float fontSize; - [SerializeField] private DemoWindowConfig config; private ImGui gui; - private DemoWindow window; private void Awake() { gui = new ImGui(graphic, graphic); - window = new DemoWindow(config); gui.TextDrawer.LoadFont(font, fontSize); } @@ -26,7 +24,7 @@ private void Update() { gui.UiScale = canvas.scaleFactor; gui.BeginFrame(); - window.Draw(gui); + ImDemoWindow.Draw(gui); gui.EndFrame(); gui.Render(); } diff --git a/Samples~/Scripts/DemoWindow.cs b/Samples~/Scripts/DemoWindow.cs deleted file mode 100644 index 744d0d4..0000000 --- a/Samples~/Scripts/DemoWindow.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using Imui.Controls; -using Imui.Controls.Layout; -using Imui.Core; -using Imui.Debugging; -using UnityEngine; - -namespace Imui.Demo -{ - [Serializable] - public struct DemoWindowConfig - { - public Texture[] Textures; - } - - public class DemoWindow - { - private static char[] formatBuffer = new char[256]; - - private DemoWindowConfig config; - private bool checkmarkValue; - private int selectedValue; - private float sliderValue; - private string[] values = new string[] - { - "Value 1", "Value 2", "Value 3", "Value 4", "Value 5", "Value 6", - "Value 7", "Value 8", "Value 9", "Value 10", "Value 11", "Value 12" - }; - private string singleLineText = "Single line text edit"; - private string multiLineText = "Multiline text\nedit"; - private bool showDebugWindow; - - public DemoWindow(DemoWindowConfig config) - { - this.config = config; - } - - public void Draw(ImGui gui) - { - gui.BeginWindow("Demo"); - gui.Checkmark(ref showDebugWindow, "Show debug window"); - - gui.BeginFoldout("Widgets", out var widgetsOpen); - if (widgetsOpen) - { - DrawWidgetsPage(gui); - } - gui.EndFoldout(); - - gui.BeginFoldout("Layout", out var layoutOpen); - if (layoutOpen) - { - DrawLayoutPage(gui); - } - gui.EndFoldout(); - - gui.BeginFoldout("Style", out var styleOpen); - if (styleOpen) - { - DrawStylePage(gui); - } - gui.EndFoldout(); - - gui.EndWindow(); - - if (showDebugWindow) - { - ImDebug.Window(gui); - } - } - - private void DrawWidgetsPage(ImGui gui) - { - const string sliderLabel = "Slider value: "; - - gui.ButtonFitted("Small Button"); - gui.Button("Big Button"); - gui.Checkmark(ref checkmarkValue, "Checkmark"); - gui.Dropdown(ref selectedValue, values); - gui.Slider(ref sliderValue, 0.0f, 1.0f); - gui.Text(Format("Slider value: ", sliderValue, "0.00")); - gui.TextEdit(ref singleLineText); - gui.TextEdit(ref multiLineText, gui.GetAvailableWidth(), 200); - - gui.AddSpacing(); - - var imageTex = config.Textures[0]; - var imageRect = gui.Layout.AddRect(imageTex.width, imageTex.height); - gui.Image(imageRect, imageTex); - } - - private void DrawLayoutPage(ImGui gui) - { - gui.AddSpacing(); - - gui.BeginHorizontal(); - for (int i = 0; i < 3; ++i) - { - gui.ButtonFitted("Horizontal"); - } - gui.EndHorizontal(); - - gui.AddSpacing(); - - gui.BeginVertical(); - for (int i = 0; i < 3; ++i) - { - gui.ButtonFitted("Vertical"); - } - gui.EndVertical(); - - gui.AddSpacing(); - - var grid = gui.BeginGrid(5, gui.GetRowHeight()); - for (int i = 0; i < 12; ++i) - { - var cell = gui.GridNextCell(ref grid); - gui.TextFittedSlow(Format("Grid cell ", i, "0"), in cell); - } - gui.EndGrid(in grid); - } - - private void DrawStylePage(ImGui gui) - { - gui.Text("Text Size"); - gui.Slider(ref ImControls.Style.TextSize, 6, 128); - - gui.Text("Controls Spacing"); - gui.Slider(ref ImControls.Style.Spacing, 0, 32); - } - - private ReadOnlySpan Format(ReadOnlySpan prefix, float value, ReadOnlySpan format = default) - { - var dst = new Span(formatBuffer); - prefix.CopyTo(dst); - value.TryFormat(dst[prefix.Length..], out var written, format); - return dst[..(prefix.Length + written)]; - } - } -} \ No newline at end of file diff --git a/Samples~/Scripts/DemoWindow.cs.meta b/Samples~/Scripts/DemoWindow.cs.meta deleted file mode 100644 index 988d0ad..0000000 --- a/Samples~/Scripts/DemoWindow.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 922b4bc66b5b342b29bd1a3fb177a3f6 -timeCreated: 1717794549 \ No newline at end of file diff --git a/Samples~/Textures.meta b/Samples~/Textures.meta deleted file mode 100644 index 936bd64..0000000 --- a/Samples~/Textures.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: c70c635bedc64428c9ae216903269850 -timeCreated: 1717795197 \ No newline at end of file diff --git a/Samples~/Textures/cat.png b/Samples~/Textures/cat.png deleted file mode 100644 index c7acaae..0000000 Binary files a/Samples~/Textures/cat.png and /dev/null differ diff --git a/Samples~/Textures/cat.png.meta b/Samples~/Textures/cat.png.meta deleted file mode 100644 index a001434..0000000 --- a/Samples~/Textures/cat.png.meta +++ /dev/null @@ -1,153 +0,0 @@ -fileFormatVersion: 2 -guid: 301ee1e2d7ddd474aa989d3e09f7d220 -TextureImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 12 - mipmaps: - mipMapMode: 0 - enableMipMap: 1 - sRGBTexture: 1 - linearTexture: 0 - fadeOut: 0 - borderMipMap: 0 - mipMapsPreserveCoverage: 0 - alphaTestReferenceValue: 0.5 - mipMapFadeDistanceStart: 1 - mipMapFadeDistanceEnd: 3 - bumpmap: - convertToNormalMap: 0 - externalNormalMap: 0 - heightScale: 0.25 - normalMapFilter: 0 - flipGreenChannel: 0 - isReadable: 0 - streamingMipmaps: 0 - streamingMipmapsPriority: 0 - vTOnly: 0 - ignoreMipmapLimit: 0 - grayScaleToAlpha: 0 - generateCubemap: 6 - cubemapConvolution: 0 - seamlessCubemap: 0 - textureFormat: 1 - maxTextureSize: 2048 - textureSettings: - serializedVersion: 2 - filterMode: 1 - aniso: 1 - mipBias: 0 - wrapU: 0 - wrapV: 0 - wrapW: 0 - nPOTScale: 1 - lightmap: 0 - compressionQuality: 50 - spriteMode: 0 - spriteExtrude: 1 - spriteMeshType: 1 - alignment: 0 - spritePivot: {x: 0.5, y: 0.5} - spritePixelsToUnits: 100 - spriteBorder: {x: 0, y: 0, z: 0, w: 0} - spriteGenerateFallbackPhysicsShape: 1 - alphaUsage: 1 - alphaIsTransparency: 0 - spriteTessellationDetail: -1 - textureType: 0 - textureShape: 1 - singleChannelComponent: 0 - flipbookRows: 1 - flipbookColumns: 1 - maxTextureSizeSet: 0 - compressionQualitySet: 0 - textureFormatSet: 0 - ignorePngGamma: 0 - applyGammaDecoding: 0 - swizzle: 50462976 - cookieLightType: 0 - platformSettings: - - serializedVersion: 3 - buildTarget: DefaultTexturePlatform - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Standalone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: iPhone - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Android - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - - serializedVersion: 3 - buildTarget: Server - maxTextureSize: 2048 - resizeAlgorithm: 0 - textureFormat: -1 - textureCompression: 1 - compressionQuality: 50 - crunchedCompression: 0 - allowsAlphaSplitting: 0 - overridden: 0 - ignorePlatformSupport: 0 - androidETC2FallbackOverride: 0 - forceMaximumCompressionQuality_BC6H_BC7: 0 - spriteSheet: - serializedVersion: 2 - sprites: [] - outline: [] - physicsShape: [] - bones: [] - spriteID: - internalID: 0 - vertices: [] - indices: - edges: [] - weights: [] - secondaryTextures: [] - nameFileIdTable: {} - mipmapLimitGroupName: - pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/package.json b/package.json index c61993a..0270ac2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "deszz.imui", "displayName": "Imui", "description": "IMGUI framework for unity", - "version": "0.1.0", + "version": "0.2.0", "unity": "2021.3", "author": "Artem Sokolov ", "repository": {