Skip to content
23 changes: 13 additions & 10 deletions Silksong.ModMenu/Elements/AbstractGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,19 @@ protected AbstractGroup()
protected abstract IEnumerable<INavigable> GetNavigables(NavigationDirection direction);

/// <inheritdoc/>
public virtual void ClearNeighbor(NavigationDirection direction)
public virtual void ClearNeighbors(NavigationDirection direction)
{
foreach (var navigable in GetNavigables(direction))
navigable.ClearNeighbor(direction);
navigable.ClearNeighbors(direction);
}

/// <inheritdoc/>
public virtual void ClearNeighbors()
{
ClearNeighbor(NavigationDirection.Up);
ClearNeighbor(NavigationDirection.Left);
ClearNeighbor(NavigationDirection.Right);
ClearNeighbor(NavigationDirection.Down);
ClearNeighbors(NavigationDirection.Up);
ClearNeighbors(NavigationDirection.Left);
ClearNeighbors(NavigationDirection.Right);
ClearNeighbors(NavigationDirection.Down);
}

/// <inheritdoc/>
Expand All @@ -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> selectables
);

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

/// <inheritdoc/>
public virtual void SetNeighbor(NavigationDirection direction, Selectable selectable)
public virtual void SetNeighbors(
NavigationDirection direction,
IEnumerable<Selectable> selectables
)
{
foreach (var navigable in GetNavigables(direction))
navigable.SetNeighbor(direction, selectable);
navigable.SetNeighbors(direction, selectables);
}

/// <inheritdoc/>
Expand Down
11 changes: 5 additions & 6 deletions Silksong.ModMenu/Elements/FreeGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,14 @@ 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> selectables
)
{
selectable = GetNavigables(direction)
.Select(n => n.GetSelectable(direction, out var s) ? s : null)
.FirstOrDefault();
return selectable != null;
selectables = GetNavigables(direction.Opposite())
.SelectMany(n => n.GetSelectables(direction, out var s) ? s : []);
return selectables.Any();
}

/// <inheritdoc/>
Expand Down
50 changes: 22 additions & 28 deletions Silksong.ModMenu/Elements/GridGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,36 +150,30 @@ 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 : []);
return selectables.Any();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -216,7 +210,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 +229,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 All @@ -250,12 +244,12 @@ static bool ClosestColumn(
prevRow[i] != null
&& ClosestColumn(nextRow, NavigationDirection.Down, i, out var s)
)
prevRow[i]!.SetNeighborDown(s);
prevRow[i]!.SetNeighborsDown(s);
if (
nextRow[i] != null
&& ClosestColumn(prevRow, NavigationDirection.Up, i, out s)
)
nextRow[i]!.SetNeighborUp(s);
nextRow[i]!.SetNeighborsUp(s);
}
}
prevRow = nextRow;
Expand Down
20 changes: 11 additions & 9 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 neighbors of this entity to one or more of the given choices.
/// </summary>
/// <returns>False if this entity has no navigation to connect.</returns>
void SetNeighbor(NavigationDirection direction, Selectable selectable);
void SetNeighbors(NavigationDirection direction, IEnumerable<Selectable> selectables);

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

/// <summary>
/// Get the Selectable to target if navigating to this element along 'direction'.
/// Get a set of choices for the Selectables 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.SetNeighbors(dir, selectables);
/// </summary>
bool GetSelectable(
bool GetSelectables(
NavigationDirection direction,
[MaybeNullWhen(false)] out Selectable selectable
[MaybeNullWhen(false)] out IEnumerable<Selectable> selectables
);
}
Loading