mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2026-04-21 12:21:56 +00:00
Fix Dual Joy-Con driver and InputView (ryubing/ryujinx!259)
See merge request ryubing/ryujinx!259
This commit is contained in:
parent
081cdcab0c
commit
1b3bf1473d
4 changed files with 168 additions and 61 deletions
|
|
@ -9,10 +9,20 @@ using static SDL.SDL3;
|
||||||
|
|
||||||
namespace Ryujinx.Input.SDL3
|
namespace Ryujinx.Input.SDL3
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
public unsafe class SDL3GamepadDriver : IGamepadDriver
|
public unsafe class SDL3GamepadDriver : IGamepadDriver
|
||||||
{
|
{
|
||||||
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
|
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
|
||||||
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
|
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
|
||||||
|
/// <summary>
|
||||||
|
/// Unlinked joy-cons
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<SDL_JoystickID, string> _joyConsIds;
|
||||||
|
/// <summary>
|
||||||
|
/// Linked joy-cons, remove dual joy-con from <c>_gamepadsIds</c> when a linked joy-con is removed
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<SDL_JoystickID,string> _linkedJoyConsIds;
|
||||||
private readonly Lock _lock = new();
|
private readonly Lock _lock = new();
|
||||||
|
|
||||||
public ReadOnlySpan<string> GamepadsIds
|
public ReadOnlySpan<string> GamepadsIds
|
||||||
|
|
@ -21,7 +31,11 @@ namespace Ryujinx.Input.SDL3
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
return _gamepadsIds.Values.ToArray();
|
List<string> temp = [];
|
||||||
|
temp.AddRange(_gamepadsIds.Values);
|
||||||
|
temp.AddRange(_joyConsIds.Values);
|
||||||
|
temp.AddRange(_linkedJoyConsIds.Values);
|
||||||
|
return temp.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +49,8 @@ namespace Ryujinx.Input.SDL3
|
||||||
{
|
{
|
||||||
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
|
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
|
||||||
_gamepadsIds = [];
|
_gamepadsIds = [];
|
||||||
|
_joyConsIds = [];
|
||||||
|
_linkedJoyConsIds = [];
|
||||||
|
|
||||||
SDL3Driver.Instance.Initialize();
|
SDL3Driver.Instance.Initialize();
|
||||||
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
|
||||||
|
|
@ -92,7 +108,7 @@ namespace Ryujinx.Input.SDL3
|
||||||
int guidIndex = 0;
|
int guidIndex = 0;
|
||||||
id = guidIndex + "-" + guidString;
|
id = guidIndex + "-" + guidString;
|
||||||
|
|
||||||
while (_gamepadsIds.ContainsValue(id))
|
while (_gamepadsIds.ContainsValue(id) || _joyConsIds.ContainsValue(id) || _linkedJoyConsIds.ContainsValue(id))
|
||||||
{
|
{
|
||||||
id = (++guidIndex) + "-" + guidString;
|
id = (++guidIndex) + "-" + guidString;
|
||||||
}
|
}
|
||||||
|
|
@ -104,16 +120,47 @@ namespace Ryujinx.Input.SDL3
|
||||||
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
|
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
|
||||||
{
|
{
|
||||||
bool joyConPairDisconnected = false;
|
bool joyConPairDisconnected = false;
|
||||||
|
string fakeId = null;
|
||||||
|
|
||||||
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_gamepadsIds.Remove(joystickInstanceId);
|
if (!_linkedJoyConsIds.ContainsKey(joystickInstanceId))
|
||||||
if (!SDL3JoyConPair.IsCombinable(_gamepadsIds))
|
|
||||||
{
|
{
|
||||||
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
|
if (!_joyConsIds.Remove(joystickInstanceId))
|
||||||
|
{
|
||||||
|
_gamepadsIds.Remove(joystickInstanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (string matchId in _gamepadsIds.Values)
|
||||||
|
{
|
||||||
|
if (matchId.Contains(id))
|
||||||
|
{
|
||||||
|
fakeId = matchId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string leftId = fakeId!.Split('_')[0];
|
||||||
|
string rightId = fakeId!.Split('_')[1];
|
||||||
|
|
||||||
|
if (leftId == id)
|
||||||
|
{
|
||||||
|
_linkedJoyConsIds.Remove(GetInstanceIdFromId(rightId));
|
||||||
|
_joyConsIds.Add(GetInstanceIdFromId(rightId), rightId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_linkedJoyConsIds.Remove(GetInstanceIdFromId(leftId));
|
||||||
|
_joyConsIds.Add(GetInstanceIdFromId(leftId), leftId);
|
||||||
|
}
|
||||||
|
|
||||||
|
_linkedJoyConsIds.Remove(joystickInstanceId);
|
||||||
|
_gamepadsIds.Remove(GetInstanceIdFromId(fakeId));
|
||||||
joyConPairDisconnected = true;
|
joyConPairDisconnected = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -121,13 +168,14 @@ namespace Ryujinx.Input.SDL3
|
||||||
OnGamepadDisconnected?.Invoke(id);
|
OnGamepadDisconnected?.Invoke(id);
|
||||||
if (joyConPairDisconnected)
|
if (joyConPairDisconnected)
|
||||||
{
|
{
|
||||||
OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
|
OnGamepadDisconnected?.Invoke(fakeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
|
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
|
||||||
{
|
{
|
||||||
bool joyConPairConnected = false;
|
bool joyConPairConnected = false;
|
||||||
|
string fakeId = null;
|
||||||
|
|
||||||
if (SDL_IsGamepad(joystickInstanceId))
|
if (SDL_IsGamepad(joystickInstanceId))
|
||||||
{
|
{
|
||||||
|
|
@ -149,27 +197,40 @@ namespace Ryujinx.Input.SDL3
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
if (!SDL3JoyCon.IsJoyCon(joystickInstanceId))
|
||||||
_gamepadsIds.Add(joystickInstanceId, id);
|
|
||||||
|
|
||||||
if (SDL3JoyConPair.IsCombinable(_gamepadsIds))
|
|
||||||
{
|
{
|
||||||
// TODO - It appears that you can only have one joy con pair connected at a time?
|
_gamepadsIds.Add(joystickInstanceId, id);
|
||||||
// This was also the behavior before SDL3
|
}
|
||||||
_gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
|
else
|
||||||
uint fakeInstanceID = uint.MaxValue;
|
{
|
||||||
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id))
|
if (SDL3JoyConPair.IsCombinable(joystickInstanceId, _joyConsIds, out SDL_JoystickID match))
|
||||||
{
|
{
|
||||||
fakeInstanceID--;
|
_joyConsIds.Remove(match, out string matchId);
|
||||||
|
_linkedJoyConsIds.Add(joystickInstanceId, id);
|
||||||
|
_linkedJoyConsIds.Add(match, matchId);
|
||||||
|
|
||||||
|
uint fakeInstanceId = uint.MaxValue;
|
||||||
|
fakeId = SDL3JoyCon.IsLeftJoyCon(joystickInstanceId)
|
||||||
|
? $"{id}_{matchId}"
|
||||||
|
: $"{matchId}_{id}";
|
||||||
|
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceId, fakeId))
|
||||||
|
{
|
||||||
|
fakeInstanceId--;
|
||||||
|
}
|
||||||
|
_gamepadsInstanceIdsMapping.Add((SDL_JoystickID)fakeInstanceId, fakeId);
|
||||||
|
joyConPairConnected = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_joyConsIds.Add(joystickInstanceId, id);
|
||||||
}
|
}
|
||||||
joyConPairConnected = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGamepadConnected?.Invoke(id);
|
OnGamepadConnected?.Invoke(id);
|
||||||
if (joyConPairConnected)
|
if (joyConPairConnected)
|
||||||
{
|
{
|
||||||
OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
|
OnGamepadConnected?.Invoke(fakeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -194,9 +255,21 @@ namespace Ryujinx.Input.SDL3
|
||||||
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var gamepad in _joyConsIds)
|
||||||
|
{
|
||||||
|
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var gamepad in _linkedJoyConsIds)
|
||||||
|
{
|
||||||
|
OnGamepadDisconnected?.Invoke(gamepad.Value);
|
||||||
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
_gamepadsIds.Clear();
|
_gamepadsIds.Clear();
|
||||||
|
_joyConsIds.Clear();
|
||||||
|
_linkedJoyConsIds.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL3Driver.Instance.Dispose();
|
SDL3Driver.Instance.Dispose();
|
||||||
|
|
@ -215,11 +288,27 @@ namespace Ryujinx.Input.SDL3
|
||||||
|
|
||||||
public IGamepad GetGamepad(string id)
|
public IGamepad GetGamepad(string id)
|
||||||
{
|
{
|
||||||
if (id == SDL3JoyConPair.Id)
|
// joy-con pair ids is the combined ids of its parts which are split using a '_'
|
||||||
|
if (id.Contains('_'))
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
return SDL3JoyConPair.GetGamepad(_gamepadsIds);
|
string leftId = id.Split('_')[0];
|
||||||
|
string rightId = id.Split('_')[1];
|
||||||
|
|
||||||
|
SDL_JoystickID leftInstanceId = GetInstanceIdFromId(leftId);
|
||||||
|
SDL_JoystickID rightInstanceId = GetInstanceIdFromId(rightId);
|
||||||
|
|
||||||
|
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad(leftInstanceId);
|
||||||
|
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad(rightInstanceId);
|
||||||
|
|
||||||
|
if (leftGamepadHandle == null || rightGamepadHandle == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, leftId),
|
||||||
|
new SDL3JoyCon(rightGamepadHandle, rightId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,7 +321,7 @@ namespace Ryujinx.Input.SDL3
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix))
|
if (SDL3JoyCon.IsJoyCon(instanceId))
|
||||||
{
|
{
|
||||||
return new SDL3JoyCon(gamepadHandle, id);
|
return new SDL3JoyCon(gamepadHandle, id);
|
||||||
}
|
}
|
||||||
|
|
@ -249,6 +338,22 @@ namespace Ryujinx.Input.SDL3
|
||||||
yield return GetGamepad(gamepad.Value);
|
yield return GetGamepad(gamepad.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lock (_joyConsIds)
|
||||||
|
{
|
||||||
|
foreach (var gamepad in _joyConsIds)
|
||||||
|
{
|
||||||
|
yield return GetGamepad(gamepad.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_linkedJoyConsIds)
|
||||||
|
{
|
||||||
|
foreach (var gamepad in _linkedJoyConsIds)
|
||||||
|
{
|
||||||
|
yield return GetGamepad(gamepad.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -398,5 +398,15 @@ namespace Ryujinx.Input.SDL3
|
||||||
|
|
||||||
return SDL_GetGamepadButton(_gamepadHandle, button);
|
return SDL_GetGamepadButton(_gamepadHandle, button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsJoyCon(SDL_JoystickID gamepadsId)
|
||||||
|
{
|
||||||
|
return SDL_GetGamepadNameForID(gamepadsId) is LeftName or RightName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsLeftJoyCon(SDL_JoystickID gamepadsId)
|
||||||
|
{
|
||||||
|
return SDL_GetGamepadNameForID(gamepadsId) is LeftName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.Input.SDL3
|
||||||
public const string Id = "JoyConPair";
|
public const string Id = "JoyConPair";
|
||||||
string IGamepad.Id => Id;
|
string IGamepad.Id => Id;
|
||||||
|
|
||||||
public string Name => "* Nintendo Switch Joy-Con (L/R)";
|
public string Name => "Nintendo Switch Dual Joy-Con (L/R)";
|
||||||
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
|
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -96,44 +96,23 @@ namespace Ryujinx.Input.SDL3
|
||||||
right.SetTriggerThreshold(triggerThreshold);
|
right.SetTriggerThreshold(triggerThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsCombinable(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
public static bool IsCombinable(SDL_JoystickID joyCon1, Dictionary<SDL_JoystickID, string> joyConIds, out SDL_JoystickID match)
|
||||||
{
|
{
|
||||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
bool isLeft = SDL3JoyCon.IsLeftJoyCon(joyCon1);
|
||||||
return leftIndex >= 0 && rightIndex >= 0;
|
string matchName = isLeft ? SDL3JoyCon.RightName : SDL3JoyCon.LeftName;
|
||||||
}
|
match = 0;
|
||||||
|
|
||||||
private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
foreach (var joyConId in joyConIds.Keys)
|
||||||
{
|
|
||||||
Dictionary<string, SDL_JoystickID> gamepadNames = gamepadsIds
|
|
||||||
.Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName)
|
|
||||||
.Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key))
|
|
||||||
.ToDictionary();
|
|
||||||
SDL_JoystickID idx;
|
|
||||||
int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1;
|
|
||||||
int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1;
|
|
||||||
|
|
||||||
return (leftIndex, rightIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe static IGamepad GetGamepad(Dictionary<SDL_JoystickID, string> gamepadsIds)
|
|
||||||
{
|
|
||||||
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
|
|
||||||
|
|
||||||
if (leftIndex <= 0 || rightIndex <= 0)
|
|
||||||
{
|
{
|
||||||
return null;
|
if (SDL_GetGamepadNameForID(joyConId) == matchName)
|
||||||
|
{
|
||||||
|
match = joyConId;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex);
|
return false;
|
||||||
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex);
|
|
||||||
|
|
||||||
if (leftGamepadHandle == null || rightGamepadHandle == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]),
|
|
||||||
new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
_controller = 0;
|
_controller = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1)
|
if (Controllers.Count > 0 && _controller < Controllers.Count && _controller > -1)
|
||||||
{
|
{
|
||||||
ControllerType controller = Controllers[_controller].Type;
|
ControllerType controller = Controllers[_controller].Type;
|
||||||
|
|
||||||
|
|
@ -521,7 +521,17 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
|
|
||||||
if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1)
|
if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1)
|
||||||
{
|
{
|
||||||
Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
|
int controllerIndex = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType);
|
||||||
|
|
||||||
|
// Avalonia bug: setting a newly instanced ComboBox to 0
|
||||||
|
// causes the selected item to show up blank
|
||||||
|
// Workaround: set the box to 1 and then 0
|
||||||
|
if (controllerIndex == 0)
|
||||||
|
{
|
||||||
|
Controller = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller = controllerIndex;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -576,7 +586,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
DeviceList.Clear();
|
DeviceList.Clear();
|
||||||
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
|
Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled]));
|
||||||
|
|
||||||
int controllerNumber = 0;
|
|
||||||
foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
|
foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds)
|
||||||
{
|
{
|
||||||
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
|
using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id);
|
||||||
|
|
@ -593,6 +603,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
|
|
||||||
if (gamepad != null)
|
if (gamepad != null)
|
||||||
{
|
{
|
||||||
|
int controllerNumber = 0;
|
||||||
string name = GetUniqueGamepadName(gamepad, ref controllerNumber);
|
string name = GetUniqueGamepadName(gamepad, ref controllerNumber);
|
||||||
Devices.Add((DeviceType.Controller, id, name));
|
Devices.Add((DeviceType.Controller, id, name));
|
||||||
}
|
}
|
||||||
|
|
@ -950,8 +961,10 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
|
||||||
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
|
LoadConfiguration(); // configuration preload is required if the paired gamepad was disconnected but was changed to another gamepad
|
||||||
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
|
Device = Devices.ToList().FindIndex(d => d.Id == RevertDeviceId);
|
||||||
|
|
||||||
LoadDevice();
|
_isLoaded = false;
|
||||||
LoadConfiguration();
|
LoadConfiguration();
|
||||||
|
LoadDevice();
|
||||||
|
_isLoaded = true;
|
||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue