Skip to content
9 changes: 6 additions & 3 deletions Silksong.ModMenu/Elements/AbstractGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ public virtual void ClearNeighbors()
.FirstOrDefault();

/// <inheritdoc/>
public abstract bool GetSelectable(
public abstract bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectable
Comment thread
kaycodes13 marked this conversation as resolved.
Outdated
);

private GameObject? gameObjectParent;
Expand All @@ -113,7 +113,10 @@ public virtual void ClearGameObjectParent()
}

/// <inheritdoc/>
public virtual void SetNeighbor(NavigationDirection direction, Selectable selectable)
public virtual void SetNeighbor(
Comment thread
kaycodes13 marked this conversation as resolved.
Outdated
NavigationDirection direction,
IEnumerable<Selectable> selectable
)
{
foreach (var navigable in GetNavigables(direction))
navigable.SetNeighbor(direction, selectable);
Expand Down
6 changes: 3 additions & 3 deletions Silksong.ModMenu/Elements/FreeGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ private static float SortKey(NavigationDirection direction, Vector2 pos) =>
};

/// <inheritdoc/>
public override bool GetSelectable(
public override bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectable
)
{
selectable = GetNavigables(direction)
.Select(n => n.GetSelectable(direction, out var s) ? s : null)
.Select(n => n.GetSelectables(direction, out var s) ? s : null)
Comment thread
kaycodes13 marked this conversation as resolved.
Outdated
.FirstOrDefault();
return selectable != null;
}
Expand Down
47 changes: 21 additions & 26 deletions Silksong.ModMenu/Elements/GridGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,36 +150,31 @@ public override void Clear()
}

/// <inheritdoc/>
public override bool GetSelectable(
public override bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
)
{
var navigable = direction switch
var navigables = direction switch
{
// Last element.
NavigationDirection.Up => AllEntities()
.Where(e => e.VisibleSelf)
.OfType<INavigable>()
.LastOrDefault(),
// Rightmost element.
NavigationDirection.Left => GetColumns()
.SelectMany(col => col.WhereNonNull(e => e.VisibleSelf).OfType<INavigable>())
.LastOrDefault(),
// Leftmost element.
NavigationDirection.Right => GetColumns()
.SelectMany(col => col.WhereNonNull(e => e.VisibleSelf).OfType<INavigable>())
.FirstOrDefault(),
// First element.
NavigationDirection.Down => AllEntities()
.Where(e => e.VisibleSelf)
.OfType<INavigable>()
.FirstOrDefault(),
// Bottommost element of every column.
NavigationDirection.Up => GetColumns()
.Select(x => (INavigable?)x.LastOrDefault(e => e is INavigable && e.VisibleSelf))
.WhereNonNull(),
// Rightmost element of every row.
NavigationDirection.Left => GetNavigables(NavigationDirection.Right),
// Leftmost element of every row.
NavigationDirection.Right => GetNavigables(NavigationDirection.Left),
// Topmost element of every column.
NavigationDirection.Down => GetColumns()
.Select(x => (INavigable?)x.FirstOrDefault(e => e is INavigable && e.VisibleSelf))
.WhereNonNull(),
_ => throw new ArgumentException($"{direction}"),
};

selectable = default;
return navigable != null && navigable.GetSelectable(direction, out selectable);
selectables =
navigables?.SelectMany(x => x.GetSelectables(direction, out var s) ? s : []) ?? [];
Comment thread
kaycodes13 marked this conversation as resolved.
Outdated
return navigables != null && selectables.Any();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -216,7 +211,7 @@ static bool ClosestColumn(
INavigable?[] row,
NavigationDirection dir,
int column,
[MaybeNullWhen(false)] out Selectable target
[MaybeNullWhen(false)] out IEnumerable<Selectable> targets
)
{
int offset = 0;
Expand All @@ -235,12 +230,12 @@ static bool ClosestColumn(
if (
idx >= 0
&& idx < row.Length
&& (row[idx]?.GetSelectable(dir, out target) ?? false)
&& (row[idx]?.GetSelectables(dir, out targets) ?? false)
)
return true;
}

target = default;
targets = default;
return false;
}

Expand Down
18 changes: 10 additions & 8 deletions Silksong.ModMenu/Elements/INavigable.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using UnityEngine.UI;

namespace Silksong.ModMenu.Elements;
Expand All @@ -14,25 +16,25 @@ public interface INavigable
void ClearNeighbors();

/// <summary>
/// Set the directional neighbor of this entity.
/// Set the directional neighbor of this entity to one of the given choices.
Comment thread
kaycodes13 marked this conversation as resolved.
Outdated
/// </summary>
/// <returns>False if this entity has no navigation to connect.</returns>
void SetNeighbor(NavigationDirection direction, Selectable selectable);
void SetNeighbor(NavigationDirection direction, IEnumerable<Selectable> selectables);

/// <summary>
/// Unset the given directional neighbor of this entity.
/// </summary>
void ClearNeighbor(NavigationDirection direction);

/// <summary>
/// Get the Selectable to target if navigating to this element along 'direction'.
/// Get a set of choices for the Selectable to target if navigating to this element along 'direction'.
///
/// In other words, typical usage would entail:
/// if (foo.GetSelectable(dir, out var selectable))
/// bar.SetNeighbor(dir, selectable);
/// if (foo.GetSelectables(dir, out var selectables))
/// bar.SetNeighbor(dir, selectables);
/// </summary>
bool GetSelectable(
bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
);
}
95 changes: 68 additions & 27 deletions Silksong.ModMenu/Elements/INavigableExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using UnityEngine.UI;

namespace Silksong.ModMenu.Elements;
Expand All @@ -17,65 +18,95 @@ public static class INavigableExtensions
/// Set the upwards neighbor of this navigable.
/// </summary>
public void SetNeighborUp(Selectable selectable) =>
self.SetNeighbor(NavigationDirection.Up, selectable);
self.SetNeighbor(NavigationDirection.Up, [selectable]);

/// <summary>
/// Set the upwards neighbor of this navigable to one of the given options.
/// </summary>
public void SetNeighborUp(IEnumerable<Selectable> selectables) =>
self.SetNeighbor(NavigationDirection.Up, selectables);

/// <summary>
/// Set the upwards neighbor of this navigable.
/// </summary>
public void SetNeighborUp(SelectableElement selectableElement) =>
self.SetNeighborUp(selectableElement.SelectableComponent);
public void SetNeighborUp(SelectableElement selectableElement)
{
if (selectableElement.GetSelectables(NavigationDirection.Up, out var selectables))
self.SetNeighborDown(selectables);
}

/// <summary>
/// Declare that this navigable has no upwards neighbor.
/// </summary>
public void ClearNeighborUp() => self.ClearNeighbor(NavigationDirection.Up);

/// <summary>
/// Get the most eligible selectable within this navigable when navigating upwards into it.
/// Get the most eligible selectables within this navigable when navigating upwards into it.
/// </summary>
public bool GetNeighborUp([MaybeNullWhen(false)] out Selectable selectable) =>
self.GetSelectable(NavigationDirection.Up, out selectable);
public bool GetNeighborsUp(
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
) => self.GetSelectables(NavigationDirection.Up, out selectables);

/// <summary>
/// Set the leftwards neighbor of this navigable.
/// </summary>
public void SetNeighborLeft(Selectable selectable) =>
self.SetNeighbor(NavigationDirection.Left, selectable);
self.SetNeighbor(NavigationDirection.Left, [selectable]);

/// <summary>
/// Set the leftwards neighbor of this navigable to one of the given options.
/// </summary>
public void SetNeighborLeft(IEnumerable<Selectable> selectables) =>
self.SetNeighbor(NavigationDirection.Left, selectables);

/// <summary>
/// Set the leftwards neighbor of this navigable.
/// </summary>
public void SetNeighborLeft(SelectableElement selectableElement) =>
self.SetNeighborLeft(selectableElement.SelectableComponent);
public void SetNeighborLeft(SelectableElement selectableElement)
{
if (selectableElement.GetSelectables(NavigationDirection.Left, out var selectables))
self.SetNeighborDown(selectables);
}

/// <summary>
/// Declare that this navigable has no upwards neighbor.
/// </summary>
public void ClearNeighborLeft() => self.ClearNeighbor(NavigationDirection.Left);

/// <summary>
/// Get the most eligible selectable within this navigable when navigating leftwards into it.
/// Get the most eligible selectables within this navigable when navigating leftwards into it.
/// </summary>
public bool GetNeighborLeft([MaybeNullWhen(false)] out Selectable selectable) =>
self.GetSelectable(NavigationDirection.Left, out selectable);
public bool GetNeighborsLeft(
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectable
) => self.GetSelectables(NavigationDirection.Left, out selectable);

/// <summary>
/// Set the rightwards neighbor of this navigable.
/// </summary>
public void SetNeighborRight(Selectable selectable) =>
self.SetNeighbor(NavigationDirection.Right, selectable);
self.SetNeighbor(NavigationDirection.Right, [selectable]);

/// <summary>
/// Set the rightwards neighbor of this navigable to one of the given options.
/// </summary>
public void SetNeighborRight(IEnumerable<Selectable> selectables) =>
self.SetNeighbor(NavigationDirection.Right, selectables);

/// <summary>
/// Set the rightwards neighbor of this navigable.
/// </summary>
public void SetNeighborRight(SelectableElement selectableElement) =>
self.SetNeighborRight(selectableElement.SelectableComponent);
public void SetNeighborRight(SelectableElement selectableElement)
{
if (selectableElement.GetSelectables(NavigationDirection.Right, out var selectables))
self.SetNeighborDown(selectables);
}

/// <summary>
/// Get the most eligible selectable within this navigable when navigating rightwards into it.
/// Get the most eligible selectables within this navigable when navigating rightwards into it.
/// </summary>
public bool GetNeighborRight([MaybeNullWhen(false)] out Selectable selectable) =>
self.GetSelectable(NavigationDirection.Right, out selectable);
public bool GetNeighborsRight(
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
) => self.GetSelectables(NavigationDirection.Right, out selectables);

/// <summary>
/// Declare that this navigable has no upwards neighbor.
Expand All @@ -86,33 +117,43 @@ public bool GetNeighborRight([MaybeNullWhen(false)] out Selectable selectable) =
/// Set the downwards neighbor of this navigable.
/// </summary>
public void SetNeighborDown(Selectable selectable) =>
self.SetNeighbor(NavigationDirection.Down, selectable);
self.SetNeighbor(NavigationDirection.Down, [selectable]);

/// <summary>
/// Set the downwards neighbor of this navigable to one of the given options.
/// </summary>
public void SetNeighborDown(IEnumerable<Selectable> selectables) =>
self.SetNeighbor(NavigationDirection.Down, selectables);

/// <summary>
/// Set the downwards neighbor of this navigable.
/// </summary>
public void SetNeighborDown(SelectableElement selectableElement) =>
self.SetNeighborDown(selectableElement.SelectableComponent);
public void SetNeighborDown(SelectableElement selectableElement)
{
if (selectableElement.GetSelectables(NavigationDirection.Down, out var selectables))
self.SetNeighborDown(selectables);
}

/// <summary>
/// Declare that this navigable has no upwards neighbor.
/// </summary>
public void ClearNeighborDown() => self.ClearNeighbor(NavigationDirection.Down);

/// <summary>
/// Get the most eligible selectable within this navigable when navigating downwards into it.
/// Get the most eligible selectables within this navigable when navigating downwards into it.
/// </summary>
public bool GetNeighborDown([MaybeNullWhen(false)] out Selectable selectable) =>
self.GetSelectable(NavigationDirection.Down, out selectable);
public bool GetNeighborsDown(
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
) => self.GetSelectables(NavigationDirection.Down, out selectables);

/// <summary>
/// Symmetrically connect two INavigables.
/// </summary>
public void ConnectSymmetric(INavigable dest, NavigationDirection direction)
{
if (dest.GetSelectable(direction, out var s))
if (dest.GetSelectables(direction, out var s))
self.SetNeighbor(direction, s);
if (self.GetSelectable(direction.Opposite(), out s))
if (self.GetSelectables(direction.Opposite(), out s))
dest.SetNeighbor(direction.Opposite(), s);
}
}
Expand Down
12 changes: 6 additions & 6 deletions Silksong.ModMenu/Elements/ScrollingPane.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,20 +288,20 @@ Func<Transform, bool> shouldSkip
public void ClearNeighbors() => Content?.ClearNeighbors();

/// <inheritdoc/>
public void SetNeighbor(NavigationDirection direction, Selectable selectable) =>
Content?.SetNeighbor(direction, selectable);
public void SetNeighbor(NavigationDirection direction, IEnumerable<Selectable> selectables) =>
Content?.SetNeighbor(direction, selectables);

/// <inheritdoc/>
public void ClearNeighbor(NavigationDirection direction) => Content?.ClearNeighbor(direction);

/// <inheritdoc/>
public bool GetSelectable(
public bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
)
{
selectable = default;
return Content?.GetSelectable(direction, out selectable) ?? false;
selectables = default;
return Content?.GetSelectables(direction, out selectables) ?? false;
}

#endregion
Expand Down
11 changes: 6 additions & 5 deletions Silksong.ModMenu/Elements/SelectableElement.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Silksong.ModMenu.Internal;
using Silksong.ModMenu.Util;
Expand Down Expand Up @@ -75,16 +76,16 @@ public void ClearNeighbor(NavigationDirection direction) =>
new SelectableWrapper(SelectableComponent).ClearNeighbor(direction);

/// <inheritdoc/>
public void SetNeighbor(NavigationDirection direction, Selectable selectable) =>
new SelectableWrapper(SelectableComponent).SetNeighbor(direction, selectable);
public void SetNeighbor(NavigationDirection direction, IEnumerable<Selectable> selectables) =>
new SelectableWrapper(SelectableComponent).SetNeighbor(direction, selectables);

/// <inheritdoc/>
public bool GetSelectable(
public bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
)
{
selectable = SelectableComponent;
selectables = [SelectableComponent];
return true;
}

Expand Down
Loading