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

orion-ecs / keen-eye / 20767041456

07 Jan 2026 12:58AM UTC coverage: 87.804% (+17.6%) from 70.212%
20767041456

push

github

tyevco
fix: Resolve all SonarAnalyzer errors for clean build

Fix all analyzer violations to achieve 0 errors, 0 warnings with
SonarAnalyzer.CSharp v10.17.0 and TreatWarningsAsErrors enabled.

Key fixes:
- S2325: Make methods static where appropriate
- S1066: Merge nested if statements
- S1481: Replace unused variables with discards
- S127: Refactor for loops to while loops in CLI arg parsing
- S3218: Rename properties that shadow System types
- S3878: Suppress where conflicting with S3220 (params array)
- S3904: Suppress for SDK projects (no assembly output)
- S3881: Implement proper IDisposable pattern
- S2292: Convert to auto-implemented properties
- IDE0059: Remove unnecessary variable assignments

Also updates test files to use static method calls after
refactoring instance methods to static.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

8869 of 11773 branches covered (75.33%)

Branch coverage included in aggregate %.

639 of 867 new or added lines in 131 files covered. (73.7%)

15 existing lines in 11 files now uncovered.

155014 of 174874 relevant lines covered (88.64%)

1.01 hits per line

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

0.55
/src/KeenEyes.Graph/Systems/GraphWidgetSystem.cs
1
using KeenEyes.Graph.Abstractions;
2
using KeenEyes.Input.Abstractions;
3

4
namespace KeenEyes.Graph;
5

6
/// <summary>
7
/// System that handles input for focused widgets in graph nodes.
8
/// </summary>
9
/// <remarks>
10
/// <para>
11
/// Runs before <see cref="GraphInputSystem"/> to intercept keyboard and mouse input
12
/// for widgets that have focus. This prevents node dragging and other interactions
13
/// when editing widget values.
14
/// </para>
15
/// <para>
16
/// Handles text editing (backspace, typing), slider dragging, dropdown selection,
17
/// and focus management (clicking outside to clear focus, ESC to cancel).
18
/// </para>
19
/// </remarks>
20
public sealed class GraphWidgetSystem : SystemBase
21
{
22
    private IInputContext? inputContext;
23
    private GraphContext? graphContext;
24

25
    // Key state for debouncing
26
    private readonly HashSet<Key> keysDownLastFrame = [];
1✔
27

28
    // Mouse state for drag detection
29
    private bool isDraggingSlider;
30

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

40
        if (graphContext is null && !World.TryGetExtension(out graphContext))
×
41
        {
42
            return;
×
43
        }
44

45
        var mouse = inputContext!.Mouse;
×
46
        var keyboard = inputContext.Keyboard;
×
47

48
        // Process each canvas that has a focused widget
49
        foreach (var canvas in World.Query<GraphCanvas, WidgetFocus, GraphCanvasTag>())
×
50
        {
51
            ProcessWidgetInput(canvas, mouse, keyboard, deltaTime);
×
52
        }
53

54
        // Update state for next frame
55
        UpdateKeyState(keyboard);
×
56
    }
×
57

58
    private void ProcessWidgetInput(Entity canvas, IMouse mouse, IKeyboard keyboard, float deltaTime)
59
    {
60
        ref var focus = ref World.Get<WidgetFocus>(canvas);
×
61

62
        // Handle ESC to cancel editing
63
        if (WasKeyJustPressed(keyboard, Key.Escape))
×
64
        {
65
            ClearFocus(canvas);
×
66
            return;
×
67
        }
68

69
        // Handle Enter to commit and clear focus
70
        if (WasKeyJustPressed(keyboard, Key.Enter))
×
71
        {
72
            CommitAndClearFocus(canvas, ref focus);
×
73
            return;
×
74
        }
75

76
        // Process input based on widget type
77
        switch (focus.Type)
×
78
        {
79
            case WidgetType.FloatField:
80
            case WidgetType.IntField:
81
                ProcessTextInput(ref focus, keyboard);
×
82
                break;
×
83

84
            case WidgetType.TextArea:
85
                ProcessTextAreaInput(ref focus, keyboard);
×
86
                break;
×
87

88
            case WidgetType.Slider:
89
                ProcessSliderInput(ref focus, mouse, deltaTime);
×
90
                break;
×
91

92
            case WidgetType.Dropdown:
93
                ProcessDropdownInput(ref focus, keyboard);
×
94
                break;
×
95

96
            case WidgetType.ColorPicker:
97
                ProcessColorPickerInput(ref focus, mouse, keyboard);
×
98
                break;
99
        }
100
    }
×
101

102
    private void ProcessTextInput(ref WidgetFocus focus, IKeyboard keyboard)
103
    {
104
        // Handle backspace
105
        if (WasKeyJustPressed(keyboard, Key.Backspace) && focus.EditBuffer.Length > 0)
×
106
        {
107
            if (focus.CursorPosition > 0)
×
108
            {
109
                focus.EditBuffer = focus.EditBuffer.Remove(focus.CursorPosition - 1, 1);
×
110
                focus.CursorPosition--;
×
111
            }
112
            return;
×
113
        }
114

115
        // Handle delete
116
        if (WasKeyJustPressed(keyboard, Key.Delete) && focus.CursorPosition < focus.EditBuffer.Length)
×
117
        {
118
            focus.EditBuffer = focus.EditBuffer.Remove(focus.CursorPosition, 1);
×
119
            return;
×
120
        }
121

122
        // Handle cursor movement
123
        if (WasKeyJustPressed(keyboard, Key.Left) && focus.CursorPosition > 0)
×
124
        {
125
            focus.CursorPosition--;
×
126
            return;
×
127
        }
128

129
        if (WasKeyJustPressed(keyboard, Key.Right) && focus.CursorPosition < focus.EditBuffer.Length)
×
130
        {
131
            focus.CursorPosition++;
×
132
            return;
×
133
        }
134

135
        // Handle Home/End
136
        if (WasKeyJustPressed(keyboard, Key.Home))
×
137
        {
138
            focus.CursorPosition = 0;
×
139
            return;
×
140
        }
141

142
        if (WasKeyJustPressed(keyboard, Key.End))
×
143
        {
144
            focus.CursorPosition = focus.EditBuffer.Length;
×
145
            return;
×
146
        }
147

148
        // Handle character input for number fields
149
        ProcessNumberKeys(ref focus, keyboard);
×
150
    }
×
151

152
    private void ProcessNumberKeys(ref WidgetFocus focus, IKeyboard keyboard)
153
    {
154
        // Number keys
155
        for (var key = Key.Number0; key <= Key.Number9; key++)
×
156
        {
157
            if (WasKeyJustPressed(keyboard, key))
×
158
            {
159
                var digit = (char)('0' + (key - Key.Number0));
×
160
                InsertChar(ref focus, digit);
×
161
                return;
×
162
            }
163
        }
164

165
        // Numpad keys
166
        for (var key = Key.Keypad0; key <= Key.Keypad9; key++)
×
167
        {
168
            if (WasKeyJustPressed(keyboard, key))
×
169
            {
170
                var digit = (char)('0' + (key - Key.Keypad0));
×
171
                InsertChar(ref focus, digit);
×
172
                return;
×
173
            }
174
        }
175

176
        // Minus/negative
177
        if (WasKeyJustPressed(keyboard, Key.Minus) || WasKeyJustPressed(keyboard, Key.KeypadSubtract))
×
178
        {
179
            // Only allow minus at the beginning
180
            if (focus.CursorPosition == 0 && !focus.EditBuffer.Contains('-'))
×
181
            {
182
                InsertChar(ref focus, '-');
×
183
            }
184
            return;
×
185
        }
186

187
        // Decimal point (for float fields only)
NEW
188
        if (focus.Type == WidgetType.FloatField &&
×
NEW
189
            (WasKeyJustPressed(keyboard, Key.Period) || WasKeyJustPressed(keyboard, Key.KeypadDecimal)) &&
×
NEW
190
            !focus.EditBuffer.Contains('.'))
×
191
        {
NEW
192
            InsertChar(ref focus, '.');
×
193
        }
194
    }
×
195

196
    private void ProcessTextAreaInput(ref WidgetFocus focus, IKeyboard keyboard)
197
    {
198
        // Handle backspace
199
        if (WasKeyJustPressed(keyboard, Key.Backspace) && focus.EditBuffer.Length > 0)
×
200
        {
201
            if (focus.CursorPosition > 0)
×
202
            {
203
                focus.EditBuffer = focus.EditBuffer.Remove(focus.CursorPosition - 1, 1);
×
204
                focus.CursorPosition--;
×
205
            }
206
            return;
×
207
        }
208

209
        // Handle alphanumeric keys (simplified)
210
        for (var key = Key.A; key <= Key.Z; key++)
×
211
        {
212
            if (WasKeyJustPressed(keyboard, key))
×
213
            {
214
                var shift = (keyboard.Modifiers & KeyModifiers.Shift) != 0;
×
215
                var ch = shift ? (char)key : char.ToLowerInvariant((char)key);
×
216
                InsertChar(ref focus, ch);
×
217
                return;
×
218
            }
219
        }
220

221
        // Handle number keys
222
        ProcessNumberKeys(ref focus, keyboard);
×
223

224
        // Handle space
225
        if (WasKeyJustPressed(keyboard, Key.Space))
×
226
        {
227
            InsertChar(ref focus, ' ');
×
228
        }
229
    }
×
230

231
    private void ProcessSliderInput(ref WidgetFocus focus, IMouse mouse, float deltaTime)
232
    {
233
        if (mouse.IsButtonDown(MouseButton.Left))
×
234
        {
235
            if (!isDraggingSlider)
×
236
            {
237
                // Start drag
238
                isDraggingSlider = true;
×
239
                focus.DragStartValue = float.TryParse(focus.EditBuffer, out var v) ? v : 0f;
×
240
            }
241

242
            // Continue drag - actual value update happens in NodeWidgets based on position
243
        }
244
        else if (isDraggingSlider)
×
245
        {
246
            // End drag
247
            isDraggingSlider = false;
×
248
        }
249
    }
×
250

251
    private void ProcessDropdownInput(ref WidgetFocus focus, IKeyboard keyboard)
252
    {
253
        // Handle arrow keys for selection
254
        if (WasKeyJustPressed(keyboard, Key.Down))
×
255
        {
256
            // Increment selection (actual bounds checking in NodeWidgets)
257
            var current = int.TryParse(focus.EditBuffer, out var v) ? v : 0;
×
258
            focus.EditBuffer = (current + 1).ToString();
×
259
        }
260
        else if (WasKeyJustPressed(keyboard, Key.Up))
×
261
        {
262
            var current = int.TryParse(focus.EditBuffer, out var v) ? v : 0;
×
263
            if (current > 0)
×
264
            {
265
                focus.EditBuffer = (current - 1).ToString();
×
266
            }
267
        }
268

269
        // Space toggles expanded state
270
        if (WasKeyJustPressed(keyboard, Key.Space))
×
271
        {
272
            focus.IsExpanded = !focus.IsExpanded;
×
273
        }
274
    }
×
275

276
    private void ProcessColorPickerInput(ref WidgetFocus focus, IMouse mouse, IKeyboard keyboard)
277
    {
278
        // Color picker input is handled primarily through mouse clicks in the popup
279
        // Space toggles the picker popup
280
        if (WasKeyJustPressed(keyboard, Key.Space))
×
281
        {
282
            focus.IsExpanded = !focus.IsExpanded;
×
283
        }
284
    }
×
285

286
    private static void InsertChar(ref WidgetFocus focus, char c)
287
    {
288
        focus.EditBuffer = focus.EditBuffer.Insert(focus.CursorPosition, c.ToString());
×
289
        focus.CursorPosition++;
×
290
    }
×
291

292
    private void ClearFocus(Entity canvas)
293
    {
294
        World.Remove<WidgetFocus>(canvas);
×
295
    }
×
296

297
    private void CommitAndClearFocus(Entity canvas, ref WidgetFocus focus)
298
    {
299
        // Value has been edited in EditBuffer - the node's RenderBody will read it on next render
300
        // Just clear the focus component
301
        World.Remove<WidgetFocus>(canvas);
×
302
    }
×
303

304
    private bool WasKeyJustPressed(IKeyboard keyboard, Key key)
305
    {
306
        var isDownNow = keyboard.IsKeyDown(key);
×
307
        var wasDownLastFrame = keysDownLastFrame.Contains(key);
×
308
        return isDownNow && !wasDownLastFrame;
×
309
    }
310

311
    private void UpdateKeyState(IKeyboard keyboard)
312
    {
313
        keysDownLastFrame.Clear();
×
314

315
        // Track common editing keys
316
        Key[] keysToTrack =
×
317
        [
×
318
            Key.Escape, Key.Enter, Key.Backspace, Key.Delete,
×
319
            Key.Left, Key.Right, Key.Up, Key.Down,
×
320
            Key.Home, Key.End, Key.Space,
×
321
            Key.Minus, Key.Period,
×
322
            Key.KeypadSubtract, Key.KeypadDecimal
×
323
        ];
×
324

325
        foreach (var key in keysToTrack)
×
326
        {
327
            if (keyboard.IsKeyDown(key))
×
328
            {
329
                keysDownLastFrame.Add(key);
×
330
            }
331
        }
332

333
        // Track letter keys
334
        for (var key = Key.A; key <= Key.Z; key++)
×
335
        {
336
            if (keyboard.IsKeyDown(key))
×
337
            {
338
                keysDownLastFrame.Add(key);
×
339
            }
340
        }
341

342
        // Track number keys
343
        for (var key = Key.Number0; key <= Key.Number9; key++)
×
344
        {
345
            if (keyboard.IsKeyDown(key))
×
346
            {
347
                keysDownLastFrame.Add(key);
×
348
            }
349
        }
350

351
        // Track numpad keys
352
        for (var key = Key.Keypad0; key <= Key.Keypad9; key++)
×
353
        {
354
            if (keyboard.IsKeyDown(key))
×
355
            {
356
                keysDownLastFrame.Add(key);
×
357
            }
358
        }
359
    }
×
360
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc