Edit Settings, Make Loading View hide after game has loaded

This commit is contained in:
Stossy11 2025-01-10 20:55:06 +11:00
parent 71551adf2d
commit 09a757c445
8 changed files with 182 additions and 93 deletions

View File

@ -634,6 +634,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
GCC_OPTIMIZATION_LEVEL = fast; GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -806,6 +807,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 0.0.8; MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
@ -845,6 +850,7 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
GCC_OPTIMIZATION_LEVEL = fast; GCC_OPTIMIZATION_LEVEL = fast;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -1017,6 +1023,10 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
); );
MARKETING_VERSION = 0.0.8; MARKETING_VERSION = 0.0.8;
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key> <key>Ryujinx.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>1</integer> <integer>2</integer>
</dict> </dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key> <key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>1</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -152,9 +152,9 @@ class Ryujinx {
args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode]) args.append(contentsOf: ["--memory-manager-mode", config.memoryManagerMode])
// args.append(contentsOf: ["--exclusive-fullscreen", String(config.fullscreen)]) args.append(contentsOf: ["--exclusive-fullscreen", String(true)])
// args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"]) args.append(contentsOf: ["--exclusive-fullscreen-width", "\(Int(UIScreen.main.bounds.width))"])
// args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"]) args.append(contentsOf: ["--exclusive-fullscreen-height", "\(Int(UIScreen.main.bounds.height))"])
// We don't need this. Ryujinx should handle it fine :3 // We don't need this. Ryujinx should handle it fine :3
if config.fullscreen { if config.fullscreen {
@ -174,10 +174,11 @@ class Ryujinx {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)]) args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
} }
if config.disableShaderCache { if !config.disableShaderCache { // same with disableShaderCache
args.append("--disable-shader-cache") args.append("--disable-shader-cache")
} }
if config.disableDockedMode {
if !config.disableDockedMode { // disableDockedMode is actually enableDockedMode, i just have flipped it around in the settings page to make it easier to understand :3
args.append("--disable-docked-mode") args.append("--disable-docked-mode")
} }
if config.enableTextureRecompression { if config.enableTextureRecompression {

View File

@ -22,7 +22,6 @@ struct MoltenVKSettings: Codable, Hashable {
struct ContentView: View { struct ContentView: View {
// MARK: - Properties // MARK: - Properties
@State private var theWindow: UIWindow? @State private var theWindow: UIWindow?
@State private var virtualController: GCVirtualController?
@State private var game: Game? @State private var game: Game?
@State private var controllersList: [Controller] = [] @State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = [] @State private var currentControllers: [Controller] = []
@ -37,6 +36,11 @@ struct ContentView: View {
@AppStorage("quit") var quit: Bool = false @AppStorage("quit") var quit: Bool = false
@State var quits: Bool = false @State var quits: Bool = false
@State private var clumpOffset: CGFloat = -100
private let clumpWidth: CGFloat = 100
private let animationDuration: Double = 1.0
@State private var isAnimating = false
@State var isLoading = true
// MARK: - Initialization // MARK: - Initialization
init() { init() {
@ -58,18 +62,27 @@ struct ContentView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
if let game, quits == false { if let game, quits == false {
emulationView if isLoading {
.onAppear() { emulationView
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in .onAppear() {
timer.invalidate() Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
quits = quit
if quits {
quit = false
timer.invalidate() timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
} }
} }
} else {
VStack {
} }
.onAppear() {
isAnimating = false
}
}
} else { } else {
mainMenuView mainMenuView
.onAppear() { .onAppear() {
@ -81,15 +94,80 @@ struct ContentView: View {
// MARK: - View Components // MARK: - View Components
private var emulationView: some View { private var emulationView: some View {
ZStack { GeometryReader { screenGeometry in
ZStack {
} HStack(spacing: screenGeometry.size.width * 0.04) {
.onAppear { if let icon = game?.icon {
Image(uiImage: icon)
setupEmulation() .resizable()
.frame(
width: min(screenGeometry.size.width * 0.25, 250),
height: min(screenGeometry.size.width * 0.25, 250)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 5)
}
VStack(alignment: .leading, spacing: screenGeometry.size.height * 0.015) {
Text("Loading \(game?.titleName ?? "Game")")
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
.foregroundColor(.white)
GeometryReader { geometry in
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
ZStack(alignment: .leading) {
// Background track
Rectangle()
.cornerRadius(10)
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.gray.opacity(0.3))
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
// Animated loading bar
Rectangle()
.cornerRadius(10)
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.blue)
.shadow(color: .blue.opacity(0.5), radius: 4, x: 0, y: 2)
.offset(x: isAnimating ? containerWidth : -clumpWidth)
.animation(
Animation.linear(duration: 1.0)
.repeatForever(autoreverses: false),
value: isAnimating
)
}
.clipShape(RoundedRectangle(cornerRadius: 16))
.onAppear {
isAnimating = true
setupEmulation()
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if get_current_fps() != 0 {
isLoading = false
isAnimating = false
timer.invalidate()
}
print(get_current_fps())
}
}
}
.frame(height: min(screenGeometry.size.height * 0.015, 12))
.frame(width: min(screenGeometry.size.width * 0.35, 350))
}
}
.padding(.horizontal, screenGeometry.size.width * 0.06)
.padding(.vertical, screenGeometry.size.height * 0.05)
.position(
x: screenGeometry.size.width / 2,
y: screenGeometry.size.height * 0.5
)
} }
}
} }
private var mainMenuView: some View { private var mainMenuView: some View {
MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller) MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
.onAppear() { .onAppear() {
@ -116,7 +194,6 @@ struct ContentView: View {
} }
private func setupEmulation() { private func setupEmulation() {
virtualController?.disconnect()
patchMakeKeyAndVisible() patchMakeKeyAndVisible()
if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) { if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) {
@ -203,6 +280,8 @@ struct ContentView: View {
print("Error: \(error.localizedDescription)") print("Error: \(error.localizedDescription)")
} }
} }
private func setMoltenVKSettings() { private func setMoltenVKSettings() {

View File

@ -140,7 +140,7 @@ struct GameLibraryView: View {
Button { Button {
var game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion) let game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion)
self.startemu = game self.startemu = game
} label: { } label: {
@ -165,7 +165,7 @@ struct GameLibraryView: View {
Text("Show MeloNX Folder") Text("Show MeloNX Folder")
} }
} label: { } label: {
Image(systemName: "ellipsis") Image(systemName: "ellipsis.circle")
.foregroundColor(.blue) .foregroundColor(.blue)
} }
@ -349,7 +349,6 @@ struct GameLibraryView: View {
} }
} }
// Make sure your Game model conforms to Codable
extension Game: Codable { extension Game: Codable {
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case titleName, titleId, developer, version, fileURL case titleName, titleId, developer, version, fileURL

View File

@ -52,7 +52,7 @@ struct SettingsView: View {
.tint(.blue) .tint(.blue)
Toggle(isOn: $config.disableShaderCache) { Toggle(isOn: $config.disableShaderCache) {
labelWithIcon("Disable Shader Cache", iconName: "memorychip") labelWithIcon("Shader Cache", iconName: "memorychip")
} }
.tint(.blue) .tint(.blue)
@ -62,7 +62,7 @@ struct SettingsView: View {
.tint(.blue) .tint(.blue)
Toggle(isOn: $config.disableDockedMode) { Toggle(isOn: $config.disableDockedMode) {
labelWithIcon("Disable Docked Mode", iconName: "dock.rectangle") labelWithIcon("Docked Mode", iconName: "dock.rectangle")
} }
.tint(.blue) .tint(.blue)

View File

@ -1306,7 +1306,7 @@ namespace Ryujinx.Headless.SDL2
options.IgnoreMissingServices, options.IgnoreMissingServices,
options.AspectRatio, options.AspectRatio,
options.AudioVolume, options.AudioVolume,
options.UseHypervisor ?? true, options.UseHypervisor ?? false,
options.MultiplayerLanInterfaceId, options.MultiplayerLanInterfaceId,
Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm); Common.Configuration.Multiplayer.MultiplayerMode.LdnMitm);
@ -1499,75 +1499,75 @@ namespace Ryujinx.Headless.SDL2
return new FileStream(safeHandle, FileAccess.ReadWrite); return new FileStream(safeHandle, FileAccess.ReadWrite);
} }
public class GameInfo public class GameInfo
{
public double FileSize;
public string? TitleName;
public string? TitleId;
public string? Developer;
public string? Version;
public byte[]? Icon;
}
public unsafe struct GameInfoNative
{
public ulong FileSize;
public fixed byte TitleName[512];
public ulong TitleId;
public fixed byte Developer[256];
public uint Version;
public byte* ImageData; // Changed to pointer
public uint ImageSize; // Actual size of image data
public GameInfoNative(ulong fileSize, string titleName, ulong titleId, string developer, uint version, byte[] imageData)
{
FileSize = fileSize;
TitleId = titleId;
Version = version;
fixed (byte* developerPtr = Developer)
fixed (byte* titleNamePtr = TitleName)
{ {
CopyStringToFixedArray(titleName, titleNamePtr, 512); public double FileSize;
CopyStringToFixedArray(developer, developerPtr, 256); public string? TitleName;
public string? TitleId;
public string? Developer;
public string? Version;
public byte[]? Icon;
} }
if (imageData == null || imageData.Length > 4096 * 4096) public unsafe struct GameInfoNative
{ {
// throw new ArgumentException("Image data must not exceed 4 MB."); public ulong FileSize;
ImageSize = (uint)0; public fixed byte TitleName[512];
ImageData = null; public ulong TitleId;
} public fixed byte Developer[256];
else public uint Version;
{ public byte* ImageData;
ImageSize = (uint)imageData.Length; public uint ImageSize;
ImageData = (byte*)Marshal.AllocHGlobal(imageData.Length);
Marshal.Copy(imageData, 0, (IntPtr)ImageData, imageData.Length);
}
}
// Don't forget to free the allocated memory public GameInfoNative(ulong fileSize, string titleName, ulong titleId, string developer, uint version, byte[] imageData)
public void Dispose() {
{ FileSize = fileSize;
if (ImageData != null) TitleId = titleId;
{ Version = version;
Marshal.FreeHGlobal((IntPtr)ImageData);
ImageData = null;
}
}
private static void CopyStringToFixedArray(string source, byte* destination, int length)
{
var span = new Span<byte>(destination, length);
Encoding.UTF8.GetBytes(source, span);
}
private static void CopyArrayToFixedArray(byte[] source, byte* destination, int maxLength) fixed (byte* developerPtr = Developer)
{ fixed (byte* titleNamePtr = TitleName)
var span = new Span<byte>(destination, maxLength); {
source.AsSpan().CopyTo(span); CopyStringToFixedArray(titleName, titleNamePtr, 512);
CopyStringToFixedArray(developer, developerPtr, 256);
}
if (imageData == null || imageData.Length > 4096 * 4096)
{
// throw new ArgumentException("Image data must not exceed 4 MB.");
ImageSize = (uint)0;
ImageData = null;
}
else
{
ImageSize = (uint)imageData.Length;
ImageData = (byte*)Marshal.AllocHGlobal(imageData.Length);
Marshal.Copy(imageData, 0, (IntPtr)ImageData, imageData.Length);
}
}
// Don't forget to free the allocated memory
public void Dispose()
{
if (ImageData != null)
{
Marshal.FreeHGlobal((IntPtr)ImageData);
ImageData = null;
}
}
private static void CopyStringToFixedArray(string source, byte* destination, int length)
{
var span = new Span<byte>(destination, length);
Encoding.UTF8.GetBytes(source, span);
}
private static void CopyArrayToFixedArray(byte[] source, byte* destination, int maxLength)
{
var span = new Span<byte>(destination, maxLength);
source.AsSpan().CopyTo(span);
}
} }
} }
}
} }