From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001
From: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
Date: Sat, 8 Apr 2023 01:22:00 +0200
Subject: Move solution and projects to src
---
src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 197 +++++++++++++++++++++
.../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 164 +++++++++++++++++
src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs | 43 +++++
src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 52 ++++++
.../UI/Applet/ErrorAppletWindow.axaml.cs | 80 +++++++++
src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 65 +++++++
.../UI/Applet/SwkbdAppletDialog.axaml.cs | 148 ++++++++++++++++
7 files changed, 749 insertions(+)
create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs
create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs
create mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
create mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs
create mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml
create mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs
(limited to 'src/Ryujinx.Ava/UI/Applet')
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
new file mode 100644
index 00000000..c87308cf
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs
@@ -0,0 +1,197 @@
+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 message = LocaleManager.Instance.UpdateAndGetDynamicValue(
+ args.PlayerCountMin == args.PlayerCountMax ? LocaleKeys.DialogControllerAppletMessage : LocaleKeys.DialogControllerAppletMessagePlayerRange,
+ args.PlayerCountMin == args.PlayerCountMax ? args.PlayerCountMin.ToString() : $"{args.PlayerCountMin}-{args.PlayerCountMax}",
+ args.SupportedStyles,
+ string.Join(", ", args.SupportedPlayers),
+ args.IsDocked ? LocaleManager.Instance[LocaleKeys.DialogControllerAppletDockModeSet] : "");
+
+ return DisplayMessageDialog(LocaleManager.Instance[LocaleKeys.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;
+
+ _parent.Activate();
+
+ UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
+ title,
+ message,
+ "",
+ LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
+ "",
+ LocaleManager.Instance[LocaleKeys.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(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.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[LocaleKeys.SoftwareKeyboard], args);
+
+ if (response.Result == UserResult.Ok)
+ {
+ inputText = response.Input;
+ okPressed = true;
+ }
+ }
+ catch (Exception ex)
+ {
+ error = true;
+
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.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.ViewModel.AppHost != null)
+ {
+ _parent.ViewModel.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(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
+ }
+ });
+
+ dialogCloseEvent.WaitOne();
+
+ return showDetails;
+ }
+
+ public IDynamicTextInputHandler CreateDynamicTextInputHandler()
+ {
+ return new AvaloniaDynamicTextInputHandler(_parent);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs
new file mode 100644
index 00000000..2dd65e36
--- /dev/null
+++ b/src/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.ViewModel.RendererHostControl.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/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs b/src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs
new file mode 100644
index 00000000..77c7a2d2
--- /dev/null
+++ b/src/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.ViewControls.SearchBox.SelectionBrush);
+ SelectionForegroundColor = BrushToThemeColor(parent.ViewControls.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/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
new file mode 100644
index 00000000..211b4725
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs
new file mode 100644
index 00000000..4134797b
--- /dev/null
+++ b/src/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[LocaleKeys.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