diff --git a/src/Filee.App/Controls/DonutMenu.cs b/src/Filee.App/Controls/DonutMenu.cs index d26a890ec..04b6ff6fb 100644 --- a/src/Filee.App/Controls/DonutMenu.cs +++ b/src/Filee.App/Controls/DonutMenu.cs @@ -143,6 +143,11 @@ public DonutMenu() FlowDirection = FlowDirection.LeftToRight; ClipToBounds = false; Cursor = new Cursor(StandardCursorType.Hand); + // The live click-mode donut handles Tab, arrows, Enter and Escape itself. It must be the focused control + // after a context-menu/shortcut launch; otherwise Avalonia has no focused target to which the key events can + // be routed. It is focused explicitly by RadialController, so it must not participate in normal Tab order. + Focusable = true; + IsTabStop = false; } public IReadOnlyList? Items { get => GetValue(ItemsProperty); set => SetValue(ItemsProperty, value); } diff --git a/src/Filee.App/Services/RadialController.cs b/src/Filee.App/Services/RadialController.cs index 943831e63..bfcd335ba 100644 --- a/src/Filee.App/Services/RadialController.cs +++ b/src/Filee.App/Services/RadialController.cs @@ -610,7 +610,13 @@ private void PlaceAndShow(RadialWindow window, int x, int y, bool activate) } _visible = true; if (activate) + { window.Activate(); + // ShowActivated is false because the same window is also used during an OS drag, where taking focus would + // break Explorer's drag. Click mode is different: the donut owns the keyboard until it closes, so focus it + // explicitly for Tab/arrow/Enter handling. + window.DonutControl.Focus(); + } } private static bool SameFiles(IReadOnlyList a, IReadOnlyList b) => diff --git a/tests/Filee.App.Tests/RadialControllerTests.cs b/tests/Filee.App.Tests/RadialControllerTests.cs index 2c99a4848..ede1551c7 100644 --- a/tests/Filee.App.Tests/RadialControllerTests.cs +++ b/tests/Filee.App.Tests/RadialControllerTests.cs @@ -2,7 +2,9 @@ // the gesture ends without files, even after a conversion left the ring behind (issue: desktop icons stopped reacting // to clicks until Filee quit). +using Avalonia.Headless; using Avalonia.Headless.XUnit; +using Avalonia.Input; using Filee.App.Controls; using Filee.App.Services; using Filee.Core.Presets; @@ -59,6 +61,26 @@ public void Esc_closes_a_donut_whose_ring_already_finished() Assert.False(radial.Window.IsVisible); } + [AvaloniaFact] + public void Click_mode_focuses_the_donut_for_tab_flip() + { + TestServices.EnsureInitialized("en"); + var radial = AppHost.Get(); + try + { + radial.ShowForFiles(400, 400, [@"C:\photos\a.jpg"]); + var window = radial.Window!; + + Assert.True(window.DonutControl.IsFocused); + window.KeyPress(Key.Tab, RawInputModifiers.None, PhysicalKey.Tab, null); + Assert.True(AppHost.Get().IsBackFace); + } + finally + { + radial.Close(animate: false); + } + } + [Fact] public void Folders_only_go_into_one_archive() {