Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to set Modal Dialog initial focus element #481

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions demo/Ursa.Demo/Dialogs/CustomDemoDialog.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
<GradientStop Offset="0.9" Color="{DynamicResource SemiLightBlue1Color}" />
</LinearGradientBrush>
</UserControl.Background>
<Grid Margin="24" RowDefinitions="Auto, *, Auto" MinWidth="400">
<Grid
MinWidth="400"
Margin="24"
RowDefinitions="Auto, *, Auto">
<TextBlock
Grid.Row="0"
Margin="8"
Expand Down Expand Up @@ -45,7 +48,10 @@
Grid.Column="1"
Margin="32,8,0,8"
Label="Owner">
<TextBox u:FormItem.Label="Owner" Text="{Binding Owner}" />
<TextBox
u:FocusHelper.DialogFocusHint="True"
u:FormItem.Label="Owner"
Text="{Binding Owner}" />
</u:FormItem>
<u:FormItem
Grid.Row="1"
Expand All @@ -69,15 +75,26 @@
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="8">
<Button Command="{Binding DialogCommand}" Content="Dialog" Theme="{DynamicResource SolidButton}" />
<Button Command="{Binding OKCommand}" Content="OK" Theme="{DynamicResource SolidButton}" Classes="Tertiary"/>
<Button Command="{Binding CancelCommand}" Content="Cancel" Theme="{DynamicResource SolidButton}" Classes="Tertiary"/>
<Button
Command="{Binding DialogCommand}"
Content="Dialog"
Theme="{DynamicResource SolidButton}" />
<Button
Classes="Tertiary"
Command="{Binding OKCommand}"
Content="OK"
Theme="{DynamicResource SolidButton}" />
<Button
Classes="Tertiary"
Command="{Binding CancelCommand}"
Content="Cancel"
Theme="{DynamicResource SolidButton}" />
<ComboBox>
<ComboBoxItem>A</ComboBoxItem>
<ComboBoxItem>B</ComboBoxItem>
<ComboBoxItem>C</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>

</UserControl>
3 changes: 2 additions & 1 deletion src/Ursa/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
[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")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls.Shapes")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Helpers")]
11 changes: 9 additions & 2 deletions src/Ursa/Controls/OverlayShared/OverlayDialogHost.Dialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Irihi.Avalonia.Shared.Helpers;
using Irihi.Avalonia.Shared.Shapes;
using Ursa.Controls.OverlayShared;
using Ursa.Helpers;

namespace Ursa.Controls;

Expand Down Expand Up @@ -111,14 +112,20 @@ internal void AddModalDialog(DialogControlBase control)
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<InputElement>().FirstOrDefault(a => a.Focusable);
var element = control.GetVisualDescendants().OfType<InputElement>()
.FirstOrDefault(FocusHelper.GetDialogFocusHint);
if (element is null)
{
element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
}
element?.Focus();
_modalCount++;
IsInModalStatus = _modalCount > 0;
control.IsClosed = false;
control.Focus();
// control.Focus();
}

// Handle dialog layer change event
Expand Down
9 changes: 8 additions & 1 deletion src/Ursa/Controls/OverlayShared/OverlayDialogHost.Drawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Ursa.Common;
using Ursa.Controls.OverlayShared;
using Ursa.EventArgs;
using Ursa.Helpers;

namespace Ursa.Controls;

Expand Down Expand Up @@ -69,7 +70,13 @@ internal async void AddModalDrawer(DrawerControlBase control)
{
await Task.WhenAll(animation.RunAsync(control), MaskAppearAnimation.RunAsync(mask));
}
var element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);

var element = control.GetVisualDescendants().OfType<InputElement>()
.FirstOrDefault(FocusHelper.GetDialogFocusHint);
if (element is null)
{
element = control.GetVisualDescendants().OfType<InputElement>().FirstOrDefault(a => a.Focusable);
}
element?.Focus();
}

Expand Down
13 changes: 13 additions & 0 deletions src/Ursa/Helpers/FocusHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Input;

namespace Ursa.Helpers;

public class FocusHelper
{
public static readonly AttachedProperty<bool> DialogFocusHintProperty =
AvaloniaProperty.RegisterAttached<FocusHelper, InputElement, bool>("DialogFocusHint");

public static void SetDialogFocusHint(InputElement obj, bool value) => obj.SetValue(DialogFocusHintProperty, value);
public static bool GetDialogFocusHint(InputElement obj) => obj.GetValue(DialogFocusHintProperty);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
mc:Ignorable="d" d:DesignWidth="800"
d:DesignHeight="450"
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.FocusDialog">
<TextBox Text="Hello" u:FocusHelper.DialogFocusHint="True"></TextBox>
</UserControl>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800"
d:DesignHeight="450"
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.NormalDialog">
<TextBox Text="Hello"></TextBox>
</UserControl>
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<DefaultDrawerControl>().FirstOrDefault();
Assert.NotNull(dialog);
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Root");
var text = dialog.GetVisualDescendants().OfType<TextBox>().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<DefaultDrawerControl>().FirstOrDefault();
Assert.NotNull(dialog);
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Root");
var text = dialog.GetVisualDescendants().OfType<TextBox>().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<DefaultDialogControl>().FirstOrDefault();
Assert.NotNull(dialog);
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Border");
var text = dialog.GetVisualDescendants().OfType<TextBox>().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<DefaultDialogControl>().FirstOrDefault();
Assert.NotNull(dialog);
var border = dialog.GetVisualDescendants().OfType<Border>().FirstOrDefault(a=>a.Name == "PART_Border");
var text = dialog.GetVisualDescendants().OfType<TextBox>().FirstOrDefault();
Assert.False(border?.IsFocused);
Assert.True(text?.IsFocused);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<u:UrsaWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
mc:Ignorable="d" d:DesignWidth="800"
d:DesignHeight="450"
x:Class="HeadlessTest.Ursa.Controls.OverlayShared.Dialog_Primary_Focus.TestWindow"
Title="TestWindow">
Welcome to Avalonia!
</u:UrsaWindow>
Original file line number Diff line number Diff line change
@@ -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<NormalDialog, object>("Hello World",
options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
}

public void InvokeFocusDrawer()
{
Drawer.ShowModal<FocusDialog, object>("Hello World",
options: new DrawerOptions() { TopLevelHashCode = GetHashCode() });
}

public void InvokeNormalDialog()
{
OverlayDialog.ShowModal<NormalDialog, object>("Hello World",
options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
}

public void InvokeFocusDialog()
{
OverlayDialog.ShowModal<FocusDialog, object>("Hello World",
options: new OverlayDialogOptions() { TopLevelHashCode = GetHashCode() });
}
}