diff options
| author | Emmanuel Hansen <emmausssss@gmail.com> | 2022-05-15 11:30:15 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-15 13:30:15 +0200 |
| commit | deb99d2cae3e80bdf70cb52c6c160094dc7c9292 (patch) | |
| tree | e60f44d1b4bd45bbf36fcfa750fb99787febfdbe /Ryujinx.Ava/Ui/Applet | |
| parent | 9ba73ffbe5f78c0403cf102b95768f388da05122 (diff) | |
Avalonia UI - Part 1 (#3270)
* avalonia part 1
* remove vulkan ui backend
* move ui common files to ui common project
* get name for oading screen from device
* rebase.
* review 1
* review 1.1
* review
* cleanup
* addressed review
* use cancellation token
* review
* review
* rebased
* cancel library loading when closing window
* remove star image, use fonticon instead
* delete render control frame buffer when game ends. change position of fav star
* addressed @Thog review
* ensure the right ui is downloaded in updates
* fix crash when showing not supported dialog during controller request
* add prefix to artifact names
* Auto-format Avalonia project
* Fix input
* Fix build, simplify app disposal
* remove nv stutter thread
* addressed review
* add missing change
* maintain window size if new size is zero length
* add game, handheld, docked to local
* reverse scale main window
* Update de_DE.json
* Update de_DE.json
* Update de_DE.json
* Update italian json
* Update it_IT.json
* let render timer poll with no wait
* remove unused code
* more unused code
* enabled tiered compilation and trimming
* check if window event is not closed before signaling
* fix atmospher case
* locale fix
* locale fix
* remove explicit tiered compilation declarations
* Remove ) it_IT.json
* Remove ) de_DE.json
* Update it_IT.json
* Update pt_BR locale with latest strings
* Remove ')'
* add more strings to locale
* update locale
* remove extra slash
* remove extra slash
* set firmware version to 0 if key's not found
* fix
* revert timer changes
* lock on object instead
* Update it_IT.json
* remove unused method
* add load screen text to locale
* drop swap event
* Update de_DE.json
* Update de_DE.json
* do null check when stopping emulator
* Update de_DE.json
* Create tr_TR.json
* Add tr_TR
* Add tr_TR + Turkish
* Update it_IT.json
* Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* Apply suggestions from code review
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* Apply suggestions from code review
Co-authored-by: Ac_K <Acoustik666@gmail.com>
* addressed review
* Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
* use avalonia's inbuilt renderer on linux
* removed whitespace
* workaround for queue render crash with vsync off
* drop custom backend
* format files
* fix not closing issue
* remove warnings
* rebase
* update avalonia library
* Reposition the Text and Button on About Page
* Assign build version
* Remove appveyor text
Co-authored-by: gdk <gab.dark.100@gmail.com>
Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com>
Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com>
Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com>
Co-authored-by: Ac_K <Acoustik666@gmail.com>
Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
Diffstat (limited to 'Ryujinx.Ava/Ui/Applet')
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs | 149 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs | 162 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs | 43 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml | 31 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs | 89 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml | 32 | ||||
| -rw-r--r-- | Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs | 152 |
7 files changed, 658 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..f573e167 --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs @@ -0,0 +1,149 @@ +using Avalonia.Controls; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Controls; +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; +using System.Threading.Tasks; + +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) + { + // TODO : Show controller applet. Needs settings window to be implemented. + Dispatcher.UIThread.InvokeAsync(() => + { + ContentDialogHelper.ShowNotAvailableMessage(_parent); + }); + + return true; + } + + 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; + ContentDialogHelper.CreateErrorDialog(_parent, 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.Length > 1 && (int)response != buttons.Length - 1) + { + showDetails = true; + } + + dialogCloseEvent.Set(); + + msgDialog.Close(); + } + catch (Exception ex) + { + dialogCloseEvent.Set(); + ContentDialogHelper.CreateErrorDialog(_parent, 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..714b6360 --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs @@ -0,0 +1,162 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Threading; +using OpenTK.Windowing.Common; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.Ui.Controls; +using Ryujinx.Ava.Ui.Windows; +using Ryujinx.Common.Configuration.Hid; +using Ryujinx.HLE.Ui; +using System; +using System.Threading; + +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, TextInputEventArgs e) + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_canProcessInput) + { + _hiddenTextBox.SendText(e.AsString); + } + }); + } + + private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, Avalonia.Input.KeyEventArgs e) + { + var key = (Key)AvaloniaMappingHelper.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, Avalonia.Input.KeyEventArgs e) + { + var key = (Key)AvaloniaMappingHelper.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.GlRenderer.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..8034b506 --- /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..6d365abf --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml @@ -0,0 +1,31 @@ +<Window 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:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow" + xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" + CanResize="False" + SizeToContent="Height" + Width="450" + Height="340" + Title="{locale:Locale ErrorWindowTitle}"> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> + <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" Margin="5, 10, 20 , 10" Grid.Column="0" + Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" MinWidth="50" /> + <TextBlock Grid.Row="1" Margin="10" Grid.Column="1" VerticalAlignment="Stretch" TextWrapping="Wrap" + Text="{Binding Message}" /> + <StackPanel Name="ButtonStack" Margin="10" Spacing="10" Grid.Row="2" Grid.Column="1" + HorizontalAlignment="Right" Orientation="Horizontal" /> + </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..d4ec45a8 --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs @@ -0,0 +1,89 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Threading; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Windows; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Ui.Applet +{ + public 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; } + + public StackPanel ButtonStack { 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; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + ButtonStack = this.FindControl<StackPanel>("ButtonStack"); + } + } +}
\ 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..b027a230 --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml @@ -0,0 +1,32 @@ +<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:window="clr-namespace:Ryujinx.Ava.Ui.Windows" + mc:Ignorable="d" + x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog" + Width="400"> + <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20"> + <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" VerticalAlignment="Center" Grid.RowSpan="5" Margin="5, 10, 20 , 10" + Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" + MinWidth="50" /> + <TextBlock Grid.Row="1" Margin="5" Grid.Column="1" Text="{Binding MainText}" TextWrapping="Wrap" /> + <TextBlock Grid.Row="2" Margin="5" Grid.Column="1" Text="{Binding SecondaryText}" TextWrapping="Wrap" /> + <TextBox Name="Input" KeyUp="Message_KeyUp" UseFloatingWatermark="True" TextInput="Message_TextInput" + Text="{Binding Message}" Grid.Row="2" + Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" /> + <TextBlock Name="Error" Margin="5" Grid.Row="4" Grid.Column="1" 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..e81f75bd --- /dev/null +++ b/Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs @@ -0,0 +1,152 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using FluentAvalonia.Core; +using FluentAvalonia.UI.Controls; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Ui.Windows; +using Ryujinx.HLE.HOS.Applets; +using System; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Ui.Controls +{ + public 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(); + + 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 TextBlock Error { get; private set; } + public TextBox Input { get; set; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + Error = this.FindControl<TextBlock>("Error"); + Input = this.FindControl<TextBox>("Input"); + + Input.Watermark = _placeholder; + + Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true); + } + + public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args) + { + ContentDialog contentDialog = window.ContentDialog; + + UserResult result = UserResult.Cancel; + + SwkbdAppletDialog content = new SwkbdAppletDialog(args.HeaderText, args.SubtitleText, args.GuideText) + { + Message = args.InitialText ?? "" + }; + + string input = string.Empty; + + content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax); + + if (contentDialog != null) + { + 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; + await contentDialog.ShowAsync(); + contentDialog.Closed -= handler; + } + + 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 |
