Compare commits

..

8 Commits

Author SHA1 Message Date
5813b2e354 Updater: Ignore files introduced by the user in base directory (#5092)
* Updater: Ignore files introduced by the user in base directory

* Replicate logic in Avalonia version.

* Address requested changes

* Updater: Ignore files introduced by the user in base directory

* Replicate logic in Avalonia version.

* Address requested changes

* Address requested changes

* Address requested changes

* Comment cleanup

* Address feedback

* Forgot comment, tehe
2023-06-05 14:19:17 +02:00
af1906ea04 Fix wrong unaligned SB state when fetching compute shaders (#5223) 2023-06-05 14:01:33 +02:00
68848000f7 Texture: Fix 3D texture size when totalBlocksOfGobsInZ > 1 (#5228)
* Texture: Fix 3D texture size when totalBlocksOfGobsInZ > 0

When there is a remainder when dividing depth by gobs in z, it is used to remove the unused part of the 3D texture's size. This was done to calculate correct sizes for single slice views of 3D textures.

However, this case can also apply to 3D textures with many slices, and more than one total block of gobs in z. In this case it's meant to trim off the end of the level size. Most textures won't encounter this as their size will be aligned, but UE4 games tend to use 3D textures with funny unaligned sizes.

The size offset should have been applied to the level size instead of the slice size, and it should only affect the slice size if it ends up larger.

Hopefully should fix issues with UE4 games without breaking other stuff, I don't have much time to test.

* Whoops
2023-06-05 13:33:09 +02:00
d98da47a0f Better application grid flex (#5218) 2023-06-05 00:48:11 +00:00
306f7e93a0 Dont Error on Invalid Enum Values (#5169)
* Dont Error on Invalid Enum

* Use TryParse

* Log warning
2023-06-05 01:19:46 +02:00
8954ff3af2 Replacing ZbcColorArray with Array4<uint> (#5210)
* Related "if/else if" statements should not have the same condition

* replacing ZbcColorArray with Array4<uint>

* fix alignment
2023-06-04 20:30:04 +00:00
d2f3adbf69 Texture: Fix layout conversion when gobs in z is used with depth = 1 (#5220)
* Texture: Fix layout conversion when gobs in z is used with depth = 1

The size calculator methods deliberately reduce the gob size of textures if they are deemed too small for it. This is required to get correct sizes when iterating mip levels of a texture.

Rendering to a slice of a 3D texture can produce a 3D texture with depth 1, but a gob size matching a much larger texture. We _can't_ "correct" this gob size, as it is intended as a slice of a larger 3D texture. Ignoring it causes layout conversion to break on read and flush.

This caused an issue in Tears of the Kingdom where the compressed 3D texture used for the gloom would always break on OpenGL, and seemingly randomly break on Vulkan. In the first case, the data is forcibly flushed to decompress the BC4 texture on the CPU to upload it as 3D, which was broken due to the incorrect layout. In the second, the data may be randomly flushed if it falls out of the cache, but it will appear correct if it's able to form copy dependencies.

This change only allows gob sizes to be reduced once per mip level. For the purpose of aligned size, it can still be reduced infinitely as our texture cache isn't properly able to handle a view being _misaligned_.

The SizeCalculator has also been changed to reduce the size of rendered depth slices to only include the exact range a single depth slice will cover. (before, the size was way too small with gobs in z reduced to 1, and too large when using the correct value)

Gobs in Y logic remains untouched, we don't support Y slices of textures so it's fine as is.

This is probably worth testing in a few games as it also affects texture size and view logic.

* Improve wording

* Maybe a bit better
2023-06-04 20:25:57 +00:00
d511c845b7 Check KeyboardMode in GUI (#4343)
* Update SoftwareKeyboard to send KeyboardMode to UI

* Update GTK UI to check text against KeyboardMode

* Update Ava UI to check text against KeyboardMode

* Restructure input validation

* true when text is not empty

* Add English validation text for SoftwareKeyboardMode

* Add Chinese validation text for SoftwareKeyboardMode

* Update base on feedback

---------

Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com>
2023-06-04 05:30:24 +02:00
19 changed files with 191 additions and 85 deletions

View File

@ -544,6 +544,9 @@
"SwkbdMinCharacters": "Must be at least {0} characters long",
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
"SoftwareKeyboard": "Software Keyboard",
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
"SoftwareKeyboardModeAlphabet": "Must be alphabets only",
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
"DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",

View File

@ -527,6 +527,9 @@
"SwkbdMinCharacters": "至少应为 {0} 个字长",
"SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
"SoftwareKeyboard": "软件键盘",
"SoftwareKeyboardModeNumbersOnly": "只接受数字",
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
"SoftwareKeyboardModeASCII": "只接受 ASCII 符号",
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型{1}\n\n玩家类型{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型{1}\n\n玩家类型{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
"DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式",

View File

@ -527,6 +527,9 @@
"SwkbdMinCharacters": "至少應為 {0} 個字長",
"SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
"SoftwareKeyboard": "軟體鍵盤",
"SoftwareKeyboardModeNumbersOnly": "只接受數字",
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
"SoftwareKeyboardModeASCII": "只接受 ASCII 符號",
"DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型{1}\n\n玩家{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
"DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型{1}\n\n玩家{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
"DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",

View File

@ -740,6 +740,18 @@ namespace Ryujinx.Modules
{
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files
if (_running)
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
// Remove user files from the paths in files.
files = files.Except(userFiles);
}
if (OperatingSystem.IsWindows())
{
foreach (string dir in WindowsDependencyDirs)

View File

@ -9,14 +9,17 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Controls
{
internal partial class SwkbdAppletDialog : UserControl
{
private Predicate<int> _checkLength;
private Predicate<int> _checkLength = _ => true;
private Predicate<string> _checkInput = _ => true;
private int _inputMax;
private int _inputMin;
private string _placeholder;
@ -35,8 +38,6 @@ namespace Ryujinx.Ava.UI.Controls
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
}
public SwkbdAppletDialog()
@ -67,6 +68,7 @@ namespace Ryujinx.Ava.UI.Controls
string input = string.Empty;
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
content.SetInputValidation(args.KeyboardMode);
content._host = contentDialog;
contentDialog.Title = title;
@ -91,6 +93,12 @@ namespace Ryujinx.Ava.UI.Controls
return (result, input);
}
private void ApplyValidationInfo(string text)
{
Error.IsVisible = !string.IsNullOrEmpty(text);
Error.Text = text;
}
public void SetInputLengthValidation(int min, int max)
{
_inputMin = Math.Min(min, max);
@ -99,6 +107,8 @@ namespace Ryujinx.Ava.UI.Controls
Error.IsVisible = false;
Error.FontStyle = FontStyle.Italic;
string validationInfoText = "";
if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
{
Error.IsVisible = false;
@ -107,21 +117,48 @@ namespace Ryujinx.Ava.UI.Controls
}
else if (_inputMin > 0 && _inputMax == int.MaxValue)
{
Error.IsVisible = true;
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
_checkLength = length => _inputMin <= length;
}
else
{
Error.IsVisible = true;
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
_checkLength = length => _inputMin <= length && length <= _inputMax;
}
ApplyValidationInfo(validationInfoText);
Message_TextInput(this, new TextInputEventArgs());
}
private void SetInputValidation(KeyboardMode mode)
{
string validationInfoText = Error.Text;
string localeText;
switch (mode)
{
case KeyboardMode.NumbersOnly:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsDigit);
break;
case KeyboardMode.Alphabet:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsAsciiLetter);
break;
case KeyboardMode.ASCII:
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII);
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
_checkInput = text => text.All(char.IsAscii);
break;
default:
_checkInput = _ => true;
break;
}
ApplyValidationInfo(validationInfoText);
Message_TextInput(this, new TextInputEventArgs());
}
@ -129,7 +166,7 @@ namespace Ryujinx.Ava.UI.Controls
{
if (_host != null)
{
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
}
}
@ -141,7 +178,7 @@ namespace Ryujinx.Ava.UI.Controls
}
else
{
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
}
}
}

View File

@ -32,10 +32,10 @@
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
HorizontalAlignment="Center"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
JustifyContent="FlexStart" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>

View File

@ -1,4 +1,5 @@
#nullable enable
using Ryujinx.Common.Logging;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
@ -18,12 +19,14 @@ namespace Ryujinx.Common.Utilities
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var enumValue = reader.GetString();
if (string.IsNullOrEmpty(enumValue))
if (Enum.TryParse(enumValue, out TEnum value))
{
return default;
return value;
}
return Enum.Parse<TEnum>(enumValue);
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
return default;
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
@ -31,4 +34,4 @@ namespace Ryujinx.Common.Utilities
writer.WriteStringValue(value.ToString());
}
}
}
}

View File

@ -151,8 +151,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
ShaderProgramInfo info = cs.Shaders[0].Info;
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
for (int index = 0; index < info.SBuffers.Count; index++)
{
BufferDescriptor sb = info.SBuffers[index];
@ -177,9 +175,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
}
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)
if (_channel.BufferManager.HasUnalignedStorageBuffers != computeState.HasUnalignedStorageBuffer)
{
// Refetch the shader, as assumptions about storage buffer alignment have changed.
computeState = new GpuChannelComputeState(
qmd.CtaThreadDimension0,
qmd.CtaThreadDimension1,
qmd.CtaThreadDimension2,
localMemorySize,
sharedMemorySize,
_channel.BufferManager.HasUnalignedStorageBuffers);
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);

View File

@ -541,7 +541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
depth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
lhs.GobBlocksInZ,
level);
return gobBlocksInY == rhs.GobBlocksInY &&
gobBlocksInZ == rhs.GobBlocksInZ;
@ -587,7 +588,8 @@ namespace Ryujinx.Graphics.Gpu.Image
lhsDepth,
lhs.FormatInfo.BlockHeight,
lhs.GobBlocksInY,
lhs.GobBlocksInZ);
lhs.GobBlocksInZ,
lhsLevel);
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
@ -597,7 +599,8 @@ namespace Ryujinx.Graphics.Gpu.Image
rhsDepth,
rhs.FormatInfo.BlockHeight,
rhs.GobBlocksInY,
rhs.GobBlocksInZ);
rhs.GobBlocksInZ,
rhsLevel);
return lhsGobBlocksInY == rhsGobBlocksInY &&
lhsGobBlocksInZ == rhsGobBlocksInZ;

View File

@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Image
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
}
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ, minLod);
}
levels = (maxLod - minLod) + 1;

View File

@ -143,7 +143,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}

View File

@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.Texture
int gobBlocksInTileX,
int gpuLayerSize = 0)
{
bool is3D = depth > 1;
bool is3D = depth > 1 || gobBlocksInZ > 1;
int layerSize = 0;
@ -67,7 +67,7 @@ namespace Ryujinx.Graphics.Texture
mipGobBlocksInY >>= 1;
}
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
{
mipGobBlocksInZ >>= 1;
}
@ -88,6 +88,10 @@ namespace Ryujinx.Graphics.Texture
int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
mipOffsets[level] = layerSize;
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
if (is3D)
{
int gobSize = mipGobBlocksInY * GobSize;
@ -105,11 +109,22 @@ namespace Ryujinx.Graphics.Texture
allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
}
}
mipOffsets[level] = layerSize;
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
int gobRemainderZ = d % mipGobBlocksInZ;
if (gobRemainderZ != 0 && level == levels - 1)
{
// The slice only covers up to the end of this slice's depth, rather than the full aligned size.
// Avoids size being too large on partial views of 3d textures.
levelSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ);
if (sliceSizes[level] > levelSizes[level])
{
sliceSizes[level] = levelSizes[level];
}
}
}
layerSize += levelSizes[level];
@ -267,7 +282,8 @@ namespace Ryujinx.Graphics.Texture
int depth,
int blockHeight,
int gobBlocksInY,
int gobBlocksInZ)
int gobBlocksInZ,
int level = int.MaxValue)
{
height = BitUtils.DivRoundUp(height, blockHeight);
@ -276,7 +292,7 @@ namespace Ryujinx.Graphics.Texture
gobBlocksInY >>= 1;
}
while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
{
gobBlocksInZ >>= 1;
}

View File

@ -3,7 +3,7 @@
/// <summary>
/// Identifies the variant of keyboard displayed on screen.
/// </summary>
enum KeyboardMode : uint
public enum KeyboardMode : uint
{
/// <summary>
/// A full alpha-numeric keyboard.

View File

@ -209,6 +209,7 @@ namespace Ryujinx.HLE.HOS.Applets
// Call the configured GUI handler to get user's input.
var args = new SoftwareKeyboardUiArgs
{
KeyboardMode = _keyboardForegroundConfig.Mode,
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),

View File

@ -1,7 +1,10 @@
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
namespace Ryujinx.HLE.HOS.Applets
{
public struct SoftwareKeyboardUiArgs
{
public KeyboardMode KeyboardMode;
public string HeaderText;
public string SubtitleText;
public string InitialText;

View File

@ -1,49 +1,16 @@
using System;
using Ryujinx.Common.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
{
[StructLayout(LayoutKind.Sequential)]
struct ZbcColorArray
{
private uint element0;
private uint element1;
private uint element2;
private uint element3;
public uint this[int index]
{
get
{
if (index == 0)
{
return element0;
}
else if (index == 1)
{
return element1;
}
else if (index == 2)
{
return element2;
}
else if (index == 2)
{
return element3;
}
throw new IndexOutOfRangeException();
}
}
}
[StructLayout(LayoutKind.Sequential)]
struct ZbcSetTableArguments
{
public ZbcColorArray ColorDs;
public ZbcColorArray ColorL2;
public uint Depth;
public uint Format;
public uint Type;
public Array4<uint> ColorDs;
public Array4<uint> ColorL2;
public uint Depth;
public uint Format;
public uint Type;
}
}

View File

@ -565,6 +565,18 @@ namespace Ryujinx.Modules
{
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files
if (Running)
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
// Remove user files from the paths in files.
files = files.Except(userFiles);
}
if (OperatingSystem.IsWindows())
{
foreach (string dir in WindowsDependencyDirs)

View File

@ -106,6 +106,7 @@ namespace Ryujinx.Ui.Applet
swkbdDialog.OkButton.Label = args.SubmitText;
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
swkbdDialog.SetInputValidation(args.KeyboardMode);
if (swkbdDialog.Run() == (int)ResponseType.Ok)
{

View File

@ -1,5 +1,7 @@
using Gtk;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using System;
using System.Linq;
namespace Ryujinx.Ui.Applet
{
@ -7,8 +9,12 @@ namespace Ryujinx.Ui.Applet
{
private int _inputMin;
private int _inputMax;
private KeyboardMode _mode;
private Predicate<int> _checkLength;
private string _validationInfoText = "";
private Predicate<int> _checkLength = _ => true;
private Predicate<string> _checkInput = _ => true;
private readonly Label _validationInfo;
@ -38,8 +44,12 @@ namespace Ryujinx.Ui.Applet
((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
}
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
private void ApplyValidationInfo()
{
_validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText);
_validationInfo.Markup = _validationInfoText;
}
public void SetInputLengthValidation(int min, int max)
@ -53,23 +63,49 @@ namespace Ryujinx.Ui.Applet
{
_validationInfo.Visible = false;
_checkLength = (length) => true;
_checkLength = _ => true;
}
else if (_inputMin > 0 && _inputMax == int.MaxValue)
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be at least {_inputMin} characters long</i>";
_validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> ";
_checkLength = (length) => _inputMin <= length;
_checkLength = length => _inputMin <= length;
}
else
{
_validationInfo.Visible = true;
_validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
_validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> ";
_checkLength = (length) => _inputMin <= length && length <= _inputMax;
_checkLength = length => _inputMin <= length && length <= _inputMax;
}
ApplyValidationInfo();
OnInputChanged(this, EventArgs.Empty);
}
public void SetInputValidation(KeyboardMode mode)
{
_mode = mode;
switch (mode)
{
case KeyboardMode.NumbersOnly:
_validationInfoText += "<i>Must be numbers only.</i>";
_checkInput = text => text.All(char.IsDigit);
break;
case KeyboardMode.Alphabet:
_validationInfoText += "<i>Must be alphabets only.</i>";
_checkInput = text => text.All(char.IsAsciiLetter);
break;
case KeyboardMode.ASCII:
_validationInfoText += "<i>Must be ASCII text only.</i>";
_checkInput = text => text.All(char.IsAscii);
break;
default:
_checkInput = _ => true;
break;
}
ApplyValidationInfo();
OnInputChanged(this, EventArgs.Empty);
}
@ -83,7 +119,7 @@ namespace Ryujinx.Ui.Applet
private void OnInputChanged(object sender, EventArgs e)
{
OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text);
}
}
}