From d4145179a90db18dd3b0ea27ff4c9e9511771231 Mon Sep 17 00:00:00 2001 From: Norite SC <162097313+cNori@users.noreply.github.com> Date: Sun, 2 Jun 2024 02:23:48 +0200 Subject: [PATCH 1/3] RadialMenu and material nodes --- Source/Editor/Surface/Archetypes/Material.cs | 136 ++++++ .../MaterialGenerator.Material.cpp | 104 +++++ Source/Engine/UI/GUI/Special/RadialMenu.cs | 402 ++++++++++++++++++ 3 files changed, 642 insertions(+) create mode 100644 Source/Engine/UI/GUI/Special/RadialMenu.cs diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 58d1b854d..4b2978ceb 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -936,6 +936,142 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2), } }, + new NodeArchetype + { + TypeID = 43, + Title = "Rotate UV [Simple Rotator]", + Description = "Rotates 2d vector", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(250, 40), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = + [ + 0.0f, + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2), + ] + }, + new NodeArchetype + { + TypeID = 44, + Title = "Cone Gradient", + Description = "", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 40), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = + [ + 0.0f, + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2), + ] + }, + new NodeArchetype + { + TypeID = 45, + Title = "Cycle Gradient", + Description = "2d verison of sphere mask", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 20), + ConnectionsHints = ConnectionsHint.Vector, + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1), + ] + }, + new NodeArchetype + { + TypeID = 46, + Title = "Falloff and Offset", + Description = "", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 60), + ConnectionsHints = ConnectionsHint.Numeric, + DefaultValues = + [ + 0.0f, + 0.9f + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0), + NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2,1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3), + ] + }, + new NodeArchetype + { + TypeID = 47, + Title = "Linear Gradient", + Description = "x = Gradient along X axis, y = Gradient along Y axis", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 60), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = + [ + 0.0f, + false + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2,1), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3), + ] + }, + new NodeArchetype + { + TypeID = 48, + Title = "Radial Gradient", + Description = "", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 40), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = + [ + 0.0f, + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2), + ] + }, + new NodeArchetype + { + TypeID = 49, + Title = "Ring Gradient", + Description = "x = InnerMask,y = OuterMask,z = Mask", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(175, 80), + ConnectionsHints = ConnectionsHint.Vector, + DefaultValues = + [ + 1.0f, + 0.8f, + 0.05f, + ], + Elements = + [ + NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2,1), + NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3,2), + NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4), + ] + }, }; } } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index 66d24d4f3..c8961e1e0 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -569,6 +569,110 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // Do the inverse interpolation and saturate it value = writeLocal(ValueType::Float, String::Format(TEXT("saturate((({0} - {1}) / ({2} - {1})))"), gradient.Value, lowerEdge.Value, upperEdge.Value), node); } + // Rotate UV [Rotator Simple] + case 43: + { + //cosine = cos(rotation); + //sine = sin(rotation); + //float2 out = float2(cosine * uv.x + sine * uv.y,cosine * uv.y - sine * uv.x); + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node); + auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node); + value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); + break; + } + // Cone Gradient + case 44: + { + //float gradient = angle - abs(atan2(uv.x,uv.y)); + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x,{0}.y))"), uv.Value, rotationAngle.Value), node); + break; + } + // Cycle Gradient + case 45: + { + //float gradient = 1 - lenght(uv * 2); + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); + break; + } + //Falloff and Offset + case 46: + { + //float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1) + const auto Value = tryGetValue(node->GetBox(0), ShaderGraphValue(0.0f)); + const auto Offset = tryGetValue(node->GetBox(1), node->Values[0].AsFloat); + const auto Falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat); + + value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - {1})) + {2}) / {2}),0,1)"), Value.Value, Offset.Value, Falloff.Value), node); + break; + } + //Linear Gradient + case 47: + { + // float2 uv = Input0.xy; + // float r = Input0.z; + // float2 A = 1.0 - float2(cos(r) * uv.x + sin(r) * uv.y, cos(r) * uv.y - sin(r) * uv.x); + // float2 out = float2(Mirror ? abs(A.x < 1.0 ? (A.x - 0.5) * 2 : (2 - ((A.x - 0.5) * 2)) * -1) : A.x < 1.0 ? (A.x - 0.5) * 2 : 1,Mirror ? abs(A.y < 1.0 ? (A.y - 0.5) * 2 : (2 - ((A.y - 0.5) * 2)) * -1) : A.y < 1.0 ? (A.y - 0.5) * 2 : 1); + + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + const auto Mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool(); + + auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node); + auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node); + auto A = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); + value = writeLocal( + ValueType::Float2, + String::Format(TEXT + ( + "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)" + ), + Mirror.Value, + A.Value), + node); + break; + } + //Radial Gradient + case 48: + { + //float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0); + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(atan2({0}.x,{0}.y) - {1},0.0,1.0)"), uv.Value, rotationAngle.Value), node); + break; + } + //Ring Gradient + case 49: + { + // Nodes: + // float c = CycleGradient(uv) + // float InnerMask = FalloffAndOffset(c,(OuterBounds - Falloff),Falloff) + // float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff) + // float Mask = OuterMask * InnerMask; + + //ToDo: cheak if there is some useless operetors + + //expanded + //float cycleGradient = 1 - lenght(uv * 2); + //float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1) + //float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1) + //float Mask = OuterMask * InnerMask; + + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto OuterBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + const auto InnerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat(); + const auto Falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat(); + auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); + auto InnerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, OuterBounds.Value, Falloff.Value), node); + auto OuterMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, InnerBounds.Value, Falloff.Value), node); + auto Mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), InnerMask.Value, OuterMask.Value), node); + value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), InnerMask.Value, OuterMask.Value, Mask.Value), node); + break; + } default: break; } diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs new file mode 100644 index 000000000..1c7d8c174 --- /dev/null +++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs @@ -0,0 +1,402 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FlaxEngine.GUI +{ + /// + /// + /// + /// + public class RadialMenu : ContainerControl + { + [NoSerialize] private bool IsDirty = true; + [NoSerialize] private float m_Angle; + [NoSerialize] private float m_SelectedSegment; + [NoSerialize] private int m_F_SelectedSegment = -1; + + private MaterialInstance MaterialInstance; + private sbyte m_SegmentCount; + private Color highlightColor; + private Color forgraundColor; + private Color selectionColor; + private float m_EdgeOffset; + private float m_Thickness = 0.0f; + private float USize => Size.X < Size.Y ? Size.X : Size.Y; + private bool ShowMatProp => MaterialInstance != null; + private MaterialBase material; + + /// + /// The material + /// + [EditorOrder(1)] + public MaterialBase Material + { + get => material; + set + { + material = value; + if (material == null) + { + FlaxEngine.Object.DestroyNow(MaterialInstance); + MaterialInstance = null; + } + else + { + IsDirty = true; + } + } + } + + /// + /// Gets or sets the edge offset. + /// + /// + /// The edge offset. + /// + [EditorOrder(2), Range(0, 1)] + public float EdgeOffset + { + get + { + return m_EdgeOffset; + } + set + { + m_EdgeOffset = Math.Clamp(value, 0, 1); + IsDirty = true; + this.PerformLayout(); + } + } + /// + /// Gets or sets the thickness. + /// + /// + /// The thickness. + /// + [EditorOrder(3), Range(0, 1), VisibleIf("ShowMatProp")] + public float Thickness + { + get + { + return m_Thickness; + } + set + { + m_Thickness = Math.Clamp(value, 0, 1); + IsDirty = true; + this.PerformLayout(); + } + } + /// + /// Gets or sets control background color (transparent color (alpha=0) means no background rendering) + /// + [VisibleIf("ShowMatProp")] + public new Color BackgroundColor //force overload + { + get => base.BackgroundColor; + set + { + IsDirty = true; + base.BackgroundColor = value; + } + } + /// + /// Gets or sets the color of the highlight. + /// + /// + /// The color of the highlight. + /// + [VisibleIf("ShowMatProp")] + public Color HighlightColor { get => highlightColor; set { IsDirty = true; highlightColor = value; } } + /// + /// Gets or sets the color of the forgraund. + /// + /// + /// The color of the forgraund. + /// + [VisibleIf("ShowMatProp")] + public Color ForgraundColor { get => forgraundColor; set { IsDirty = true; forgraundColor = value; } } + /// + /// Gets or sets the color of the selection. + /// + /// + /// The color of the selection. + /// + [VisibleIf("ShowMatProp")] + public Color SelectionColor { get => selectionColor; set { IsDirty = true; selectionColor = value; } } + + /// + /// The selected callback + /// + [HideInEditor] + public Action Selected; + + /// + /// The allow change selection when inside + /// + [VisibleIf("ShowMatProp")] + public bool AllowChangeSelectionWhenInside; + /// + /// The center as button + /// + [VisibleIf("ShowMatProp")] + public bool CenterAsButton; + + + /// + /// Initializes a new instance of the class. + /// + public RadialMenu() + : this(0, 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Position X coordinate + /// Position Y coordinate + /// Width + /// Height + public RadialMenu(float x, float y, float width = 100, float height = 100) + : base(x, y, width, height) + { + var style = Style.Current; + if (style != null) + { + BackgroundColor = style.BackgroundNormal; + HighlightColor = style.BackgroundSelected; + ForgraundColor = style.BackgroundHighlighted; + SelectionColor = style.BackgroundSelected; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Position + /// Size + public RadialMenu(Float2 location, Float2 size) + : this(location.X, location.Y, size.X, size.Y) + { + } + + /// + /// Draws the control. + /// + public override void DrawSelf() + { + if (MaterialInstance != null) + { + if (IsDirty) + { + MaterialInstance.SetParameterValue("RadialPieMenu_EdgeOffset", Math.Clamp(1 - m_EdgeOffset, 0, 1)); + MaterialInstance.SetParameterValue("RadialPieMenu_Thickness", Math.Clamp(m_Thickness, 0, 1)); + MaterialInstance.SetParameterValue("RadialPieMenu_Angle", ((float)1 / m_SegmentCount) * Mathf.Pi); + MaterialInstance.SetParameterValue("RadialPieMenu_SCount", m_SegmentCount); + + MaterialInstance.SetParameterValue("RadialPieMenu_HighlightColor", HighlightColor); + MaterialInstance.SetParameterValue("RadialPieMenu_ForgraundColor", ForgraundColor); + MaterialInstance.SetParameterValue("RadialPieMenu_BackgroundColor", BackgroundColor); + MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); + UpdateFSS(); + IsDirty = false; + } + Render2D.DrawMaterial(MaterialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y))); + } + else + { + if (Material != null) + { + MaterialInstance = Material.CreateVirtualInstance(); + } + } + } + /// + public override void OnMouseMove(Float2 location) + { + if (MaterialInstance != null) + { + if (m_F_SelectedSegment == -1) + { + var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; + var max = (1 - m_EdgeOffset) * USize * 0.5f; + var val = ((USize * 0.5f) - location).Length; + if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside) + { + var size = new Float2(USize); + var p = (size * 0.5f) - location; + var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi; + m_Angle = Mathf.Atan2(p.X, p.Y); + m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa; + m_SelectedSegment = m_Angle; + m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa); + if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle)) + m_Angle = 0; + MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); + } + } + } + base.OnMouseMove(location); + } + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (MaterialInstance == null) + return base.OnMouseDown(location, button); + + var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; + var max = (1 - m_EdgeOffset) * USize * 0.5f; + var val = ((USize * 0.5f) - location).Length; + var c = val < min && CenterAsButton; + var selected = (int)m_SelectedSegment; + selected++; + if (Mathf.IsInRange(val, min, max) || c) + { + if (c) + { + m_F_SelectedSegment = 0; + } + else + { + m_F_SelectedSegment = selected; + } + UpdateFSS(); + return true; + } + else + { + m_F_SelectedSegment = -1; + UpdateFSS(); + } + return base.OnMouseDown(location, button); + } + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (MaterialInstance == null) + return base.OnMouseDown(location, button); + + if (m_F_SelectedSegment >= 0) + { + if (Selected != null) + { + Selected(m_F_SelectedSegment); + } + m_F_SelectedSegment = -1; + UpdateFSS(); + var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; + var max = (1 - m_EdgeOffset) * USize * 0.5f; + var val = ((USize * 0.5f) - location).Length; + if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside) + { + var size = new Float2(USize); + var p = (size * 0.5f) - location; + var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi; + m_Angle = Mathf.Atan2(p.X, p.Y); + m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa; + m_SelectedSegment = m_Angle; + m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa); + if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle)) + m_Angle = 0; + MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); + } + } + return base.OnMouseUp(location, button); + } + private void UpdateFSS() + { + if (m_F_SelectedSegment == -1) + { + MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor); + } + else + { + if (CenterAsButton) + { + if (m_F_SelectedSegment > 0) + { + MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor); + } + else + { + MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor); + } + } + else + { + MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor); + } + } + } + /// + public override void OnMouseLeave() + { + if (MaterialInstance == null) + return; + + m_SelectedSegment = 0; + m_F_SelectedSegment = -1; + if (Selected != null) + { + Selected(m_F_SelectedSegment); + } + UpdateFSS(); + } + /// + public override void OnChildrenChanged() + { + m_SegmentCount = 0; + for (int i = 0; i < Children.Count; i++) + { + if (Children[i] is Image) + { + m_SegmentCount++; + } + } + IsDirty = true; + base.OnChildrenChanged(); + } + /// + public override void PerformLayout(bool force = false) + { + var Sa = -1.0f / m_SegmentCount * Mathf.TwoPi; + var midp = USize * 0.5f; + var mp = ((1 - m_EdgeOffset) - (m_Thickness * 0.5f)) * midp; + float f = 0; + if (m_SegmentCount % 2 != 0) + { + f += Sa * 0.5f; + } + if (MaterialInstance == null) + { + for (int i = 0; i < Children.Count; i++) + { + Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp; + f += Sa; + } + } + else + { + for (int i = 0; i < Children.Count; i++) + { + if (Children[i] is Image) + { + Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp; + f += Sa; + } + } + } + + base.PerformLayout(force); + } + private Float2 Rotate2D(Float2 point, float angle) + { + return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y, + Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X); + } + } +} From f95e7e96bf29d25b2bec6f53484228c3269ab05b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Jun 2024 08:43:31 +0200 Subject: [PATCH 2/3] Codestyle fixes and some nodes docs --- Source/Editor/Surface/Archetypes/Material.cs | 44 ++++++++-------- .../MaterialGenerator.Material.cpp | 50 +++++++++---------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Material.cs b/Source/Editor/Surface/Archetypes/Material.cs index 4b2978ceb..f1da861c1 100644 --- a/Source/Editor/Surface/Archetypes/Material.cs +++ b/Source/Editor/Surface/Archetypes/Material.cs @@ -939,8 +939,8 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 43, - Title = "Rotate UV [Simple Rotator]", - Description = "Rotates 2d vector", + Title = "Rotate UV", + Description = "Rotates 2D vector by given angle around (0,0) origin", Flags = NodeFlags.MaterialGraph, Size = new Float2(250, 40), ConnectionsHints = ConnectionsHint.Vector, @@ -950,8 +950,8 @@ namespace FlaxEditor.Surface.Archetypes ], Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), - NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 2), ] }, @@ -959,7 +959,7 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 44, Title = "Cone Gradient", - Description = "", + Description = "Creates cone gradient around normalized UVs (range [-1; 1]), angle is in radians (range [0; TwoPi])", Flags = NodeFlags.MaterialGraph, Size = new Float2(175, 40), ConnectionsHints = ConnectionsHint.Vector, @@ -969,8 +969,8 @@ namespace FlaxEditor.Surface.Archetypes ], Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), - NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2), ] }, @@ -978,13 +978,13 @@ namespace FlaxEditor.Surface.Archetypes { TypeID = 45, Title = "Cycle Gradient", - Description = "2d verison of sphere mask", + Description = "Creates 2D sphere mask gradient around normalized UVs (range [-1; 1])", Flags = NodeFlags.MaterialGraph, Size = new Float2(175, 20), ConnectionsHints = ConnectionsHint.Vector, Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 1), ] }, @@ -1004,8 +1004,8 @@ namespace FlaxEditor.Surface.Archetypes Elements = [ NodeElementArchetype.Factory.Input(0, "Value", true, typeof(float), 0), - NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1,0), - NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2,1), + NodeElementArchetype.Factory.Input(1, "Offset", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Input(2, "Falloff", true, typeof(float), 2, 1), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 3), ] }, @@ -1024,9 +1024,9 @@ namespace FlaxEditor.Surface.Archetypes ], Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), - NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), - NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2,1), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Input(2, "Mirror", true, typeof(bool), 2, 1), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float2), 3), ] }, @@ -1042,10 +1042,10 @@ namespace FlaxEditor.Surface.Archetypes [ 0.0f, ], - Elements = + Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), - NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1,0), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "Angle", true, typeof(float), 1, 0), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(float), 2), ] }, @@ -1063,12 +1063,12 @@ namespace FlaxEditor.Surface.Archetypes 0.8f, 0.05f, ], - Elements = + Elements = [ - NodeElementArchetype.Factory.Input(0, "Uvs", true, typeof(Float2), 0), - NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1,0), - NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2,1), - NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3,2), + NodeElementArchetype.Factory.Input(0, "UV", true, typeof(Float2), 0), + NodeElementArchetype.Factory.Input(1, "OuterBounds", true, typeof(float), 1, 0), + NodeElementArchetype.Factory.Input(2, "InnerBounds", true, typeof(float), 2, 1), + NodeElementArchetype.Factory.Input(3, "Falloff", true, typeof(float), 3, 2), NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4), ] }, diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index c8961e1e0..a9c983eec 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -594,23 +594,22 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // Cycle Gradient case 45: { - //float gradient = 1 - lenght(uv * 2); + //float gradient = 1 - length(uv * 2); const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); break; } - //Falloff and Offset + // Falloff and Offset case 46: { //float out = clamp((((Value - (1 - Offset)) + Falloff) / Falloff),0,1) - const auto Value = tryGetValue(node->GetBox(0), ShaderGraphValue(0.0f)); - const auto Offset = tryGetValue(node->GetBox(1), node->Values[0].AsFloat); - const auto Falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat); - - value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - {1})) + {2}) / {2}),0,1)"), Value.Value, Offset.Value, Falloff.Value), node); + const auto in = tryGetValue(node->GetBox(0), ShaderGraphValue::Zero); + const auto graphValue = tryGetValue(node->GetBox(1), node->Values[0].AsFloat); + const auto falloff = tryGetValue(node->GetBox(2), node->Values[1].AsFloat); + value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - {1})) + {2}) / {2}))"), in.Value, graphValue.Value, falloff.Value), node); break; } - //Linear Gradient + // Linear Gradient case 47: { // float2 uv = Input0.xy; @@ -620,19 +619,16 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); - const auto Mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool(); + const auto mirror = tryGetValue(node->GetBox(2), node->Values[1].AsBool).AsBool(); auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node); auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node); - auto A = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); + auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); value = writeLocal( - ValueType::Float2, - String::Format(TEXT - ( - "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)" - ), - Mirror.Value, - A.Value), + ValueType::Float2, String::Format(TEXT + ( + "float2({0} ? abs({1}.x < 1.0 ? ({1}.x - 0.5) * 2 : (2 - (({1}.x - 0.5) * 2)) * -1) : {1}.x < 1.0 ? ({1}.x - 0.5) * 2 : 1,{0} ? abs({1}.y < 1.0 ? ({1}.y - 0.5) * 2 : (2 - (({1}.y - 0.5) * 2)) * -1) : {1}.y < 1.0 ? ({1}.y - 0.5) * 2 : 1)" + ), mirror.Value, a.Value), node); break; } @@ -654,23 +650,23 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) // float OuterMask = FalloffAndOffset(1-c,1-InnerBounds,Falloff) // float Mask = OuterMask * InnerMask; - //ToDo: cheak if there is some useless operetors + // TODO: check if there is some useless operators //expanded - //float cycleGradient = 1 - lenght(uv * 2); + //float cycleGradient = 1 - length(uv * 2); //float InnerMask = clamp((((c - (1 - (OuterBounds - Falloff))) + Falloff) / Falloff),0,1) //float OuterMask = clamp(((((1-c) - (1 - (1-InnerBounds))) + Falloff) / Falloff),0,1) //float Mask = OuterMask * InnerMask; - const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); - const auto OuterBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); - const auto InnerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat(); - const auto Falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat(); + const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); + const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); + const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat(); + const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat(); auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); - auto InnerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, OuterBounds.Value, Falloff.Value), node); - auto OuterMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, InnerBounds.Value, Falloff.Value), node); - auto Mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), InnerMask.Value, OuterMask.Value), node); - value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), InnerMask.Value, OuterMask.Value, Mask.Value), node); + auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, outerBounds.Value, falloff.Value), node); + auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, innerBounds.Value, falloff.Value), node); + auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node); + value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), innerMask.Value, outerMask.Value, mask.Value), node); break; } default: From 210c443b30c4f647cfe7f649b392481b1a0c39bc Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Jun 2024 09:48:53 +0200 Subject: [PATCH 3/3] Cleanup code --- .../MaterialGenerator.Material.cpp | 22 +- Source/Engine/UI/GUI/Special/RadialMenu.cs | 369 +++++++++--------- 2 files changed, 189 insertions(+), 202 deletions(-) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp index a9c983eec..03b27bfa1 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp @@ -579,7 +579,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node); auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node); - value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); + value = writeLocal(ValueType::Float2, String::Format(TEXT("float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); break; } // Cone Gradient @@ -588,7 +588,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) //float gradient = angle - abs(atan2(uv.x,uv.y)); const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); - value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x,{0}.y))"), uv.Value, rotationAngle.Value), node); + value = writeLocal(ValueType::Float, String::Format(TEXT("{1} - abs(atan2({0}.x, {0}.y))"), uv.Value, rotationAngle.Value), node); break; } // Cycle Gradient @@ -596,7 +596,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { //float gradient = 1 - length(uv * 2); const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); - value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); + value = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node); break; } // Falloff and Offset @@ -623,7 +623,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) auto c = writeLocal(ValueType::Float, String::Format(TEXT("cos({0})"), rotationAngle.Value), node); auto s = writeLocal(ValueType::Float, String::Format(TEXT("sin({0})"), rotationAngle.Value), node); - auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y,{1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); + auto a = writeLocal(ValueType::Float2, String::Format(TEXT("1.0 - float2({1} * {0}.x + {2} * {0}.y, {1} * {0}.y - {2} * {0}.x)"), uv.Value, c.Value, s.Value), node); value = writeLocal( ValueType::Float2, String::Format(TEXT ( @@ -632,16 +632,16 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) node); break; } - //Radial Gradient + // Radial Gradient case 48: { //float gradient = clamp(atan2(uv.x,uv.y) - angle,0.0,1.0); const auto uv = tryGetValue(node->GetBox(0), getUVs).AsFloat2(); const auto rotationAngle = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); - value = writeLocal(ValueType::Float, String::Format(TEXT("clamp(atan2({0}.x,{0}.y) - {1},0.0,1.0)"), uv.Value, rotationAngle.Value), node); + value = writeLocal(ValueType::Float, String::Format(TEXT("saturate(atan2({0}.x, {0}.y) - {1})"), uv.Value, rotationAngle.Value), node); break; } - //Ring Gradient + // Ring Gradient case 49: { // Nodes: @@ -662,11 +662,11 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) const auto outerBounds = tryGetValue(node->GetBox(1), node->Values[0].AsFloat).AsFloat(); const auto innerBounds = tryGetValue(node->GetBox(2), node->Values[1].AsFloat).AsFloat(); const auto falloff = tryGetValue(node->GetBox(3), node->Values[2].AsFloat).AsFloat(); - auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2)"), uv.Value), node); - auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((({0} - (1 - ({1} - {2}))) + {2}) / {2}),0,1)"), c.Value, outerBounds.Value, falloff.Value), node); - auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("clamp(((((1-{0}) - (1 - (1-{1}))) + {2}) / {2}),0,1)"), c.Value, innerBounds.Value, falloff.Value), node); + auto c = writeLocal(ValueType::Float, String::Format(TEXT("1 - length({0} * 2.0)"), uv.Value), node); + auto innerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((({0} - (1.0 - ({1} - {2}))) + {2}) / {2}))"), c.Value, outerBounds.Value, falloff.Value), node); + auto outerMask = writeLocal(ValueType::Float, String::Format(TEXT("saturate(((((1.0 - {0}) - (1.0 - (1.0 - {1}))) + {2}) / {2}))"), c.Value, innerBounds.Value, falloff.Value), node); auto mask = writeLocal(ValueType::Float, String::Format(TEXT("{0} * {1}"), innerMask.Value, outerMask.Value), node); - value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0},{1},{2})"), innerMask.Value, outerMask.Value, mask.Value), node); + value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node); break; } default: diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs index 1c7d8c174..2ffe8006a 100644 --- a/Source/Engine/UI/GUI/Special/RadialMenu.cs +++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs @@ -1,132 +1,134 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace FlaxEngine.GUI { /// - /// + /// Radial menu control that arranges child controls (of type Image) in a circle. /// /// public class RadialMenu : ContainerControl { - [NoSerialize] private bool IsDirty = true; - [NoSerialize] private float m_Angle; - [NoSerialize] private float m_SelectedSegment; - [NoSerialize] private int m_F_SelectedSegment = -1; + private bool _materialIsDirty = true; + private float _angle; + private float _selectedSegment; + private int _highlightSegment = -1; + private MaterialBase _material; + private MaterialInstance _materialInstance; + private int _segmentCount; + private Color _highlightColor; + private Color _foregroundColor; + private Color _selectionColor; + private float _edgeOffset; + private float _thickness = 0.2f; - private MaterialInstance MaterialInstance; - private sbyte m_SegmentCount; - private Color highlightColor; - private Color forgraundColor; - private Color selectionColor; - private float m_EdgeOffset; - private float m_Thickness = 0.0f; private float USize => Size.X < Size.Y ? Size.X : Size.Y; - private bool ShowMatProp => MaterialInstance != null; - private MaterialBase material; + private bool ShowMatProp => _material != null; /// - /// The material + /// The material to use for menu background drawing. /// [EditorOrder(1)] public MaterialBase Material { - get => material; + get => _material; set { - material = value; - if (material == null) - { - FlaxEngine.Object.DestroyNow(MaterialInstance); - MaterialInstance = null; - } - else - { - IsDirty = true; - } + if (_material == value) + return; + _material = value; + Object.DestroyNow(_materialInstance); + _materialInstance = null; + _materialIsDirty = true; } } /// /// Gets or sets the edge offset. /// - /// - /// The edge offset. - /// [EditorOrder(2), Range(0, 1)] public float EdgeOffset { - get - { - return m_EdgeOffset; - } + get => _edgeOffset; set { - m_EdgeOffset = Math.Clamp(value, 0, 1); - IsDirty = true; - this.PerformLayout(); + _edgeOffset = Math.Clamp(value, 0, 1); + _materialIsDirty = true; + PerformLayout(); } } + /// /// Gets or sets the thickness. /// - /// - /// The thickness. - /// - [EditorOrder(3), Range(0, 1), VisibleIf("ShowMatProp")] + [EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))] public float Thickness { - get - { - return m_Thickness; - } + get => _thickness; set { - m_Thickness = Math.Clamp(value, 0, 1); - IsDirty = true; - this.PerformLayout(); + _thickness = Math.Clamp(value, 0, 1); + _materialIsDirty = true; + PerformLayout(); } } + /// - /// Gets or sets control background color (transparent color (alpha=0) means no background rendering) + /// Gets or sets control background color (transparent color (alpha=0) means no background rendering). /// - [VisibleIf("ShowMatProp")] - public new Color BackgroundColor //force overload + [VisibleIf(nameof(ShowMatProp))] + public new Color BackgroundColor { get => base.BackgroundColor; set { - IsDirty = true; + _materialIsDirty = true; base.BackgroundColor = value; } } + /// /// Gets or sets the color of the highlight. /// - /// - /// The color of the highlight. - /// - [VisibleIf("ShowMatProp")] - public Color HighlightColor { get => highlightColor; set { IsDirty = true; highlightColor = value; } } + [VisibleIf(nameof(ShowMatProp))] + public Color HighlightColor + { + get => _highlightColor; + set + { + _materialIsDirty = true; + _highlightColor = value; + } + } + /// - /// Gets or sets the color of the forgraund. + /// Gets or sets the color of the foreground. /// - /// - /// The color of the forgraund. - /// - [VisibleIf("ShowMatProp")] - public Color ForgraundColor { get => forgraundColor; set { IsDirty = true; forgraundColor = value; } } + [VisibleIf(nameof(ShowMatProp))] + public Color ForegroundColor + { + get => _foregroundColor; + set + { + _materialIsDirty = true; + _foregroundColor = value; + } + } + /// /// Gets or sets the color of the selection. /// - /// - /// The color of the selection. - /// - [VisibleIf("ShowMatProp")] - public Color SelectionColor { get => selectionColor; set { IsDirty = true; selectionColor = value; } } + [VisibleIf(nameof(ShowMatProp))] + public Color SelectionColor + { + get => _selectionColor; + set + { + _materialIsDirty = true; + _selectionColor = value; + } + } /// /// The selected callback @@ -137,15 +139,15 @@ namespace FlaxEngine.GUI /// /// The allow change selection when inside /// - [VisibleIf("ShowMatProp")] + [VisibleIf(nameof(ShowMatProp))] public bool AllowChangeSelectionWhenInside; + /// /// The center as button /// - [VisibleIf("ShowMatProp")] + [VisibleIf(nameof(ShowMatProp))] public bool CenterAsButton; - /// /// Initializes a new instance of the class. /// @@ -169,7 +171,7 @@ namespace FlaxEngine.GUI { BackgroundColor = style.BackgroundNormal; HighlightColor = style.BackgroundSelected; - ForgraundColor = style.BackgroundHighlighted; + ForegroundColor = style.BackgroundHighlighted; SelectionColor = style.BackgroundSelected; } } @@ -184,199 +186,148 @@ namespace FlaxEngine.GUI { } - /// - /// Draws the control. - /// + /// public override void DrawSelf() { - if (MaterialInstance != null) + if (_materialInstance == null && Material != null) { - if (IsDirty) - { - MaterialInstance.SetParameterValue("RadialPieMenu_EdgeOffset", Math.Clamp(1 - m_EdgeOffset, 0, 1)); - MaterialInstance.SetParameterValue("RadialPieMenu_Thickness", Math.Clamp(m_Thickness, 0, 1)); - MaterialInstance.SetParameterValue("RadialPieMenu_Angle", ((float)1 / m_SegmentCount) * Mathf.Pi); - MaterialInstance.SetParameterValue("RadialPieMenu_SCount", m_SegmentCount); - - MaterialInstance.SetParameterValue("RadialPieMenu_HighlightColor", HighlightColor); - MaterialInstance.SetParameterValue("RadialPieMenu_ForgraundColor", ForgraundColor); - MaterialInstance.SetParameterValue("RadialPieMenu_BackgroundColor", BackgroundColor); - MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); - UpdateFSS(); - IsDirty = false; - } - Render2D.DrawMaterial(MaterialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y))); + _materialInstance = Material.CreateVirtualInstance(); + _materialIsDirty = true; } - else + if (_materialInstance != null) { - if (Material != null) + if (_materialIsDirty) { - MaterialInstance = Material.CreateVirtualInstance(); + _materialInstance.SetParameterValue("RadialMenu_EdgeOffset", Math.Clamp(1 - _edgeOffset, 0, 1)); + _materialInstance.SetParameterValue("RadialMenu_Thickness", Math.Clamp(_thickness, 0, 1)); + _materialInstance.SetParameterValue("RadialMenu_Angle", (1.0f / _segmentCount) * Mathf.Pi); + _materialInstance.SetParameterValue("RadialMenu_SegmentCount", _segmentCount); + _materialInstance.SetParameterValue("RadialMenu_HighlightColor", _highlightColor); + _materialInstance.SetParameterValue("RadialMenu_ForegroundColor", _foregroundColor); + _materialInstance.SetParameterValue("RadialMenu_BackgroundColor", BackgroundColor); + _materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi); + UpdateSelectionColor(); + _materialIsDirty = false; } + Render2D.DrawMaterial(_materialInstance, new Rectangle(Float2.Zero, new Float2(Size.X < Size.Y ? Size.X : Size.Y))); } } + /// public override void OnMouseMove(Float2 location) { - if (MaterialInstance != null) + if (_materialInstance != null) { - if (m_F_SelectedSegment == -1) + if (_highlightSegment == -1) { - var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; - var max = (1 - m_EdgeOffset) * USize * 0.5f; + var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f; + var max = (1 - _edgeOffset) * USize * 0.5f; var val = ((USize * 0.5f) - location).Length; if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside) { - var size = new Float2(USize); - var p = (size * 0.5f) - location; - var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi; - m_Angle = Mathf.Atan2(p.X, p.Y); - m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa; - m_SelectedSegment = m_Angle; - m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa); - if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle)) - m_Angle = 0; - MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); + UpdateAngle(ref location); } } } + base.OnMouseMove(location); } + /// public override bool OnMouseDown(Float2 location, MouseButton button) { - if (MaterialInstance == null) + if (_materialInstance == null) return base.OnMouseDown(location, button); - var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; - var max = (1 - m_EdgeOffset) * USize * 0.5f; + var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f; + var max = (1 - _edgeOffset) * USize * 0.5f; var val = ((USize * 0.5f) - location).Length; var c = val < min && CenterAsButton; - var selected = (int)m_SelectedSegment; + var selected = (int)_selectedSegment; selected++; if (Mathf.IsInRange(val, min, max) || c) { - if (c) - { - m_F_SelectedSegment = 0; - } - else - { - m_F_SelectedSegment = selected; - } - UpdateFSS(); + _highlightSegment = c ? 0 : selected; + UpdateSelectionColor(); return true; } else { - m_F_SelectedSegment = -1; - UpdateFSS(); + _highlightSegment = -1; + UpdateSelectionColor(); } + return base.OnMouseDown(location, button); } + /// public override bool OnMouseUp(Float2 location, MouseButton button) { - if (MaterialInstance == null) + if (_materialInstance == null) return base.OnMouseDown(location, button); - if (m_F_SelectedSegment >= 0) + if (_highlightSegment >= 0) { - if (Selected != null) - { - Selected(m_F_SelectedSegment); - } - m_F_SelectedSegment = -1; - UpdateFSS(); - var min = ((1 - m_EdgeOffset) - m_Thickness) * USize * 0.5f; - var max = (1 - m_EdgeOffset) * USize * 0.5f; + Selected?.Invoke(_highlightSegment); + _highlightSegment = -1; + UpdateSelectionColor(); + var min = ((1 - _edgeOffset) - _thickness) * USize * 0.5f; + var max = (1 - _edgeOffset) * USize * 0.5f; var val = ((USize * 0.5f) - location).Length; if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside) { - var size = new Float2(USize); - var p = (size * 0.5f) - location; - var Sa = ((float)1 / m_SegmentCount) * Mathf.TwoPi; - m_Angle = Mathf.Atan2(p.X, p.Y); - m_Angle = Mathf.Ceil((m_Angle - (Sa * 0.5f)) / Sa) * Sa; - m_SelectedSegment = m_Angle; - m_SelectedSegment = Mathf.RoundToInt((m_SelectedSegment < 0 ? Mathf.TwoPi + m_SelectedSegment : m_SelectedSegment) / Sa); - if (float.IsNaN(m_Angle) || float.IsInfinity(m_Angle)) - m_Angle = 0; - MaterialInstance.SetParameterValue("RadialPieMenu_Rotation", -m_Angle + Mathf.Pi); + UpdateAngle(ref location); } } + return base.OnMouseUp(location, button); } - private void UpdateFSS() - { - if (m_F_SelectedSegment == -1) - { - MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor); - } - else - { - if (CenterAsButton) - { - if (m_F_SelectedSegment > 0) - { - MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor); - } - else - { - MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", ForgraundColor); - } - } - else - { - MaterialInstance.SetParameterValue("RadialPieMenu_SelectionColor", SelectionColor); - } - } - } + /// public override void OnMouseLeave() { - if (MaterialInstance == null) + if (_materialInstance == null) return; - m_SelectedSegment = 0; - m_F_SelectedSegment = -1; - if (Selected != null) - { - Selected(m_F_SelectedSegment); - } - UpdateFSS(); + _selectedSegment = 0; + _highlightSegment = -1; + Selected?.Invoke(_highlightSegment); + UpdateSelectionColor(); } + /// public override void OnChildrenChanged() { - m_SegmentCount = 0; + _segmentCount = 0; for (int i = 0; i < Children.Count; i++) { if (Children[i] is Image) { - m_SegmentCount++; + _segmentCount++; } } - IsDirty = true; + _materialIsDirty = true; + base.OnChildrenChanged(); } + /// public override void PerformLayout(bool force = false) { - var Sa = -1.0f / m_SegmentCount * Mathf.TwoPi; + var sa = -1.0f / _segmentCount * Mathf.TwoPi; var midp = USize * 0.5f; - var mp = ((1 - m_EdgeOffset) - (m_Thickness * 0.5f)) * midp; + var mp = ((1 - _edgeOffset) - (_thickness * 0.5f)) * midp; float f = 0; - if (m_SegmentCount % 2 != 0) + if (_segmentCount % 2 != 0) { - f += Sa * 0.5f; + f += sa * 0.5f; } - if (MaterialInstance == null) + if (_materialInstance == null) { for (int i = 0; i < Children.Count; i++) { Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp; - f += Sa; + f += sa; } } else @@ -386,17 +337,53 @@ namespace FlaxEngine.GUI if (Children[i] is Image) { Children[i].Center = Rotate2D(new Float2(0, mp), f) + midp; - f += Sa; + f += sa; } } } base.PerformLayout(force); } - private Float2 Rotate2D(Float2 point, float angle) + + private void UpdateSelectionColor() + { + Color color; + if (_highlightSegment == -1) + { + color = _foregroundColor; + } + else + { + if (CenterAsButton) + { + color = _highlightSegment > 0 ? SelectionColor : _foregroundColor; + } + else + { + color = SelectionColor; + } + } + _materialInstance.SetParameterValue("RadialMenu_SelectionColor", color); + } + + private void UpdateAngle(ref Float2 location) + { + var size = new Float2(USize); + var p = (size * 0.5f) - location; + var sa = (1.0f / _segmentCount) * Mathf.TwoPi; + _angle = Mathf.Atan2(p.X, p.Y); + _angle = Mathf.Ceil((_angle - (sa * 0.5f)) / sa) * sa; + _selectedSegment = _angle; + _selectedSegment = Mathf.RoundToInt((_selectedSegment < 0 ? Mathf.TwoPi + _selectedSegment : _selectedSegment) / sa); + if (float.IsNaN(_angle) || float.IsInfinity(_angle)) + _angle = 0; + _materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi); + } + + private static Float2 Rotate2D(Float2 point, float angle) { return new Float2(Mathf.Cos(angle) * point.X + Mathf.Sin(angle) * point.Y, - Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X); + Mathf.Cos(angle) * point.Y - Mathf.Sin(angle) * point.X); } } }