rds1983 / Myra

UI Library for MonoGame, FNA and Stride
MIT License
703 stars 94 forks source link

Added feature for focus neighbours to simplify button navigation #450

Open aardv4rk opened 5 months ago

aardv4rk commented 5 months ago

Not sure if this is something people inherently want or if only I'd find some use, but alas:

This can hook into controller/keyboard using an input middleground, An example use case might be,

If you have a vertical list of buttons setup like so: protected List<ButtonBase2> _listOfButtons = []; in an abstract base class, (using the ButtonBase2 abstract class as the list type to account for buttons or toggle buttons). The list would get populated whenever you add a button to the desktop. Then the following code to hook up the vertical navigation in the buttons.

foreach (var button in _listOfButtons)
{
    var x = index;
    var next = x < _listOfButtons.Count - 1 ? x + 1 : 0;
    var previous = x > 0 ? x - 1 : _listOfButtons.Count - 1;
    button.FocusNeighbourTop = _listOfButtons[previous];
    button.FocusNeighbourRight = button;
    button.FocusNeighbourLeft = button;
    button.FocusNeighbourBottom = _listOfButtons[next];
    // Wraparound
    if (x == 0)
    {
        button.FocusNeighbourTop = _listOfButtons[_listOfButtons.Count - 1];
    }
    else if
    {
        (x == _listOfButtons.Count - 1) button.FocusNeighbourBottom = _listOfButtons[0];
    }
    // Increment index
    index++;
}

and later,

protected void MoveSelectionDown(bool forceSelection = false)
{
   _lastButton = _lastButton?.FocusNeighbourBottom;
   _lastButton?.SetKeyboardFocus();
   _currentSelection += 1;
   if (_currentSelection > _numberOfButtons)
   {
       _currentSelection = 0;
   }
   // Optionally force select
   if (forceSelection)
   { 
       ProcessSelection();
    }
}

public void ProcessSelection()
{
    if (_listOfButtons.Count > 0)
    {
        _lastButton?.DoClick();
    }
}

private void UpdateSelection()
{
    if (_listOfButtons.Count > 0)
    {
        _lastButton?.SetKeyboardFocus();
    }
}

This can also be done in other cases; in a horizontal list you could just hook into the FocusNeighbourLeft and FocusNeighbourRight. The next/previous bit could be used if you have something akin to tab/shift tab to focus whatever's after or before. The code above would work the same if you had a controller or a keyboard.

It's something I'm personally using to create a focus flow for easier navigation.