From f1d5c0257feef5bc0d1023a5a4457dc54bf44d91 Mon Sep 17 00:00:00 2001 From: Saas Date: Mon, 2 Feb 2026 12:50:24 +0100 Subject: [PATCH 1/3] improve radial menu control - Add AllowChangeSelectionWhenOutside to allow changing the selection from the outside the menu - Expose UpdateAngle() to get *some* kind of controller support - Expose `SelectedSegment` - Improve various doc comments --- Source/Engine/UI/GUI/Special/RadialMenu.cs | 77 +++++++++++++++------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs index 060c4ef90..f8309fb1e 100644 --- a/Source/Engine/UI/GUI/Special/RadialMenu.cs +++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs @@ -5,7 +5,7 @@ using System; namespace FlaxEngine.GUI { /// - /// Radial menu control that arranges child controls (of type Image) in a circle. + /// Radial menu control that arranges child controls (of type ) in a circle. /// /// public class RadialMenu : ContainerControl @@ -27,7 +27,7 @@ namespace FlaxEngine.GUI private bool ShowMatProp => _material != null; /// - /// The material to use for menu background drawing. + /// The material used for menu background drawing. /// [EditorOrder(1)] public MaterialBase Material @@ -44,7 +44,7 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the edge offset. + /// Gets or sets the offset of the outer edge from the bounds of the Control. /// [EditorOrder(2), Range(0, 1)] public float EdgeOffset @@ -59,7 +59,7 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the thickness. + /// Gets or sets the thickness of the menu. /// [EditorOrder(3), Range(0, 1), VisibleIf(nameof(ShowMatProp))] public float Thickness @@ -74,7 +74,7 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets control background color (transparent color (alpha=0) means no background rendering). + /// Gets or sets control background color (transparent color means no background rendering). /// [VisibleIf(nameof(ShowMatProp))] public new Color BackgroundColor @@ -88,7 +88,7 @@ namespace FlaxEngine.GUI } /// - /// Gets or sets the color of the highlight. + /// Gets or sets the color of the outer edge highlight. /// [VisibleIf(nameof(ShowMatProp))] public Color HighlightColor @@ -130,19 +130,37 @@ namespace FlaxEngine.GUI } /// - /// The selected callback + /// The selected callback. /// [HideInEditor] public Action Selected; /// - /// The allow change selection when inside + /// Invoked when the hovered segment is changed. + /// + [HideInEditor] + public Action HoveredSelectionChanged; + + /// + /// The selected segment. + /// + [HideInEditor] + public float SelectedSegment => _selectedSegment; + + /// + /// Allows the selected to change when the mouse is moved in the empty center of the menu. /// [VisibleIf(nameof(ShowMatProp))] public bool AllowChangeSelectionWhenInside; /// - /// The center as button + /// Allows the selected to change when the mouse is moved outside of the menu. + /// + [VisibleIf(nameof(ShowMatProp))] + public bool AllowChangeSelectionWhenOutside; + + /// + /// Wether the center is a button. /// [VisibleIf(nameof(ShowMatProp))] public bool CenterAsButton; @@ -225,7 +243,7 @@ namespace FlaxEngine.GUI 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) + if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside) { UpdateAngle(ref location); } @@ -276,7 +294,7 @@ namespace FlaxEngine.GUI 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) + if (Mathf.IsInRange(val, min, max) || val < min && AllowChangeSelectionWhenInside || val > max && AllowChangeSelectionWhenOutside) { UpdateAngle(ref location); } @@ -347,6 +365,29 @@ namespace FlaxEngine.GUI base.PerformLayout(force); } + /// + /// Updates the current angle and selected segment of the radial menu based on the specified location inside of the control. + /// + /// The position used to determine the angle and segment selection within the radial menu. + public void UpdateAngle(ref Float2 location) + { + float previousSelectedSegment = _selectedSegment; + + 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); + + if (previousSelectedSegment != _selectedSegment) + HoveredSelectionChanged?.Invoke((int)_selectedSegment); + } + private void UpdateSelectionColor() { Color color; @@ -368,20 +409,6 @@ namespace FlaxEngine.GUI _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, From b0a0c5b66c3f783a5bf6de391ef26725b0a4806d Mon Sep 17 00:00:00 2001 From: Saas Date: Mon, 2 Feb 2026 12:52:54 +0100 Subject: [PATCH 2/3] change selectedSegment to int --- Source/Engine/UI/GUI/Special/RadialMenu.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs index f8309fb1e..3e752aa46 100644 --- a/Source/Engine/UI/GUI/Special/RadialMenu.cs +++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs @@ -12,7 +12,7 @@ namespace FlaxEngine.GUI { private bool _materialIsDirty = true; private float _angle; - private float _selectedSegment; + private int _selectedSegment; private int _highlightSegment = -1; private MaterialBase _material; private MaterialInstance _materialInstance; @@ -145,7 +145,7 @@ namespace FlaxEngine.GUI /// The selected segment. /// [HideInEditor] - public float SelectedSegment => _selectedSegment; + public int SelectedSegment => _selectedSegment; /// /// Allows the selected to change when the mouse is moved in the empty center of the menu. @@ -378,8 +378,7 @@ namespace FlaxEngine.GUI 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); + _selectedSegment = Mathf.RoundToInt((_angle < 0 ? Mathf.TwoPi + _angle : _angle) / sa); if (float.IsNaN(_angle) || float.IsInfinity(_angle)) _angle = 0; _materialInstance.SetParameterValue("RadialMenu_Rotation", -_angle + Mathf.Pi); From b27e55636302ad5eaf1c4cad1027b7ced01c0fa5 Mon Sep 17 00:00:00 2001 From: Saas Date: Mon, 2 Feb 2026 21:35:40 +0100 Subject: [PATCH 3/3] expose material instance --- Source/Engine/UI/GUI/Special/RadialMenu.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Engine/UI/GUI/Special/RadialMenu.cs b/Source/Engine/UI/GUI/Special/RadialMenu.cs index 3e752aa46..994d81ab2 100644 --- a/Source/Engine/UI/GUI/Special/RadialMenu.cs +++ b/Source/Engine/UI/GUI/Special/RadialMenu.cs @@ -129,6 +129,12 @@ namespace FlaxEngine.GUI } } + /// + /// The material instance of used to draw the menu. + /// + [HideInEditor] + public MaterialInstance MaterialInstance => _materialInstance; + /// /// The selected callback. ///