Feature/make radial menu great again (#32653)
* it works! kinda * so it works now * minor cleanup * central button now is useful too * more cleanup * minor cleanup * more cleanup * refactor: migrated code from toolbox (as it was rejected as too specific) * feat: moved border drawing for radial menu into RadialMenuTextureButton. Radial menu position setting into was moved to OverrideArrange to not being called on every frame * refactor: major reworks! * renamed DrawBagleSector to DrawAnnulusSector * Remove strange indexing * Regularize math * refactor: re-orienting segment elements to be Y-mirrored * refactor: extracted radial menu radius multiplier property, changed color pallet for radial menu button * refactor: removed icon backgrounds on textures used in current radial menu buttons with sectors, RadialContainer Radius renamed and now actually changed control radius. * refactor: in RadialMenuTextureButtonWithSector all sector colors are converted to and from sRGB in property getter-setters * refactor: renamed srgb to include Srgb suffix so devs gonna see that its srgb clearly * fix: enabled any functional keys pressed when pushing radial menu buttons * fix: radial menu sector now scales with UIScale * fix: accept only one event when clicking on radial menu ContextualButton * fix: now radial menu buttons accepts only click/alt-click, now clicks outside menu closes menu always --------- Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru> Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
@@ -8,6 +7,11 @@ namespace Content.Client.UserInterface.Controls;
|
||||
[Virtual]
|
||||
public class RadialContainer : LayoutContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Increment of radius per child element to be rendered.
|
||||
/// </summary>
|
||||
private const float RadiusIncrement = 5f;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the anglular range, in radians, in which child elements will be placed.
|
||||
/// The first value denotes the angle at which the first element is to be placed, and
|
||||
@@ -49,10 +53,30 @@ public class RadialContainer : LayoutContainer
|
||||
public RAlignment RadialAlignment { get; set; } = RAlignment.Clockwise;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how far from the radial container's center that its child elements will be placed
|
||||
/// Radial menu radius determines how far from the radial container's center its child elements will be placed.
|
||||
/// To correctly display dynamic amount of elements control actually resizes depending on amount of child buttons,
|
||||
/// but uses this property as base value for final radius calculation.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Radius { get; set; } = 100f;
|
||||
public float InitialRadius { get; set; } = 100f;
|
||||
|
||||
/// <summary>
|
||||
/// Radial menu radius determines how far from the radial container's center its child elements will be placed.
|
||||
/// This is dynamically calculated (based on child button count) radius, result of <see cref="InitialRadius"/> and
|
||||
/// <see cref="RadiusIncrement"/> multiplied by currently visible child button count.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public float CalculatedRadius { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines radial menu button sectors inner radius, is a multiplier of <see cref="InitialRadius"/>.
|
||||
/// </summary>
|
||||
public float InnerRadiusMultiplier { get; set; } = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Determines radial menu button sectors outer radius, is a multiplier of <see cref="InitialRadius"/>.
|
||||
/// </summary>
|
||||
public float OuterRadiusMultiplier { get; set; } = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether the container should reserve a space on the layout for child which are not currently visible
|
||||
@@ -67,37 +91,74 @@ public class RadialContainer : LayoutContainer
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
|
||||
const float baseRadius = 100f;
|
||||
const float radiusIncrement = 5f;
|
||||
|
||||
var children = ReserveSpaceForHiddenChildren ? Children : Children.Where(x => x.Visible);
|
||||
var children = ReserveSpaceForHiddenChildren
|
||||
? Children
|
||||
: Children.Where(x => x.Visible);
|
||||
|
||||
var childCount = children.Count();
|
||||
|
||||
// Add padding from the center at higher child counts so they don't overlap.
|
||||
Radius = baseRadius + (childCount * radiusIncrement);
|
||||
|
||||
// Add padding from the center at higher child counts so they don't overlap.
|
||||
CalculatedRadius = InitialRadius + (childCount * RadiusIncrement);
|
||||
|
||||
var isAntiClockwise = RadialAlignment == RAlignment.AntiClockwise;
|
||||
|
||||
// Determine the size of the arc, accounting for clockwise and anti-clockwise arrangements
|
||||
var arc = AngularRange.Y - AngularRange.X;
|
||||
arc = (arc < 0) ? MathF.Tau + arc : arc;
|
||||
arc = (RadialAlignment == RAlignment.AntiClockwise) ? MathF.Tau - arc : arc;
|
||||
arc = arc < 0
|
||||
? MathF.Tau + arc
|
||||
: arc;
|
||||
arc = isAntiClockwise
|
||||
? MathF.Tau - arc
|
||||
: arc;
|
||||
|
||||
// Account for both circular arrangements and arc-based arrangements
|
||||
var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f) ? 0 : 1;
|
||||
var childMod = MathHelper.CloseTo(arc, MathF.Tau, 0.01f)
|
||||
? 0
|
||||
: 1;
|
||||
|
||||
// Determine the separation between child elements
|
||||
var sepAngle = arc / (childCount - childMod);
|
||||
sepAngle *= (RadialAlignment == RAlignment.AntiClockwise) ? -1f : 1f;
|
||||
sepAngle *= isAntiClockwise
|
||||
? -1f
|
||||
: 1f;
|
||||
|
||||
var controlCenter = finalSize * 0.5f;
|
||||
|
||||
// Adjust the positions of all the child elements
|
||||
foreach (var (i, child) in children.Select((x, i) => (i, x)))
|
||||
var query = children.Select((x, index) => (index, x));
|
||||
foreach (var (childIndex, child) in query)
|
||||
{
|
||||
var position = new Vector2(Radius * MathF.Sin(AngularRange.X + sepAngle * i) + Width / 2f - child.Width / 2f, -Radius * MathF.Cos(AngularRange.X + sepAngle * i) + Height / 2f - child.Height / 2f);
|
||||
const float angleOffset = MathF.PI * 0.5f;
|
||||
|
||||
var targetAngleOfChild = AngularRange.X + sepAngle * (childIndex + 0.5f) + angleOffset;
|
||||
|
||||
// flooring values for snapping float values to physical grid -
|
||||
// it prevents gaps and overlapping between different button segments
|
||||
var position = new Vector2(
|
||||
MathF.Floor(CalculatedRadius * MathF.Cos(targetAngleOfChild)),
|
||||
MathF.Floor(-CalculatedRadius * MathF.Sin(targetAngleOfChild))
|
||||
) + controlCenter - child.DesiredSize * 0.5f + Position;
|
||||
|
||||
SetPosition(child, position);
|
||||
|
||||
// radial menu buttons with sector need to also know in which sector and around which point
|
||||
// they should be rendered, how much space sector should should take etc.
|
||||
if (child is IRadialMenuItemWithSector tb)
|
||||
{
|
||||
tb.AngleSectorFrom = sepAngle * childIndex;
|
||||
tb.AngleSectorTo = sepAngle * (childIndex + 1);
|
||||
tb.AngleOffset = angleOffset;
|
||||
tb.InnerRadius = CalculatedRadius * InnerRadiusMultiplier;
|
||||
tb.OuterRadius = CalculatedRadius * OuterRadiusMultiplier;
|
||||
tb.ParentCenter = controlCenter;
|
||||
}
|
||||
}
|
||||
|
||||
return base.ArrangeOverride(finalSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,4 +170,5 @@ public class RadialContainer : LayoutContainer
|
||||
Clockwise,
|
||||
AntiClockwise,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user