mirror of
https://git.743378673.xyz/MeloNX/MeloNX.git
synced 2025-08-03 01:07:10 +02:00
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>
This commit is contained in:
parent
be60578bc9
commit
9a257d1cc3
161 changed files with 17179 additions and 855 deletions
149
Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs
Normal file
149
Ryujinx.Ava/Ui/Applet/AvaHostUiHandler.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
162
Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs
Normal file
162
Ryujinx.Ava/Ui/Applet/AvaloniaDynamicTextInputHandler.cs
Normal file
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
43
Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs
Normal file
43
Ryujinx.Ava/Ui/Applet/AvaloniaHostUiTheme.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml
Normal file
31
Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml
Normal file
|
@ -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>
|
89
Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs
Normal file
89
Ryujinx.Ava/Ui/Applet/ErrorAppletWindow.axaml.cs
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
32
Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml
Normal file
32
Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml
Normal file
|
@ -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>
|
152
Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs
Normal file
152
Ryujinx.Ava/Ui/Applet/SwkbdAppletDialog.axaml.cs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue