Compare commits

..

4 Commits

15 changed files with 139 additions and 40 deletions

View File

@@ -0,0 +1,58 @@
using Gommon;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Common.Helper
{
public class RefEvent<T>
{
public delegate void Handler(ref T arg);
private readonly Lock _subLock = new();
private readonly List<Handler> _subscriptions = [];
public bool HasSubscribers
{
get
{
lock (_subLock)
return _subscriptions.Count != 0;
}
}
public IReadOnlyList<Handler> Subscriptions
{
get
{
lock (_subLock)
return _subscriptions;
}
}
public void Add(Handler subscriber)
{
Guard.Require(subscriber, nameof(subscriber));
lock (_subLock)
_subscriptions.Add(subscriber);
}
public void Remove(Handler subscriber)
{
Guard.Require(subscriber, nameof(subscriber));
lock (_subLock)
_subscriptions.Remove(subscriber);
}
public void Clear()
{
lock (_subLock)
_subscriptions.Clear();
}
public void Call(ref T arg)
{
foreach (Handler subscription in Subscriptions)
subscription(ref arg);
}
}
}

View File

@@ -1,4 +1,5 @@
using Gommon; using Gommon;
using Ryujinx.Common.Helper;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Threading; using System.Threading;
@@ -55,7 +56,7 @@ namespace Ryujinx.Common.Utilities
{ {
_color = HsbToRgb((_color.GetHue() + Speed) / 360); _color = HsbToRgb((_color.GetHue() + Speed) / 360);
_updatedHandler.Call(_color.ToArgb()); _updatedHandler.Call(ref _color);
} }
} }
@@ -67,13 +68,13 @@ namespace Ryujinx.Common.Utilities
_color = Color.Blue; _color = Color.Blue;
} }
public static event Action<int> Updated public static event RefEvent<Color>.Handler Updated
{ {
add => _updatedHandler.Add(value); add => _updatedHandler.Add(value);
remove => _updatedHandler.Remove(value); remove => _updatedHandler.Remove(value);
} }
private static readonly Event<int> _updatedHandler = new(); private static readonly RefEvent<Color> _updatedHandler = new();
private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1) private static Color HsbToRgb(float hue, float saturation = 1, float brightness = 1)
{ {

View File

@@ -296,7 +296,7 @@ namespace Ryujinx.HLE.HOS
AddModsFromDirectory(mods, applicationDir, modMetadata); AddModsFromDirectory(mods, applicationDir, modMetadata);
} }
public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong applicationId) public static void QueryContentsDir(ModCache mods, DirectoryInfo contentsDir, ulong applicationId, ulong[] installedDlcs)
{ {
if (!contentsDir.Exists) if (!contentsDir.Exists)
{ {
@@ -311,6 +311,16 @@ namespace Ryujinx.HLE.HOS
{ {
QueryApplicationDir(mods, applicationDir, applicationId); QueryApplicationDir(mods, applicationDir, applicationId);
} }
foreach (ulong installedDlcId in installedDlcs)
{
DirectoryInfo dlcModDir = FindApplicationDir(contentsDir, $"{installedDlcId:x16}");
if (dlcModDir != null)
{
QueryApplicationDir(mods, dlcModDir, applicationId);
}
}
} }
private static int QueryCheatsDir(ModCache mods, DirectoryInfo cheatsDir) private static int QueryCheatsDir(ModCache mods, DirectoryInfo cheatsDir)
@@ -417,7 +427,7 @@ namespace Ryujinx.HLE.HOS
{ {
foreach ((ulong applicationId, ModCache cache) in modCaches) foreach ((ulong applicationId, ModCache cache) in modCaches)
{ {
QueryContentsDir(cache, searchDir, applicationId); QueryContentsDir(cache, searchDir, applicationId, Array.Empty<ulong>());
} }
return true; return true;

View File

@@ -2,8 +2,6 @@ using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Services.Hid;
using SDL2;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
@@ -12,7 +10,7 @@ using static SDL2.SDL;
namespace Ryujinx.Input.SDL2 namespace Ryujinx.Input.SDL2
{ {
class SDL2Gamepad : IGamepad public class SDL2Gamepad : IGamepad
{ {
private bool HasConfiguration => _configuration != null; private bool HasConfiguration => _configuration != null;
@@ -113,7 +111,7 @@ namespace Ryujinx.Input.SDL2
byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0; byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0) if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
Logger.Error?.Print(LogClass.Hid, "LED is not supported on this game controller."); Logger.Error?.Print(LogClass.Hid, "LED setting failed; probably in the middle of disconnecting.");
} }
private GamepadFeaturesFlag GetFeaturesFlag() private GamepadFeaturesFlag GetFeaturesFlag()

View File

@@ -1,13 +0,0 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="MenuItem.withCheckbox Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="MenuItem.withCheckbox ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
</Styles>

View File

@@ -218,6 +218,15 @@
<Setter Property="BorderBrush" <Setter Property="BorderBrush"
Value="{DynamicResource ThemeControlBorderColor}" /> Value="{DynamicResource ThemeControlBorderColor}" />
</Style> </Style>
<Style Selector="MenuItem.withCheckbox Viewbox#PART_IconPresenter">
<Setter Property="MaxHeight" Value="36" />
<Setter Property="MinHeight" Value="36" />
<Setter Property="MaxWidth" Value="36" />
<Setter Property="MinWidth" Value="36" />
</Style>
<Style Selector="MenuItem.withCheckbox ContentPresenter#PART_HeaderPresenter">
<Setter Property="Padding" Value="-10,0,0,0" />
</Style>
<Style Selector="TabItem > ScrollViewer"> <Style Selector="TabItem > ScrollViewer">
<Setter Property="Background" <Setter Property="Background"
Value="{DynamicResource ThemeBackgroundColor}" /> Value="{DynamicResource ThemeBackgroundColor}" />

View File

@@ -123,13 +123,11 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</AvaloniaResource> </AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" /> <AvaloniaResource Include="Assets\Styles\Styles.xaml" />
<AvaloniaResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="Assets\locales.json" /> <None Remove="Assets\locales.json" />
<None Remove="Assets\Styles\Themes.xaml" /> <None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Styles\CheckboxMenuItemStyle.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" /> <None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" /> <None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" /> <None Remove="Assets\Icons\Controller_JoyConRight.svg" />
@@ -151,7 +149,6 @@
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Assets\locales.json" /> <EmbeddedResource Include="Assets\locales.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" /> <EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Styles\CheckboxMenuItemStyle.axaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" /> <EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />

View File

@@ -16,6 +16,5 @@
<Application.Styles> <Application.Styles>
<sty:FluentAvaloniaTheme PreferUserAccentColor="True" PreferSystemTheme="False" /> <sty:FluentAvaloniaTheme PreferUserAccentColor="True" PreferSystemTheme="False" />
<StyleInclude Source="/Assets/Styles/Styles.xaml" /> <StyleInclude Source="/Assets/Styles/Styles.xaml" />
<StyleInclude Source="/Assets/Styles/CheckboxMenuItemStyle.axaml"/>
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -128,7 +128,11 @@ namespace Ryujinx.Ava.UI.Controls
public async void OpenModManager_Click(object sender, RoutedEventArgs args) public async void OpenModManager_Click(object sender, RoutedEventArgs args)
{ {
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await ModManagerWindow.Show(viewModel.SelectedApplication.Id, viewModel.SelectedApplication.Name); await ModManagerWindow.Show(
viewModel.SelectedApplication.Id,
viewModel.SelectedApplication.IdBase,
viewModel.ApplicationLibrary,
viewModel.SelectedApplication.Name);
} }
public async void PurgePtcCache_Click(object sender, RoutedEventArgs args) public async void PurgePtcCache_Click(object sender, RoutedEventArgs args)

View File

@@ -1,11 +1,10 @@
using Avalonia.Svg.Skia; using Avalonia.Svg.Skia;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Models.Input;
using Ryujinx.Ava.UI.Views.Input; using Ryujinx.Ava.UI.Views.Input;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.Views.Input; using Ryujinx.UI.Views.Input;
using System.Drawing;
namespace Ryujinx.Ava.UI.ViewModels.Input namespace Ryujinx.Ava.UI.ViewModels.Input
{ {
@@ -48,6 +47,23 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ParentModel = model; ParentModel = model;
model.NotifyChangesEvent += OnParentModelChanged; model.NotifyChangesEvent += OnParentModelChanged;
OnParentModelChanged(); OnParentModelChanged();
config.PropertyChanged += (_, args) =>
{
if (args.PropertyName is nameof(Config.UseRainbowLed))
{
if (Config is { UseRainbowLed: true, TurnOffLed: false, EnableLedChanging: true })
Rainbow.Updated += (ref Color color) => ParentModel.SelectedGamepad.SetLed((uint)color.ToArgb());
else
{
Rainbow.Reset();
if (Config.TurnOffLed)
ParentModel.SelectedGamepad.ClearLed();
else
ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32());
}
}
};
Config = config; Config = config;
} }

View File

@@ -23,6 +23,7 @@ using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Drawing;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@@ -63,7 +64,13 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
get => _selectedGamepad; get => _selectedGamepad;
private set private set
{ {
Rainbow.Reset();
_selectedGamepad = value; _selectedGamepad = value;
if (ConfigViewModel is ControllerInputViewModel { Config.UseRainbowLed: true })
Rainbow.Updated += (ref Color color) => _selectedGamepad.SetLed((uint)color.ToArgb());
OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed)); OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
} }
} }

View File

@@ -7,6 +7,7 @@ using Gommon;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@@ -29,6 +30,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _search; private string _search;
private readonly ulong _applicationId; private readonly ulong _applicationId;
private readonly ulong[] _installedDlcIds;
private readonly IStorageProvider _storageProvider; private readonly IStorageProvider _storageProvider;
private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
@@ -61,18 +63,23 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count); get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count);
} }
public ModManagerViewModel(ulong applicationId) public ModManagerViewModel(ulong applicationId, ulong applicationIdBase, ApplicationLibrary appLibrary)
{ {
_applicationId = applicationId; _applicationId = applicationId;
_installedDlcIds = appLibrary.DownloadableContents.Keys
.Where(x => x.TitleIdBase == applicationIdBase)
.Select(x => x.TitleId)
.ToArray();
_modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json"); _modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json");
_storageProvider = RyujinxApp.MainWindow.StorageProvider; _storageProvider = RyujinxApp.MainWindow.StorageProvider;
LoadMods(applicationId); LoadMods(applicationId, _installedDlcIds);
} }
private void LoadMods(ulong applicationId) private void LoadMods(ulong applicationId, ulong[] installedDlcIds)
{ {
Mods.Clear(); Mods.Clear();
SelectedMods.Clear(); SelectedMods.Clear();
@@ -84,7 +91,7 @@ namespace Ryujinx.Ava.UI.ViewModels
bool inSd = path == ModLoader.GetSdModsBasePath(); bool inSd = path == ModLoader.GetSdModsBasePath();
ModLoader.ModCache modCache = new(); ModLoader.ModCache modCache = new();
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId); ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId, _installedDlcIds);
foreach (ModLoader.Mod<DirectoryInfo> mod in modCache.RomfsDirs) foreach (ModLoader.Mod<DirectoryInfo> mod in modCache.RomfsDirs)
{ {
@@ -278,7 +285,7 @@ namespace Ryujinx.Ava.UI.ViewModels
File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true); File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true);
} }
LoadMods(_applicationId); LoadMods(_applicationId, _installedDlcIds);
} }
public async void Add() public async void Add()

View File

@@ -134,7 +134,12 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager); Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
Rainbow.Enable();
await Window.SettingsWindow.ShowDialog(Window); await Window.SettingsWindow.ShowDialog(Window);
Rainbow.Disable();
Rainbow.Reset();
Window.SettingsWindow = null; Window.SettingsWindow = null;

View File

@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.UI.Windows
ModLoader.ModCache mods = new(); ModLoader.ModCache mods = new();
ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue); ModLoader.QueryContentsDir(mods, new DirectoryInfo(Path.Combine(modsBasePath, "contents")), titleIdValue, []);
string currentCheatFile = string.Empty; string currentCheatFile = string.Empty;
string buildId = string.Empty; string buildId = string.Empty;

View File

@@ -6,6 +6,7 @@ using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models; using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities.AppLibrary;
using Ryujinx.Common.Helper; using Ryujinx.Common.Helper;
using System.Threading.Tasks; using System.Threading.Tasks;
using Button = Avalonia.Controls.Button; using Button = Avalonia.Controls.Button;
@@ -23,21 +24,21 @@ namespace Ryujinx.Ava.UI.Windows
InitializeComponent(); InitializeComponent();
} }
public ModManagerWindow(ulong titleId) public ModManagerWindow(ulong titleId, ulong titleIdBase, ApplicationLibrary applicationLibrary)
{ {
DataContext = ViewModel = new ModManagerViewModel(titleId); DataContext = ViewModel = new ModManagerViewModel(titleId, titleIdBase, applicationLibrary);
InitializeComponent(); InitializeComponent();
} }
public static async Task Show(ulong titleId, string titleName) public static async Task Show(ulong titleId, ulong titleIdBase, ApplicationLibrary appLibrary, string titleName)
{ {
ContentDialog contentDialog = new() ContentDialog contentDialog = new()
{ {
PrimaryButtonText = string.Empty, PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty, CloseButtonText = string.Empty,
Content = new ModManagerWindow(titleId), Content = new ModManagerWindow(titleId, titleIdBase, appLibrary),
Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowTitle], titleName, titleId.ToString("X16")), Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowTitle], titleName, titleId.ToString("X16")),
}; };