diff options
| author | Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com> | 2022-12-29 14:24:05 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-29 15:24:05 +0100 |
| commit | 76671d63d4f3ea18f8ad99e9ce9f0b2ec9a2599d (patch) | |
| tree | 05013214e4696a9254369d0706173f58877f6a83 /Ryujinx.Ava/UI/Applet | |
| parent | 3d1a0bf3749afa14da5b5ba1e0666fdb78c99beb (diff) | |
Ava GUI: Restructure `Ryujinx.Ava` (#4165)
* Restructure `Ryujinx.Ava`
* Stylistic consistency
* Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Controls/UserEditor.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Controls/UserSelector.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Controls/SaveManager.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Update Ryujinx.Ava/UI/Helpers/EmbeddedWindow.cs
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
* Fix redundancies
* Remove redunancies
* Add back elses
Co-authored-by: TSRBerry <20988865+TSRBerry@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Ava/UI/Applet')
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 198 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs | 164 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs | 43 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 52 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs | 80 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 64 | ||||
| -rw-r--r-- | Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs | 177 |
7 files changed, 778 insertions, 0 deletions
diff --git a/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs new file mode 100644 index 00000000..a8e76275 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs @@ -0,0 +1,198 @@ +using Avalonia.Controls; +using Avalonia.Threading; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.Ui; +using System; +using System.Threading; + +namespace Ryujinx.Ava.UI.Applet +{ + internal class AvaHostUiHandler : IHostUiHandler + { + private readonly MainWindow _parent; + + public IHostUiTheme HostUiTheme { get; } + + public AvaHostUiHandler(MainWindow parent) + { + _parent = parent; + + HostUiTheme = new AvaloniaHostUiTheme(parent); + } + + public bool DisplayMessageDialog(ControllerAppletUiArgs args) + { + string playerCount = args.PlayerCountMin == args.PlayerCountMax + ? args.PlayerCountMin.ToString() + : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; + + string key = args.PlayerCountMin == args.PlayerCountMax ? "DialogControllerAppletMessage" : "DialogControllerAppletMessagePlayerRange"; + + string message = string.Format(LocaleManager.Instance[key], + playerCount, + args.SupportedStyles, + string.Join(", ", args.SupportedPlayers), + args.IsDocked ? LocaleManager.Instance["DialogControllerAppletDockModeSet"] : ""); + + return DisplayMessageDialog(LocaleManager.Instance["DialogControllerAppletTitle"], message); + } + + public bool DisplayMessageDialog(string title, string message) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + + Dispatcher.UIThread.InvokeAsync(async () => + { + try + { + ManualResetEvent deferEvent = new(false); + + bool opened = false; + + UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent, + title, + message, + "", + LocaleManager.Instance["DialogOpenSettingsWindowLabel"], + "", + LocaleManager.Instance["SettingsButtonClose"], + (int)Symbol.Important, + deferEvent, + async (window) => + { + if (opened) + { + return; + } + + opened = true; + + _parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager); + + await _parent.SettingsWindow.ShowDialog(window); + + opened = false; + }); + + if (response == UserResult.Ok) + { + okPressed = true; + } + + dialogCloseEvent.Set(); + } + catch (Exception ex) + { + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex)); + + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + return okPressed; + } + + public bool DisplayInputDialog(SoftwareKeyboardUiArgs args, out string userText) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool okPressed = false; + bool error = false; + string inputText = args.InitialText ?? ""; + + Dispatcher.UIThread.Post(async () => + { + try + { + var response = await SwkbdAppletDialog.ShowInputDialog(_parent, LocaleManager.Instance["SoftwareKeyboard"], args); + + if (response.Result == UserResult.Ok) + { + inputText = response.Input; + okPressed = true; + } + } + catch (Exception ex) + { + error = true; + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex)); + } + finally + { + dialogCloseEvent.Set(); + } + }); + + dialogCloseEvent.WaitOne(); + + userText = error ? null : inputText; + + return error || okPressed; + } + + public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value) + { + device.Configuration.UserChannelPersistence.ExecuteProgram(kind, value); + if (_parent.AppHost != null) + { + _parent.AppHost.Stop(); + } + } + + public bool DisplayErrorAppletDialog(string title, string message, string[] buttons) + { + ManualResetEvent dialogCloseEvent = new(false); + + bool showDetails = false; + + Dispatcher.UIThread.Post(async () => + { + try + { + ErrorAppletWindow msgDialog = new(_parent, buttons, message) + { + Title = title, + WindowStartupLocation = WindowStartupLocation.CenterScreen, + Width = 400 + }; + + object response = await msgDialog.Run(); + + if (response != null && buttons != null && buttons.Length > 1 && (int)response != buttons.Length - 1) + { + showDetails = true; + } + + dialogCloseEvent.Set(); + + msgDialog.Close(); + } + catch (Exception ex) + { + dialogCloseEvent.Set(); + await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex)); + } + }); + + dialogCloseEvent.WaitOne(); + + return showDetails; + } + + public IDynamicTextInputHandler CreateDynamicTextInputHandler() + { + return new AvaloniaDynamicTextInputHandler(_parent); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs new file mode 100644 index 00000000..314746e7 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs @@ -0,0 +1,164 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Threading; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.Ui; +using System; +using System.Threading; + +using HidKey = Ryujinx.Common.Configuration.Hid.Key; + +namespace Ryujinx.Ava.UI.Applet +{ + class AvaloniaDynamicTextInputHandler : IDynamicTextInputHandler + { + private MainWindow _parent; + private OffscreenTextBox _hiddenTextBox; + private bool _canProcessInput; + private IDisposable _textChangedSubscription; + private IDisposable _selectionStartChangedSubscription; + private IDisposable _selectionEndtextChangedSubscription; + + public AvaloniaDynamicTextInputHandler(MainWindow parent) + { + _parent = parent; + + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed += AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease += AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput += AvaloniaDynamicTextInputHandler_TextInput; + + _hiddenTextBox = _parent.HiddenTextBox; + + Dispatcher.UIThread.Post(() => + { + _textChangedSubscription = _hiddenTextBox.GetObservable(TextBox.TextProperty).Subscribe(TextChanged); + _selectionStartChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionStartProperty).Subscribe(SelectionChanged); + _selectionEndtextChangedSubscription = _hiddenTextBox.GetObservable(TextBox.SelectionEndProperty).Subscribe(SelectionChanged); + }); + } + + private void TextChanged(string text) + { + TextChangedEvent?.Invoke(text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void SelectionChanged(int selection) + { + if (_hiddenTextBox.SelectionEnd < _hiddenTextBox.SelectionStart) + { + _hiddenTextBox.SelectionStart = _hiddenTextBox.SelectionEnd; + } + + TextChangedEvent?.Invoke(_hiddenTextBox.Text ?? string.Empty, _hiddenTextBox.SelectionStart, _hiddenTextBox.SelectionEnd, true); + } + + private void AvaloniaDynamicTextInputHandler_TextInput(object sender, string text) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendText(text); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyUpEvent(e); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) + { + var key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key); + + if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) + { + return; + } + + e.RoutedEvent = _hiddenTextBox.GetKeyUpRoutedEvent(); + + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendKeyDownEvent(e); + } + }); + } + + public bool TextProcessingEnabled + { + get + { + return Volatile.Read(ref _canProcessInput); + } + set + { + Volatile.Write(ref _canProcessInput, value); + } + } + + public event DynamicTextChangedHandler TextChangedEvent; + public event KeyPressedHandler KeyPressedEvent; + public event KeyReleasedHandler KeyReleasedEvent; + + public void Dispose() + { + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyPressed -= AvaloniaDynamicTextInputHandler_KeyPressed; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).KeyRelease -= AvaloniaDynamicTextInputHandler_KeyRelease; + (_parent.InputManager.KeyboardDriver as AvaloniaKeyboardDriver).TextInput -= AvaloniaDynamicTextInputHandler_TextInput; + + _textChangedSubscription?.Dispose(); + _selectionStartChangedSubscription?.Dispose(); + _selectionEndtextChangedSubscription?.Dispose(); + + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Clear(); + _parent.RendererControl.Focus(); + + _parent = null; + }); + } + + public void SetText(string text, int cursorBegin) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.CaretIndex = cursorBegin; + }); + } + + public void SetText(string text, int cursorBegin, int cursorEnd) + { + Dispatcher.UIThread.Post(() => + { + _hiddenTextBox.Text = text; + _hiddenTextBox.SelectionStart = cursorBegin; + _hiddenTextBox.SelectionEnd = cursorEnd; + }); + } + } +} diff --git a/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs b/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs new file mode 100644 index 00000000..fe5e2721 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs @@ -0,0 +1,43 @@ +using Avalonia.Media; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.Ui; +using System; + +namespace Ryujinx.Ava.UI.Applet +{ + class AvaloniaHostUiTheme : IHostUiTheme + { + public AvaloniaHostUiTheme(MainWindow parent) + { + FontFamily = OperatingSystem.IsWindows() && OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000, 0) ? "Segoe UI Variable" : parent.FontFamily.Name; + DefaultBackgroundColor = BrushToThemeColor(parent.Background); + DefaultForegroundColor = BrushToThemeColor(parent.Foreground); + DefaultBorderColor = BrushToThemeColor(parent.BorderBrush); + SelectionBackgroundColor = BrushToThemeColor(parent.SearchBox.SelectionBrush); + SelectionForegroundColor = BrushToThemeColor(parent.SearchBox.SelectionForegroundBrush); + } + + public string FontFamily { get; } + + public ThemeColor DefaultBackgroundColor { get; } + public ThemeColor DefaultForegroundColor { get; } + public ThemeColor DefaultBorderColor { get; } + public ThemeColor SelectionBackgroundColor { get; } + public ThemeColor SelectionForegroundColor { get; } + + private ThemeColor BrushToThemeColor(IBrush brush) + { + if (brush is SolidColorBrush solidColor) + { + return new ThemeColor((float)solidColor.Color.A / 255, + (float)solidColor.Color.R / 255, + (float)solidColor.Color.G / 255, + (float)solidColor.Color.B / 255); + } + else + { + return new ThemeColor(); + } + } + } +} diff --git a/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml new file mode 100644 index 00000000..211b4725 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml @@ -0,0 +1,52 @@ +<Window + x:Class="Ryujinx.Ava.UI.Applet.ErrorAppletWindow" + xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + Title="{locale:Locale ErrorWindowTitle}" + Width="450" + Height="340" + CanResize="False" + SizeToContent="Height" + mc:Ignorable="d" + Focusable="True"> + <Grid + Margin="20" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="*" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Image + Grid.Row="1" + Grid.RowSpan="2" + Grid.Column="0" + Height="80" + MinWidth="50" + Margin="5,10,20,10" + Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock + Grid.Row="1" + Grid.Column="1" + Margin="10" + VerticalAlignment="Stretch" + Text="{Binding Message}" + TextWrapping="Wrap" /> + <StackPanel + Name="ButtonStack" + Grid.Row="2" + Grid.Column="1" + Margin="10" + HorizontalAlignment="Right" + Orientation="Horizontal" + Spacing="10" /> + </Grid> +</Window>
\ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs new file mode 100644 index 00000000..a17826f8 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs @@ -0,0 +1,80 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Windows; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Applet +{ + internal partial class ErrorAppletWindow : StyleableWindow + { + private readonly Window _owner; + private object _buttonResponse; + + public ErrorAppletWindow(Window owner, string[] buttons, string message) + { + _owner = owner; + Message = message; + DataContext = this; + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + int responseId = 0; + + if (buttons != null) + { + foreach (string buttonText in buttons) + { + AddButton(buttonText, responseId); + responseId++; + } + } + else + { + AddButton(LocaleManager.Instance["InputDialogOk"], 0); + } + } + + public ErrorAppletWindow() + { + DataContext = this; + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + public string Message { get; set; } + + private void AddButton(string label, object tag) + { + Dispatcher.UIThread.InvokeAsync(() => + { + Button button = new() { Content = label, Tag = tag }; + + button.Click += Button_Click; + ButtonStack.Children.Add(button); + }); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is Button button) + { + _buttonResponse = button.Tag; + } + + Close(); + } + + public async Task<object> Run() + { + await ShowDialog(_owner); + + return _buttonResponse; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml new file mode 100644 index 00000000..43ccf9e7 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml @@ -0,0 +1,64 @@ +<UserControl + x:Class="Ryujinx.Ava.UI.Controls.SwkbdAppletDialog" + 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" + Width="400" + mc:Ignorable="d" + Focusable="True"> + <Grid + Margin="20" + HorizontalAlignment="Stretch" + VerticalAlignment="Stretch"> + <Grid.RowDefinitions> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + <RowDefinition Height="Auto" /> + </Grid.RowDefinitions> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="Auto" /> + <ColumnDefinition /> + </Grid.ColumnDefinitions> + <Image + Grid.Row="1" + Grid.RowSpan="5" + Height="80" + MinWidth="50" + Margin="5,10,20,10" + VerticalAlignment="Center" + Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" /> + <TextBlock + Grid.Row="1" + Grid.Column="1" + Margin="5" + Text="{Binding MainText}" + TextWrapping="Wrap" /> + <TextBlock + Grid.Row="2" + Grid.Column="1" + Margin="5" + Text="{Binding SecondaryText}" + TextWrapping="Wrap" /> + <TextBox + Name="Input" + Grid.Row="3" + Grid.Column="1" + HorizontalAlignment="Stretch" + VerticalAlignment="Center" + KeyUp="Message_KeyUp" + Text="{Binding Message}" + TextInput="Message_TextInput" + TextWrapping="Wrap" + UseFloatingWatermark="True" /> + <TextBlock + Name="Error" + Grid.Row="4" + Grid.Column="1" + Margin="5" + HorizontalAlignment="Stretch" + TextWrapping="Wrap" /> + </Grid> +</UserControl>
\ No newline at end of file diff --git a/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs new file mode 100644 index 00000000..80be2979 --- /dev/null +++ b/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -0,0 +1,177 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Media; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.HLE.HOS.Applets; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.UI.Controls +{ + internal partial class SwkbdAppletDialog : UserControl + { + private Predicate<int> _checkLength; + private int _inputMax; + private int _inputMin; + private string _placeholder; + + private ContentDialog _host; + + public SwkbdAppletDialog(string mainText, string secondaryText, string placeholder) + { + MainText = mainText; + SecondaryText = secondaryText; + DataContext = this; + _placeholder = placeholder; + InitializeComponent(); + + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + + SetInputLengthValidation(0, int.MaxValue); // Disable by default. + } + + public SwkbdAppletDialog() + { + DataContext = this; + InitializeComponent(); + } + + public string Message { get; set; } = ""; + public string MainText { get; set; } = ""; + public string SecondaryText { get; set; } = ""; + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args) + { + ContentDialog contentDialog = new ContentDialog(); + + UserResult result = UserResult.Cancel; + + SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText) + { + Message = args.InitialText ?? "" + }; + + string input = string.Empty; + + var overlay = new ContentDialogOverlayWindow() + { + Height = window.Bounds.Height, + Width = window.Bounds.Width, + Position = window.PointToScreen(new Point()) + }; + + window.PositionChanged += OverlayOnPositionChanged; + + void OverlayOnPositionChanged(object sender, PixelPointEventArgs e) + { + overlay.Position = window.PointToScreen(new Point()); + } + + contentDialog = overlay.ContentDialog; + + bool opened = false; + + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + + content._host = contentDialog; + contentDialog.Title = title; + contentDialog.PrimaryButtonText = args.SubmitText; + contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); + contentDialog.SecondaryButtonText = ""; + contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"]; + contentDialog.Content = content; + + TypedEventHandler<ContentDialog, ContentDialogClosedEventArgs> handler = (sender, eventArgs) => + { + if (eventArgs.Result == ContentDialogResult.Primary) + { + result = UserResult.Ok; + input = content.Input.Text; + } + }; + contentDialog.Closed += handler; + + overlay.Opened += OverlayOnActivated; + + async void OverlayOnActivated(object sender, EventArgs e) + { + if (opened) + { + return; + } + + opened = true; + + overlay.Position = window.PointToScreen(new Point()); + + await contentDialog.ShowAsync(overlay); + contentDialog.Closed -= handler; + overlay.Close(); + }; + + await overlay.ShowDialog(window); + + return (result, input); + } + + public void SetInputLengthValidation(int min, int max) + { + _inputMin = Math.Min(min, max); + _inputMax = Math.Max(min, max); + + Error.IsVisible = false; + Error.FontStyle = FontStyle.Italic; + + if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. + { + Error.IsVisible = false; + + _checkLength = length => true; + } + else if (_inputMin > 0 && _inputMax == int.MaxValue) + { + Error.IsVisible = true; + Error.Text = string.Format(LocaleManager.Instance["SwkbdMinCharacters"], _inputMin); + + _checkLength = length => _inputMin <= length; + } + else + { + Error.IsVisible = true; + Error.Text = string.Format(LocaleManager.Instance["SwkbdMinRangeCharacters"], _inputMin, _inputMax); + + _checkLength = length => _inputMin <= length && length <= _inputMax; + } + + Message_TextInput(this, new TextInputEventArgs()); + } + + private void Message_TextInput(object sender, TextInputEventArgs e) + { + if (_host != null) + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length); + } + } + + private void Message_KeyUp(object sender, KeyEventArgs e) + { + if (e.Key == Key.Enter && _host.IsPrimaryButtonEnabled) + { + _host.Hide(ContentDialogResult.Primary); + } + else + { + _host.IsPrimaryButtonEnabled = _checkLength(Message.Length); + } + } + } +}
\ No newline at end of file |
