aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Desktop/PadNavigator.cs
blob: b4b59d527d09136c99c373d957fdd5cdfe06e649 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Avalonia.Media;
using Godosa.Core.Input;

namespace Godosa.Desktop;

/// <summary>Gamepad control of an open window: D-pad moves a highlight through its enabled
/// buttons (wrapping), A presses (the game decides B/Start).</summary>
public sealed class PadNavigator
{
    private static readonly Thickness HighlightThickness = new(2);
    private readonly MenuCursor _cursor = new();
    private readonly List<Button> _buttons = new();
    private Control? _container;
    private Button? _highlighted;
    private readonly IBrush _highlight;

    public PadNavigator(IBrush highlight) => _highlight = highlight;

    /// <summary>The window pad input should drive now, or null (pad drives the character).</summary>
    public Control? Active { get; private set; }

    /// <summary>Highlight only while the gamepad is the device in use (keyboard/mouse users
    /// get no stray gold border).</summary>
    public bool ShowHighlight { get; set; }

    public void Refresh(Control? active)
    {
        Active = active;
        if (!ReferenceEquals(active, _container))
        {
            _container = active;
            _cursor.Clamp(0);
        }
        Collect();
        _cursor.Clamp(_buttons.Count);
        Highlight();
    }

    public void Move(int delta)
    {
        Collect();
        _cursor.Clamp(_buttons.Count);
        _cursor.Move(delta, _buttons.Count);
        Highlight();
    }

    public void Press()
    {
        Collect();
        _cursor.Clamp(_buttons.Count);
        if (_cursor.Index >= 0) Buttons.Press(_buttons[_cursor.Index]);
    }

    private void Collect()
    {
        _buttons.Clear();
        if (_container is null) return;
        _buttons.AddRange(_container.GetVisualDescendants().OfType<Button>()
            .Where(b => b.IsEffectivelyVisible && b.IsEffectivelyEnabled));
    }

    private void Highlight()
    {
        var target = ShowHighlight && _cursor.Index >= 0 && _cursor.Index < _buttons.Count ? _buttons[_cursor.Index] : null;
        if (ReferenceEquals(target, _highlighted)) return;
        if (_highlighted is not null)
        {
            _highlighted.ClearValue(Button.BorderBrushProperty);
            _highlighted.ClearValue(Button.BorderThicknessProperty);
        }
        _highlighted = target;
        if (target is not null)
        {
            target.BorderBrush = _highlight;
            target.BorderThickness = HighlightThickness;
            target.BringIntoView(); // long option pages scroll
        }
    }
}