diff --git a/demo/Ursa.Demo/Dialogs/CustomDemoDialog.axaml b/demo/Ursa.Demo/Dialogs/CustomDemoDialog.axaml
index 3b38ce3..dc61a44 100644
--- a/demo/Ursa.Demo/Dialogs/CustomDemoDialog.axaml
+++ b/demo/Ursa.Demo/Dialogs/CustomDemoDialog.axaml
@@ -17,7 +17,10 @@
-
+
-
+
-
-
-
+
+
+
A
B
@@ -79,5 +96,5 @@
-
+
diff --git a/src/Ursa/AssemblyInfo.cs b/src/Ursa/AssemblyInfo.cs
index 5fb4320..a45e3de 100644
--- a/src/Ursa/AssemblyInfo.cs
+++ b/src/Ursa/AssemblyInfo.cs
@@ -3,4 +3,5 @@ using Avalonia.Metadata;
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
-[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls.Shapes")]
\ No newline at end of file
+[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls.Shapes")]
+[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Helpers")]
\ No newline at end of file
diff --git a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs
index e2f2be3..5a8a4f2 100644
--- a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs
+++ b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs
@@ -4,6 +4,7 @@ using Avalonia.VisualTree;
using Irihi.Avalonia.Shared.Helpers;
using Irihi.Avalonia.Shared.Shapes;
using Ursa.Controls.OverlayShared;
+using Ursa.Helpers;
namespace Ursa.Controls;
@@ -111,14 +112,20 @@ public partial class OverlayDialogHost
SetToPosition(control);
control.AddHandler(OverlayFeedbackElement.ClosedEvent, OnDialogControlClosing);
control.AddHandler(DialogControlBase.LayerChangedEvent, OnDialogLayerChanged);
+ // Notice: mask animation here is not really awaited, because currently dialogs appears immediately.
if (!IsAnimationDisabled) MaskAppearAnimation.RunAsync(mask);
- var element = control.GetVisualDescendants().OfType().FirstOrDefault(a => a.Focusable);
+ var element = control.GetVisualDescendants().OfType()
+ .FirstOrDefault(FocusHelper.GetDialogFocusHint);
+ if (element is null)
+ {
+ element = control.GetVisualDescendants().OfType().FirstOrDefault(a => a.Focusable);
+ }
element?.Focus();
_modalCount++;
IsInModalStatus = _modalCount > 0;
control.IsClosed = false;
- control.Focus();
+ // control.Focus();
}
// Handle dialog layer change event
diff --git a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs
index 438d8a9..3c3a35b 100644
--- a/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs
+++ b/src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs
@@ -9,6 +9,7 @@ using Irihi.Avalonia.Shared.Shapes;
using Ursa.Common;
using Ursa.Controls.OverlayShared;
using Ursa.EventArgs;
+using Ursa.Helpers;
namespace Ursa.Controls;
@@ -69,7 +70,13 @@ public partial class OverlayDialogHost
{
await Task.WhenAll(animation.RunAsync(control), MaskAppearAnimation.RunAsync(mask));
}
- var element = control.GetVisualDescendants().OfType().FirstOrDefault(a => a.Focusable);
+
+ var element = control.GetVisualDescendants().OfType()
+ .FirstOrDefault(FocusHelper.GetDialogFocusHint);
+ if (element is null)
+ {
+ element = control.GetVisualDescendants().OfType().FirstOrDefault(a => a.Focusable);
+ }
element?.Focus();
}
diff --git a/src/Ursa/Helpers/FocusHelper.cs b/src/Ursa/Helpers/FocusHelper.cs
new file mode 100644
index 0000000..2139c81
--- /dev/null
+++ b/src/Ursa/Helpers/FocusHelper.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Input;
+
+namespace Ursa.Helpers;
+
+public class FocusHelper
+{
+ public static readonly AttachedProperty DialogFocusHintProperty =
+ AvaloniaProperty.RegisterAttached("DialogFocusHint");
+
+ public static void SetDialogFocusHint(InputElement obj, bool value) => obj.SetValue(DialogFocusHintProperty, value);
+ public static bool GetDialogFocusHint(InputElement obj) => obj.GetValue(DialogFocusHintProperty);
+}
\ No newline at end of file
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml
new file mode 100644
index 0000000..e1903bd
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml.cs b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml.cs
new file mode 100644
index 0000000..fc70acd
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/FocusDialog.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
+
+public partial class FocusDialog : UserControl
+{
+ public FocusDialog()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml
new file mode 100644
index 0000000..3d4c3e6
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml.cs b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml.cs
new file mode 100644
index 0000000..0d0025d
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/NormalDialog.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
+
+public partial class NormalDialog : UserControl
+{
+ public NormalDialog()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/Test.cs b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/Test.cs
new file mode 100644
index 0000000..bc86188
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/Test.cs
@@ -0,0 +1,74 @@
+using Avalonia.Controls;
+using Avalonia.Headless.XUnit;
+using Avalonia.Threading;
+using Avalonia.VisualTree;
+using Ursa.Controls;
+
+namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
+
+public class Test
+{
+ [AvaloniaFact]
+ public async Task Normal_Drawer_Focus_On_Border()
+ {
+ var ursaWindow = new TestWindow();
+ ursaWindow.Show();
+ ursaWindow.InvokeNormalDrawer();
+ Dispatcher.UIThread.RunJobs();
+ await Task.Delay(500);
+ var dialog = ursaWindow.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.NotNull(dialog);
+ var border = dialog.GetVisualDescendants().OfType().FirstOrDefault(a=>a.Name == "PART_Root");
+ var text = dialog.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.True(border?.IsFocused);
+ Assert.False(text?.IsFocused);
+ }
+
+ [AvaloniaFact]
+ public async Task Focus_Drawer_Focus_On_Primary()
+ {
+ var ursaWindow = new TestWindow();
+ ursaWindow.Show();
+ ursaWindow.InvokeFocusDrawer();
+ Dispatcher.UIThread.RunJobs();
+ await Task.Delay(500);
+ var dialog = ursaWindow.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.NotNull(dialog);
+ var border = dialog.GetVisualDescendants().OfType().FirstOrDefault(a=>a.Name == "PART_Root");
+ var text = dialog.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.False(border?.IsFocused);
+ Assert.True(text?.IsFocused);
+ }
+
+ [AvaloniaFact]
+ public async Task Normal_Dialog_Focus_On_Border()
+ {
+ var ursaWindow = new TestWindow();
+ ursaWindow.Show();
+ ursaWindow.InvokeNormalDialog();
+ Dispatcher.UIThread.RunJobs();
+ await Task.Delay(100);
+ var dialog = ursaWindow.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.NotNull(dialog);
+ var border = dialog.GetVisualDescendants().OfType().FirstOrDefault(a=>a.Name == "PART_Border");
+ var text = dialog.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.True(border?.IsFocused);
+ Assert.False(text?.IsFocused);
+ }
+
+ [AvaloniaFact]
+ public async Task Focus_Dialog_Focus_On_Primary()
+ {
+ var ursaWindow = new TestWindow();
+ ursaWindow.Show();
+ ursaWindow.InvokeFocusDialog();
+ Dispatcher.UIThread.RunJobs();
+ await Task.Delay(100);
+ var dialog = ursaWindow.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.NotNull(dialog);
+ var border = dialog.GetVisualDescendants().OfType().FirstOrDefault(a=>a.Name == "PART_Border");
+ var text = dialog.GetVisualDescendants().OfType().FirstOrDefault();
+ Assert.False(border?.IsFocused);
+ Assert.True(text?.IsFocused);
+ }
+}
\ No newline at end of file
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml
new file mode 100644
index 0000000..855ec1d
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml
@@ -0,0 +1,11 @@
+
+ Welcome to Avalonia!
+
diff --git a/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml.cs b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml.cs
new file mode 100644
index 0000000..a835212
--- /dev/null
+++ b/tests/HeadlessTest.Ursa/Controls/OverlayShared/Dialog_Primary_Focus/TestWindow.axaml.cs
@@ -0,0 +1,36 @@
+using Ursa.Controls;
+using Ursa.Controls.Options;
+
+namespace HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus;
+
+public partial class TestWindow : UrsaWindow
+{
+ public TestWindow()
+ {
+ InitializeComponent();
+ }
+
+ public void InvokeNormalDrawer()
+ {
+ Drawer.ShowModal("Hello World",
+ options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
+ }
+
+ public void InvokeFocusDrawer()
+ {
+ Drawer.ShowModal("Hello World",
+ options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
+ }
+
+ public void InvokeNormalDialog()
+ {
+ OverlayDialog.ShowModal("Hello World",
+ options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
+ }
+
+ public void InvokeFocusDialog()
+ {
+ OverlayDialog.ShowModal("Hello World",
+ options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
+ }
+}
\ No newline at end of file