• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 30113831081

24 Jul 2026 05:39PM UTC coverage: 64.224% (-0.07%) from 64.291%
30113831081

push

github

tyevco
fix(audio): Fix non-looping replay, WAV validation, paused-source recycling, double master volume (#1185,#1186,#1188,#1189)

- #1185: AudioSourceSystem now restarts a stopped backend source when State is set back to Playing (distinguishes a replay request from a natural finish via CurrentSound validity), so non-looping sources are replayable per the AudioSource.State contract.
- #1186: WavDecoder validates each chunk size against the remaining bytes and throws AudioLoadException on negative/oversized sizes, preventing the ArgumentOutOfRangeException slice crash and the negative-size infinite loop on untrusted files.
- #1188: SourcePool.Update only recycles Stopped sources (Paused treated as still-active), so Pause/PauseAll no longer kills pooled one-shots on the next frame.
- #1189: SilkAudioContext applies master + Master-channel volume only via the listener gain; ComputeEffectiveVolume no longer re-multiplies them into the per-source gain, eliminating the squared master volume and the new-vs-playing inconsistency.

Adds KeenEyes.Audio.Tests (AudioSourceSystem replay/finish, via fake IAudioDevice/IAudioContext) and KeenEyes.Audio.Silk.Tests (WavDecoder malformed-input validation). #1188/#1189 are OpenAL-device-bound and verified by code trace.

Closes #1185
Closes #1186
Closes #1188
Closes #1189

8612 of 12731 branches covered (67.65%)

Branch coverage included in aggregate %.

10 of 15 new or added lines in 4 files covered. (66.67%)

368 existing lines in 26 files now uncovered.

51312 of 80574 relevant lines covered (63.68%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.91
/src/KeenEyes.UI/Systems/UIFocusSystem.cs
1
using KeenEyes.Input.Abstractions;
2
using KeenEyes.UI.Abstractions;
3

4
namespace KeenEyes.UI;
5

6
/// <summary>
7
/// System that handles keyboard navigation between focusable UI elements.
8
/// </summary>
9
/// <remarks>
10
/// <para>
11
/// The focus system processes Tab and Shift+Tab key presses to navigate between
12
/// <see cref="UIInteractable"/> elements with <see cref="UIInteractable.CanFocus"/> enabled.
13
/// </para>
14
/// <para>
15
/// Focus order is determined by <see cref="UIInteractable.TabIndex"/> - lower values
16
/// are focused first. Elements with the same TabIndex are ordered by their position
17
/// in the hierarchy.
18
/// </para>
19
/// <para>
20
/// This system should run in <see cref="SystemPhase.EarlyUpdate"/> phase after
21
/// <see cref="UIInputSystem"/>.
22
/// </para>
23
/// </remarks>
24
public sealed class UIFocusSystem : SystemBase
25
{
26
    private UIContext? uiContext;
27
    private IInputContext? inputContext;
28
    private bool tabWasDown;
29
    private bool escapeWasDown;
30
    private bool enterWasDown;
31
    private bool spaceWasDown;
32

33
    /// <inheritdoc />
34
    public override void Update(float deltaTime)
35
    {
36
        // Lazy initialization
37
        if (inputContext is null && !World.TryGetExtension<IInputContext>(out inputContext))
1✔
38
        {
39
            return;
1✔
40
        }
41

42
        if (uiContext is null && !World.TryGetExtension<UIContext>(out uiContext))
1✔
43
        {
44
            return;
1✔
45
        }
46

47
        // At this point both contexts are guaranteed non-null
48
        var input = inputContext!;
1✔
49
        var ui = uiContext!;
1✔
50

51
        var keyboard = input.Keyboard;
1✔
52

53
        // Handle Tab navigation
54
        bool tabIsDown = keyboard.IsKeyDown(Key.Tab);
1✔
55
        if (tabIsDown && !tabWasDown)
1✔
56
        {
57
            bool isShiftDown = (keyboard.Modifiers & KeyModifiers.Shift) != 0;
1✔
58
            NavigateFocus(isShiftDown);
1✔
59
        }
60
        tabWasDown = tabIsDown;
1✔
61

62
        // Handle Escape to clear focus
63
        bool escapeIsDown = keyboard.IsKeyDown(Key.Escape);
1✔
64
        if (escapeIsDown && !escapeWasDown)
1✔
65
        {
66
            ui.ClearFocus();
1✔
67
        }
68
        escapeWasDown = escapeIsDown;
1✔
69

70
        // Handle Enter/Space on the focused element. These are edge-triggered:
71
        // holding the key down must fire Submit/Click exactly once per press, not
72
        // every frame the key remains held (see #1193). Previous-frame state is
73
        // tracked unconditionally so the edge is detected regardless of focus.
74
        bool enterIsDown = keyboard.IsKeyDown(Key.Enter) || keyboard.IsKeyDown(Key.KeypadEnter);
1✔
75
        bool spaceIsDown = keyboard.IsKeyDown(Key.Space);
1✔
76
        bool enterPressed = enterIsDown && !enterWasDown;
1✔
77
        bool spacePressed = spaceIsDown && !spaceWasDown;
1✔
78
        enterWasDown = enterIsDown;
1✔
79
        spaceWasDown = spaceIsDown;
1✔
80

81
        if (ui.HasFocus && (enterPressed || spacePressed))
1✔
82
        {
83
            var focused = ui.FocusedEntity;
1✔
84
            if (World.IsAlive(focused) && World.Has<UIInteractable>(focused))
1✔
85
            {
86
                ref var interactable = ref World.Get<UIInteractable>(focused);
1✔
87

88
                if (enterPressed)
1✔
89
                {
90
                    interactable.PendingEvents |= UIEventType.Submit;
1✔
91
                    World.Send(new UISubmitEvent(focused));
1✔
92
                }
93

94
                if (spacePressed && interactable.CanClick)
1✔
95
                {
96
                    interactable.PendingEvents |= UIEventType.Click;
1✔
97
                    var rect = World.Get<UIRect>(focused);
1✔
98
                    World.Send(new UIClickEvent(focused, rect.ComputedBounds.Center, MouseButton.Left));
1✔
99
                }
100
            }
101
        }
102
    }
1✔
103

104
    private void NavigateFocus(bool reverse)
105
    {
106
        if (uiContext is null)
1✔
107
        {
UNCOV
108
            return;
×
109
        }
110

111
        // Get all focusable elements sorted by tab index
112
        var focusableElements = new List<(Entity Entity, int TabIndex, int Order)>();
1✔
113
        int order = 0;
1✔
114

115
        foreach (var entity in World.Query<UIInteractable, UIElement, UIRect>())
1✔
116
        {
117
            if (World.Has<UIHiddenTag>(entity) || World.Has<UIDisabledTag>(entity))
1✔
118
            {
119
                continue;
120
            }
121

122
            ref readonly var element = ref World.Get<UIElement>(entity);
1✔
123
            if (!element.Visible)
1✔
124
            {
125
                continue;
126
            }
127

128
            ref readonly var interactable = ref World.Get<UIInteractable>(entity);
1✔
129
            if (!interactable.CanFocus)
1✔
130
            {
131
                continue;
132
            }
133

134
            focusableElements.Add((entity, interactable.TabIndex, order++));
1✔
135
        }
136

137
        if (focusableElements.Count == 0)
1✔
138
        {
139
            return;
1✔
140
        }
141

142
        // Sort by tab index, then by order
143
        focusableElements.Sort((a, b) =>
1✔
144
        {
1✔
145
            int cmp = a.TabIndex.CompareTo(b.TabIndex);
1✔
146
            return cmp != 0 ? cmp : a.Order.CompareTo(b.Order);
1✔
147
        });
1✔
148

149
        // Find current focused element
150
        int currentIndex = -1;
1✔
151
        if (uiContext.HasFocus)
1✔
152
        {
153
            for (int i = 0; i < focusableElements.Count; i++)
1✔
154
            {
155
                if (focusableElements[i].Entity == uiContext.FocusedEntity)
1✔
156
                {
157
                    currentIndex = i;
1✔
158
                    break;
1✔
159
                }
160
            }
161
        }
162

163
        // Calculate next index
164
        int nextIndex;
165
        if (reverse)
1✔
166
        {
167
            nextIndex = currentIndex <= 0
1✔
168
                ? focusableElements.Count - 1
1✔
169
                : currentIndex - 1;
1✔
170
        }
171
        else
172
        {
173
            nextIndex = currentIndex >= focusableElements.Count - 1
1✔
174
                ? 0
1✔
175
                : currentIndex + 1;
1✔
176
        }
177

178
        // Focus the next element
179
        uiContext.RequestFocus(focusableElements[nextIndex].Entity);
1✔
180
    }
1✔
181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc