From 19a949d0bf02fd5850d1222b2d51c3bc3e0e5670 Mon Sep 17 00:00:00 2001
From: Isaac Marovitz <42140194+IsaacMarovitz@users.noreply.github.com>
Date: Mon, 25 Dec 2023 05:57:14 +0000
Subject: [PATCH] Ava UI: Fix crash when clicking on a cheat's name (#5860)

* Fix crash

* Remove nullable

* Hide BuildId for child nodes

* Fix warning

* Fix charset
---
 src/Ryujinx.Ava/UI/Models/CheatModel.cs       | 40 -------------
 src/Ryujinx.Ava/UI/Models/CheatNode.cs        | 57 +++++++++++++++++++
 src/Ryujinx.Ava/UI/Models/CheatsList.cs       | 51 -----------------
 src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml  | 24 ++------
 .../UI/Windows/CheatWindow.axaml.cs           | 14 ++---
 5 files changed, 70 insertions(+), 116 deletions(-)
 delete mode 100644 src/Ryujinx.Ava/UI/Models/CheatModel.cs
 create mode 100644 src/Ryujinx.Ava/UI/Models/CheatNode.cs
 delete mode 100644 src/Ryujinx.Ava/UI/Models/CheatsList.cs

diff --git a/src/Ryujinx.Ava/UI/Models/CheatModel.cs b/src/Ryujinx.Ava/UI/Models/CheatModel.cs
deleted file mode 100644
index 3917d4b6d..000000000
--- a/src/Ryujinx.Ava/UI/Models/CheatModel.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using Ryujinx.Ava.UI.ViewModels;
-using System;
-
-namespace Ryujinx.Ava.UI.Models
-{
-    public class CheatModel : BaseModel
-    {
-        private bool _isEnabled;
-
-        public event EventHandler<bool> EnableToggled;
-
-        public CheatModel(string name, string buildId, bool isEnabled)
-        {
-            Name = name;
-            BuildId = buildId;
-            IsEnabled = isEnabled;
-        }
-
-        public bool IsEnabled
-        {
-            get => _isEnabled;
-            set
-            {
-                _isEnabled = value;
-
-                EnableToggled?.Invoke(this, _isEnabled);
-
-                OnPropertyChanged();
-            }
-        }
-
-        public string BuildId { get; }
-
-        public string BuildIdKey => $"{BuildId}-{Name}";
-
-        public string Name { get; }
-
-        public string CleanName => Name[1..^7];
-    }
-}
diff --git a/src/Ryujinx.Ava/UI/Models/CheatNode.cs b/src/Ryujinx.Ava/UI/Models/CheatNode.cs
new file mode 100644
index 000000000..8e9aee254
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Models/CheatNode.cs
@@ -0,0 +1,57 @@
+using Ryujinx.Ava.UI.ViewModels;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace Ryujinx.Ava.UI.Models
+{
+    public class CheatNode : BaseModel
+    {
+        private bool _isEnabled = false;
+        public ObservableCollection<CheatNode> SubNodes { get; } = new();
+        public string CleanName => Name[1..^7];
+        public string BuildIdKey => $"{BuildId}-{Name}";
+        public bool IsRootNode { get; }
+        public string Name { get; }
+        public string BuildId { get; }
+        public string Path { get; }
+        public bool IsEnabled
+        {
+            get
+            {
+                if (SubNodes.Count > 0)
+                {
+                    return SubNodes.ToList().TrueForAll(x => x.IsEnabled);
+                }
+
+                return _isEnabled;
+            }
+            set
+            {
+                foreach (var cheat in SubNodes)
+                {
+                    cheat.IsEnabled = value;
+                    cheat.OnPropertyChanged();
+                }
+
+                _isEnabled = value;
+            }
+        }
+
+        public CheatNode(string name, string buildId, string path, bool isRootNode, bool isEnabled = false)
+        {
+            Name = name;
+            BuildId = buildId;
+            Path = path;
+            IsEnabled = isEnabled;
+            IsRootNode = isRootNode;
+
+            SubNodes.CollectionChanged += CheatsList_CollectionChanged;
+        }
+
+        private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            OnPropertyChanged(nameof(IsEnabled));
+        }
+    }
+}
diff --git a/src/Ryujinx.Ava/UI/Models/CheatsList.cs b/src/Ryujinx.Ava/UI/Models/CheatsList.cs
deleted file mode 100644
index abe8e4df9..000000000
--- a/src/Ryujinx.Ava/UI/Models/CheatsList.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Collections.Specialized;
-using System.ComponentModel;
-using System.Linq;
-
-namespace Ryujinx.Ava.UI.Models
-{
-    public class CheatsList : ObservableCollection<CheatModel>
-    {
-        public CheatsList(string buildId, string path)
-        {
-            BuildId = buildId;
-            Path = path;
-
-            CollectionChanged += CheatsList_CollectionChanged;
-        }
-
-        public string BuildId { get; }
-        public string Path { get; }
-
-        public bool IsEnabled
-        {
-            get
-            {
-                return this.ToList().TrueForAll(x => x.IsEnabled);
-            }
-            set
-            {
-                foreach (var cheat in this)
-                {
-                    cheat.IsEnabled = value;
-                }
-
-                OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
-            }
-        }
-
-        private void CheatsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
-        {
-            if (e.Action == NotifyCollectionChangedAction.Add)
-            {
-                (e.NewItems[0] as CheatModel).EnableToggled += Item_EnableToggled;
-            }
-        }
-
-        private void Item_EnableToggled(object sender, bool e)
-        {
-            OnPropertyChanged(new PropertyChangedEventArgs(nameof(IsEnabled)));
-        }
-    }
-}
diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
index 8a5da5cc2..57d5f7eff 100644
--- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
@@ -86,28 +86,16 @@
                         </Style>
                     </Styles>
                 </TreeView.Styles>
-                <TreeView.DataTemplates>
-                    <TreeDataTemplate DataType="model:CheatsList" ItemsSource="{Binding}">
+                <TreeView.ItemTemplate>
+                    <TreeDataTemplate ItemsSource="{Binding SubNodes}">
                         <StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
                             <CheckBox MinWidth="20" IsChecked="{Binding IsEnabled}" />
-                            <TextBlock Width="150" Text="{Binding BuildId}" />
-                            <TextBlock Text="{Binding Path}" />
+                            <TextBlock Width="150" Text="{Binding CleanName}" IsVisible="{Binding !IsRootNode}" />
+                            <TextBlock Width="150" Text="{Binding BuildId}" IsVisible="{Binding IsRootNode}" />
+                            <TextBlock Text="{Binding Path}" IsVisible="{Binding IsRootNode}" />
                         </StackPanel>
                     </TreeDataTemplate>
-                    <DataTemplate x:DataType="model:CheatModel">
-                        <StackPanel
-                            Margin="0"
-                            HorizontalAlignment="Left"
-                            Orientation="Horizontal">
-                            <CheckBox
-                                MinWidth="20"
-                                Margin="5,0"
-                                Padding="0"
-                                IsChecked="{Binding IsEnabled}" />
-                            <TextBlock VerticalAlignment="Center" Text="{Binding CleanName}" />
-                        </StackPanel>
-                    </DataTemplate>
-                </TreeView.DataTemplates>
+                </TreeView.ItemTemplate>
             </TreeView>
         </Border>
         <DockPanel
diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
index 34444934a..c2de67ab2 100644
--- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Ava.UI.Windows
         private readonly string _enabledCheatsPath;
         public bool NoCheatsFound { get; }
 
-        public AvaloniaList<CheatsList> LoadedCheats { get; }
+        public AvaloniaList<CheatNode> LoadedCheats { get; }
 
         public string Heading { get; }
         public string BuildId { get; }
@@ -33,7 +33,7 @@ namespace Ryujinx.Ava.UI.Windows
 
         public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
         {
-            LoadedCheats = new AvaloniaList<CheatsList>();
+            LoadedCheats = new AvaloniaList<CheatNode>();
 
             Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
             BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
@@ -62,7 +62,7 @@ namespace Ryujinx.Ava.UI.Windows
             string currentCheatFile = string.Empty;
             string buildId = string.Empty;
 
-            CheatsList currentGroup = null;
+            CheatNode currentGroup = null;
 
             foreach (var cheat in mods.Cheats)
             {
@@ -72,13 +72,13 @@ namespace Ryujinx.Ava.UI.Windows
                     string parentPath = currentCheatFile.Replace(titleModsPath, "");
 
                     buildId = Path.GetFileNameWithoutExtension(currentCheatFile).ToUpper();
-                    currentGroup = new CheatsList(buildId, parentPath);
+                    currentGroup = new CheatNode("", buildId, parentPath, true);
 
                     LoadedCheats.Add(currentGroup);
                 }
 
-                var model = new CheatModel(cheat.Name, buildId, enabled.Contains($"{buildId}-{cheat.Name}"));
-                currentGroup?.Add(model);
+                var model = new CheatNode(cheat.Name, buildId, "", false, enabled.Contains($"{buildId}-{cheat.Name}"));
+                currentGroup?.SubNodes.Add(model);
 
                 cheatAdded++;
             }
@@ -104,7 +104,7 @@ namespace Ryujinx.Ava.UI.Windows
 
             foreach (var cheats in LoadedCheats)
             {
-                foreach (var cheat in cheats)
+                foreach (var cheat in cheats.SubNodes)
                 {
                     if (cheat.IsEnabled)
                     {