Merge pull request 'Settings' (#3) from 0-blu-ui-stuff into XC-ios-ht

Reviewed-on: MeloNX/MeloNX#3
This commit is contained in:
stossy11 2024-12-10 06:34:43 +00:00
commit 1735216de6

View File

@ -19,164 +19,253 @@ struct SettingsView: View {
] ]
@AppStorage("RyuDemoControls") var ryuDemo: Bool = false @AppStorage("RyuDemoControls") var ryuDemo: Bool = false
@AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false @AppStorage("MTL_HUD_ENABLED") var metalHUDEnabled: Bool = false
@State private var showResolutionInfo = false
@State private var searchText = ""
var filteredMemoryModes: [(String, String)] {
guard !searchText.isEmpty else { return memoryManagerModes }
return memoryManagerModes.filter { $0.1.localizedCaseInsensitiveContains(searchText) }
}
var body: some View { var body: some View {
ScrollView { NavigationStack {
VStack { List {
Section(header: Title("Graphics and Performance")) { // Graphics & Performance
Toggle("Ryujinx Fullscreen", isOn: $config.fullscreen) Section {
Toggle("Disable Shader Cache", isOn: $config.disableShaderCache) Toggle(isOn: $config.fullscreen) {
Toggle("Enable Texture Recompression", isOn: $config.enableTextureRecompression) labelWithIcon("Fullscreen", iconName: "rectangle.expand.vertical")
Toggle("Disable Docked Mode", isOn: $config.disableDockedMode) }
Resolution(value: $config.resscale) .tint(.blue)
Toggle("Enable Metal HUD", isOn: $metalHUDEnabled) Toggle(isOn: $config.disableShaderCache) {
.onChange(of: metalHUDEnabled) { newValue in labelWithIcon("Disable Shader Cache", iconName: "memorychip")
if newValue { }
MTLHud.shared.enable() .tint(.blue)
} else {
MTLHud.shared.disable() Toggle(isOn: $config.enableTextureRecompression) {
labelWithIcon("Texture Recompression", iconName: "rectangle.compress.vertical")
}
.tint(.blue)
Toggle(isOn: $config.disableDockedMode) {
labelWithIcon("Disable Docked Mode", iconName: "dock.rectangle")
}
.tint(.blue)
VStack(alignment: .leading, spacing: 10) {
HStack {
labelWithIcon("Resolution Scale", iconName: "magnifyingglass")
.font(.headline)
Spacer()
Button {
showResolutionInfo.toggle()
} label: {
Image(systemName: "info.circle")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.help("Learn more about Resolution Scale")
.alert(isPresented: $showResolutionInfo) {
Alert(
title: Text("Resolution Scale"),
message: Text("Adjust the internal rendering resolution. Higher values improve visuals but may reduce performance."),
dismissButton: .default(Text("OK"))
)
} }
} }
Slider(value: $config.resscale, in: 0.1...3.0, step: 0.1) {
Text("Resolution Scale")
} minimumValueLabel: {
Text("0.1x")
.font(.footnote)
.foregroundColor(.secondary)
} maximumValueLabel: {
Text("3.0x")
.font(.footnote)
.foregroundColor(.secondary)
}
Text("\(config.resscale, specifier: "%.2f")x")
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 8)
Toggle(isOn: $metalHUDEnabled) {
labelWithIcon("Metal HUD", iconName: "speedometer")
}
.tint(.blue)
.onChange(of: metalHUDEnabled) { newValue in
// Preserves original functionality
if newValue {
MTLHud.shared.enable()
} else {
MTLHud.shared.disable()
}
}
} header: {
Text("Graphics & Performance")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Fine-tune graphics and performance to suit your device and preferences.")
} }
Section(header: Title("Input Settings")) { // Input Settings
Toggle("List Input IDs", isOn: $config.listinputids) Section {
Toggle("Ryujinx Demo On-Screen Controller", isOn: $ryuDemo) Toggle(isOn: $config.listinputids) {
// Toggle("Host Mapped Memory", isOn: $config.hostMappedMemory) labelWithIcon("List Input IDs", iconName: "list.bullet")
}
.tint(.blue)
Toggle(isOn: $ryuDemo) {
labelWithIcon("On-Screen Controller (Demo)", iconName: "hand.draw")
}
.tint(.blue)
} header: {
Text("Input Settings")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Configure input devices and on-screen controls for easier navigation and play.")
} }
Section(header: Title("Logging Settings")) { // Logging
Toggle("Enable Debug Logs", isOn: $config.debuglogs) Section {
Toggle("Enable Trace Logs", isOn: $config.tracelogs) Toggle(isOn: $config.debuglogs) {
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
}
.tint(.blue)
Toggle(isOn: $config.tracelogs) {
labelWithIcon("Trace Logs", iconName: "waveform.path")
}
.tint(.blue)
} header: {
Text("Logging")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Enable logs for troubleshooting or keep them off for a cleaner experience.")
} }
Section(header: Title("CPU Mode")) {
HStack { // CPU Mode
Spacer() Section {
Picker("Memory Manager Mode", selection: $config.memoryManagerMode) { if filteredMemoryModes.isEmpty {
ForEach(memoryManagerModes, id: \.0) { key, displayName in Text("No matches for \"\(searchText)\"")
.foregroundColor(.secondary)
} else {
Picker(selection: $config.memoryManagerMode) {
ForEach(filteredMemoryModes, id: \.0) { key, displayName in
Text(displayName).tag(key) Text(displayName).tag(key)
} }
} label: {
labelWithIcon("Memory Manager Mode", iconName: "gearshape")
} }
.pickerStyle(MenuPickerStyle()) // Dropdown style
} }
} header: {
Text("CPU Mode")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("Select how memory is managed. 'Host (fast)' is best for most users.")
} }
// Advanced
Section {
Section(header: Title("Additional Settings")) { DisclosureGroup {
//TextField("Game Path", text: $config.gamepath) HStack {
labelWithIcon("Page Size", iconName: "textformat.size")
Text("PageSize \(String(Int(getpagesize())))") Spacer()
TextField("Additional Arguments", text: Binding( Text("\(String(Int(getpagesize())))")
get: { .foregroundColor(.secondary)
config.additionalArgs.joined(separator: ", ")
},
set: { newValue in
config.additionalArgs = newValue.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
} }
))
TextField("Additional Arguments", text: Binding(
get: {
config.additionalArgs.joined(separator: ", ")
},
set: { newValue in
config.additionalArgs = newValue
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespaces) }
}
))
.textInputAutocapitalization(.none)
.disableAutocorrection(true)
} label: {
Text("Advanced Options")
}
} header: {
Text("Advanced")
.font(.title3.weight(.semibold))
.textCase(nil)
.headerProminence(.increased)
} footer: {
Text("For advanced users. Adjust page size or add custom arguments for experimental features.")
} }
} }
.padding() // Searching memory modes
} .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
.onAppear { .navigationTitle("Settings")
if let configs = loadSettings() { .navigationBarTitleDisplayMode(.inline)
self.config = configs .listStyle(.insetGrouped)
print(configs) // Load and save settings to preserve original functionality
.onAppear {
if let configs = loadSettings() {
self.config = configs
}
}
.onChange(of: config) { _ in
saveSettings()
} }
} }
.onChange(of: config) { newValue in .navigationViewStyle(.stack)
print(newValue)
saveSettings()
}
} }
func saveSettings() { func saveSettings() {
do { do {
let encoder = JSONEncoder() let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // Optional: Makes the JSON easier to read encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(config) let data = try encoder.encode(config)
let jsonString = String(data: data, encoding: .utf8) let jsonString = String(data: data, encoding: .utf8)
// Save to UserDefaults
UserDefaults.standard.set(jsonString, forKey: "config") UserDefaults.standard.set(jsonString, forKey: "config")
print("Settings saved successfully!")
} catch { } catch {
print("Failed to save settings: \(error)") print("Failed to save settings: \(error)")
} }
} }
}
// Original loadSettings function assumed to exist
func loadSettings() -> Ryujinx.Configuration? {
struct Resolution: View { guard let jsonString = UserDefaults.standard.string(forKey: "config"),
@Binding var value: Float let data = jsonString.data(using: .utf8) else {
return nil
var body: some View { }
HStack { do {
Text("Resolution Scale (Custom):") let decoder = JSONDecoder()
Spacer() let configs = try decoder.decode(Ryujinx.Configuration.self, from: data)
return configs
Button(action: { } catch {
if value > 0.1 { // Prevent values going below 0.1 print("Failed to load settings: \(error)")
value -= 0.10 return nil
value = round(value * 1000) / 1000 // Round to two decimal places
}
print(value)
}) {
Text("-")
.frame(width: 30, height: 30)
.background(Color.gray.opacity(0.2))
.cornerRadius(5)
}
TextField("", value: $value, formatter: NumberFormatter.floatFormatter)
.multilineTextAlignment(.center)
.frame(width: 60)
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.decimalPad)
Button(action: {
value += 0.10
value = round(value * 1000) / 1000 // Round to two decimal places
print(value)
}) {
Text("+")
.frame(width: 30, height: 30)
.background(Color.gray.opacity(0.2))
.cornerRadius(5)
}
} }
} }
}
extension NumberFormatter {
static var floatFormatter: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.allowsFloats = true
return formatter
}
}
struct Title: View {
let string: String
init(_ string: String) { @ViewBuilder
self.string = string private func labelWithIcon(_ text: String, iconName: String) -> some View {
} HStack(spacing: 8) {
Image(systemName: iconName)
var body: some View { .symbolRenderingMode(.hierarchical)
VStack { .foregroundStyle(.blue)
Text(string) Text(text)
.font(.title2)
Divider()
} }
.font(.body)
} }
} }