More adjustments to UI

This commit is contained in:
CamilleLaVey
2026-09-09 04:00:45 -04:00
parent ffc431848c
commit d1df35a720
20 changed files with 643 additions and 152 deletions
@@ -298,6 +298,7 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
steps: Int,
selectedStep: Int,
describe: (Int) -> String,
onCommitted: (Int) -> Unit,
onChanged: (Int) -> Unit
) {
val inflater = LayoutInflater.from(emulationFragment.requireContext())
@@ -327,14 +328,20 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
}
}
var pressedValue = slider.value
slider.setOnTouchListener { _, event ->
val drawer = emulationFragment.view?.findViewById<DrawerLayout>(R.id.drawer_layout)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
drawer?.requestDisallowInterceptTouchEvent(true)
pressedValue = slider.value
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
drawer?.requestDisallowInterceptTouchEvent(false)
if (slider.value != pressedValue) {
onCommitted(slider.value.toInt())
}
}
}
false
@@ -389,6 +396,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
fun addEffectPicker(
container: ViewGroup,
choices: List<String>,
hasEffects: Boolean,
onRemoveAll: () -> Unit,
onPicked: (Int) -> Unit
) {
val context = emulationFragment.requireContext()
@@ -398,6 +407,10 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
val button = itemView.findViewById<com.google.android.material.button.MaterialButton>(
R.id.add_button
)
val removeButton =
itemView.findViewById<com.google.android.material.button.MaterialButton>(
R.id.remove_all_button
)
val choiceGroup = itemView.findViewById<RadioGroup>(R.id.add_choices)
choices.forEachIndexed { index, name ->
@@ -413,22 +426,27 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
choiceGroup.addView(radioButton)
}
val closedBackground = MaterialColors.getColor(
button,
com.google.android.material.R.attr.colorSecondaryContainer
)
val closedForeground = MaterialColors.getColor(
button,
com.google.android.material.R.attr.colorOnSecondaryContainer
)
val openBackground = MaterialColors.getColor(
button,
var closedLabel = R.string.post_processing_add
if (hasEffects) {
closedLabel = R.string.post_processing_open_list
removeButton.visibility = View.VISIBLE
}
button.setText(closedLabel)
val removeBackground = MaterialColors.getColor(
removeButton,
com.google.android.material.R.attr.colorErrorContainer
)
val openForeground = MaterialColors.getColor(
button,
val removeForeground = MaterialColors.getColor(
removeButton,
com.google.android.material.R.attr.colorOnErrorContainer
)
removeButton.backgroundTintList = ColorStateList.valueOf(removeBackground)
removeButton.setTextColor(removeForeground)
removeButton.iconTint = ColorStateList.valueOf(removeForeground)
removeButton.setOnClickListener {
onRemoveAll()
}
val slide = context.resources.displayMetrics.density * 24.0f
@@ -451,11 +469,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
}
.start()
button.text = context.getString(R.string.post_processing_hide_effects)
button.setText(R.string.post_processing_close_list)
button.setIconResource(R.drawable.ic_clear)
button.backgroundTintList = ColorStateList.valueOf(openBackground)
button.setTextColor(openForeground)
button.iconTint = ColorStateList.valueOf(openForeground)
} else {
choiceGroup.animate()
.alpha(0.0f)
@@ -466,11 +481,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
}
.start()
button.text = context.getString(R.string.post_processing_add)
button.setText(closedLabel)
button.setIconResource(R.drawable.ic_add)
button.backgroundTintList = ColorStateList.valueOf(closedBackground)
button.setTextColor(closedForeground)
button.iconTint = ColorStateList.valueOf(closedForeground)
}
}
@@ -494,8 +506,9 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
switchView.isChecked = NativePostProcessing.isEnabled()
switchView.setOnCheckedChangeListener { _, checked ->
NativePostProcessing.setEnabled(checked)
saveSettings()
emulationFragment.editPostProcessing {
NativePostProcessing.setEnabled(checked)
}
}
container.addView(itemView)
@@ -510,12 +523,21 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
val preset = NativePostProcessing.getActivePreset()
if (preset.isNotEmpty()) {
addDivider(container)
var summary =
YuzuApplication.appContext.getString(R.string.post_processing_preset_locked)
var summary = ""
val described = NativePostProcessing.presets().firstOrNull { it.name == preset }
if (described != null) {
summary = described.description
}
if (summary.isEmpty()) {
summary =
YuzuApplication.appContext.getString(R.string.post_processing_preset_locked)
}
if (NativePostProcessing.isPresetModified()) {
summary =
YuzuApplication.appContext.getString(R.string.post_processing_preset_modified)
}
addPresetBand(container, preset, summary)
return
}
@@ -553,8 +575,9 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
}
val body = addShaderCard(title, summary, container) {
NativePostProcessing.remove(index)
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
emulationFragment.editPostProcessing {
NativePostProcessing.remove(index)
}
onStructureChanged()
}
@@ -565,9 +588,20 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
}
}
addEffectPicker(container, labels) { picked ->
NativePostProcessing.append(files[picked], techniques[picked])
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
addEffectPicker(
container,
labels,
chain.isNotEmpty(),
{
emulationFragment.editPostProcessing {
NativePostProcessing.clearChain()
}
onStructureChanged()
}
) { picked ->
emulationFragment.editPostProcessing {
NativePostProcessing.append(files[picked], techniques[picked])
}
onStructureChanged()
}
}
@@ -600,7 +634,8 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
container,
steps,
step,
{ position -> describeUniform(uniform, position) }
{ position -> describeUniform(uniform, position) },
{ emulationFragment.persistPostProcessing() }
) { position ->
NativePostProcessing.setValue(
index,
@@ -608,7 +643,6 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
component,
uniform.min + position * uniform.step
)
NativePostProcessing.persistFor(emulationFragment.shouldUseCustom)
}
}
}
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
class FxPresetNameSetting(private val onNamed: (String) -> Unit) : AbstractStringSetting {
override val key: String
get() = "fx_preset_name"
override val defaultValue: Any
get() = ""
override val isRuntimeModifiable: Boolean
get() = true
override val pairedSettingKey: String
get() = ""
override val isSwitchable: Boolean
get() = false
override val isSaveable: Boolean
get() = true
override var global: Boolean
get() = true
set(_) {}
override fun getString(needsGlobal: Boolean): String = ""
override fun setString(value: String) = onNamed(value)
override fun getValueAsString(needsGlobal: Boolean): String = ""
override fun reset() {}
}
@@ -39,7 +39,7 @@ abstract class FxUniformSetting(
protected fun commit(value: Float) {
NativePostProcessing.setValue(index, uniform.name, component, value)
NativePostProcessing.persist()
NativePostProcessing.store()
}
override fun reset() = commit(uniform.defaultAt(component))
@@ -92,11 +92,11 @@ class FxUniformBooleanSetting(
override fun getBoolean(needsGlobal: Boolean): Boolean = currentValue() != 0f
override fun setBoolean(value: Boolean) {
var scalar = 0f
if (value) {
commit(1f)
return
scalar = 1f
}
commit(0f)
commit(scalar)
}
override fun getValueAsString(needsGlobal: Boolean): String = getBoolean().toString()
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view
class FxPresetSetting(
titleString: String,
descriptionString: String = "",
val deletable: Boolean = false,
val onApply: () -> Unit,
val onDelete: () -> Unit = {}
) : SettingsItem(emptySetting, 0, titleString, 0, descriptionString) {
override val type = TYPE_FX_PRESET
}
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model.view
import androidx.annotation.StringRes
class FxToolbarSetting(
@StringRes val addLabelId: Int,
val listOpen: Boolean,
val presetLabel: String,
val hasEffects: Boolean,
val createPreset: StringInputSetting,
val onAdd: () -> Unit,
val onPresets: () -> Unit,
val onRemoveAll: () -> Unit
) : SettingsItem(emptySetting, 0, "", 0, "") {
override val type = TYPE_FX_TOOLBAR
}
@@ -145,6 +145,8 @@ abstract class SettingsItem(
const val TYPE_PATH = 14
const val TYPE_GPU_UNSWIZZLE = 15
const val TYPE_CARD = 16
const val TYPE_FX_TOOLBAR = 17
const val TYPE_FX_PRESET = 18
const val FASTMEM_COMBINED = "fastmem_combined"
const val GPU_UNSWIZZLE_COMBINED = "gpu_unswizzle_combined"
@@ -24,6 +24,8 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.SettingsNavigationDirections
import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingCardBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingInputBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingSwitchBinding
import org.yuzu.yuzu_emu.databinding.ListItemSettingsHeaderBinding
@@ -108,7 +110,21 @@ class SettingsAdapter(
}
SettingsItem.TYPE_CARD -> {
CardViewHolder(ListItemSettingCardBinding.inflate(inflater), this)
CardViewHolder(ListItemSettingCardBinding.inflate(inflater, parent, false), this)
}
SettingsItem.TYPE_FX_TOOLBAR -> {
FxToolbarViewHolder(
ListItemSettingFxToolbarBinding.inflate(inflater, parent, false),
this
)
}
SettingsItem.TYPE_FX_PRESET -> {
FxPresetViewHolder(
ListItemSettingFxPresetBinding.inflate(inflater, parent, false),
this
)
}
else -> {
@@ -18,6 +18,7 @@ import org.yuzu.yuzu_emu.features.input.model.NpadStyleIndex
import org.yuzu.yuzu_emu.features.settings.model.AbstractBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting
import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxPresetNameSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformBooleanSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformChoiceSetting
import org.yuzu.yuzu_emu.features.settings.model.FxUniformSliderSetting
@@ -54,6 +55,8 @@ class SettingsFragmentPresenter(
private var presetPickerOpen = false
private var postProcessingSynced = false
private val context get() = YuzuApplication.appContext
// Extension for altering settings list based on each setting's properties
@@ -202,6 +205,11 @@ class SettingsFragmentPresenter(
}
private fun addPostProcessingSettings(sl: ArrayList<SettingsItem>) {
if (!postProcessingSynced) {
postProcessingSynced = true
NativePostProcessing.reload()
}
val usable = NativePostProcessing.catalog().filter { it.valid }
sl.apply {
@@ -216,68 +224,6 @@ class SettingsFragmentPresenter(
return@apply
}
val active = NativePostProcessing.getActivePreset()
var presetSummary = context.getString(R.string.post_processing_preset_none)
if (active.isNotEmpty()) {
presetSummary = active
if (NativePostProcessing.isPresetModified()) {
presetSummary = context.getString(R.string.post_processing_preset_modified)
}
}
add(
CardSetting(
titleId = R.string.post_processing_preset,
descriptionString = presetSummary,
expanded = presetPickerOpen
) {
presetPickerOpen = !presetPickerOpen
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
if (presetPickerOpen) {
add(
RunnableSetting(
titleId = R.string.post_processing_preset_none,
isRunnable = true
) {
NativePostProcessing.clearPreset()
presetPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
for (preset in NativePostProcessing.presets()) {
add(
RunnableSetting(
titleString = preset.name,
descriptionString = preset.description,
isRunnable = true
) {
NativePostProcessing.applyPreset(preset.name)
NativePostProcessing.store()
presetPickerOpen = false
expandedShaderSlots.clear()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
}
if (active.isNotEmpty()) {
add(
RunnableSetting(
titleId = R.string.post_processing_preset_reset,
isRunnable = true
) {
NativePostProcessing.applyPreset(active)
NativePostProcessing.store()
expandedShaderSlots.clear()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
val labels = mutableListOf<String>()
val summaries = mutableListOf<String>()
val files = mutableListOf<String>()
@@ -296,6 +242,124 @@ class SettingsFragmentPresenter(
}
val chain = NativePostProcessing.chain()
val active = NativePostProcessing.getActivePreset()
var addLabel = R.string.post_processing_add
if (chain.isNotEmpty()) {
addLabel = R.string.post_processing_open_list
}
if (shaderPickerOpen) {
addLabel = R.string.post_processing_close_list
}
var presetLabel = context.getString(R.string.post_processing_presets)
if (active.isNotEmpty()) {
presetLabel = active
}
val createPreset = StringInputSetting(
setting = FxPresetNameSetting { name ->
NativePostProcessing.savePreset(name, "")
settingsViewModel.setReloadListAndNotifyDataset(true)
},
titleId = R.string.post_processing_preset_new,
descriptionId = R.string.post_processing_preset_new_description,
validator = { it != null && it.isNotBlank() && !it.contains('=') },
errorId = R.string.post_processing_preset_name_invalid
)
add(
FxToolbarSetting(
addLabelId = addLabel,
listOpen = shaderPickerOpen,
presetLabel = presetLabel,
hasEffects = chain.isNotEmpty(),
createPreset = createPreset,
onAdd = {
shaderPickerOpen = !shaderPickerOpen
presetPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
},
onPresets = {
presetPickerOpen = !presetPickerOpen
shaderPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
},
onRemoveAll = {
NativePostProcessing.clearChain()
NativePostProcessing.clearPreset()
NativePostProcessing.store()
expandedShaderSlots.clear()
shaderPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
)
if (shaderPickerOpen) {
for (choice in labels.indices) {
add(
RunnableSetting(
titleString = labels[choice],
descriptionString = summaries[choice],
isRunnable = true
) {
NativePostProcessing.append(files[choice], techniques[choice])
NativePostProcessing.store()
shaderPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
}
if (presetPickerOpen) {
add(
FxPresetSetting(
titleString = context.getString(R.string.post_processing_preset_none),
onApply = {
NativePostProcessing.clearPreset()
presetPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
)
for (preset in NativePostProcessing.presets()) {
add(
FxPresetSetting(
titleString = preset.name,
descriptionString = preset.description,
deletable = !preset.bundled,
onApply = {
NativePostProcessing.applyPreset(preset.name)
NativePostProcessing.store()
presetPickerOpen = false
expandedShaderSlots.clear()
settingsViewModel.setReloadListAndNotifyDataset(true)
},
onDelete = {
NativePostProcessing.deletePreset(preset.name)
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
)
}
}
if (active.isNotEmpty()) {
add(
RunnableSetting(
titleId = R.string.post_processing_preset_reset,
isRunnable = true
) {
NativePostProcessing.applyPreset(active)
NativePostProcessing.store()
expandedShaderSlots.clear()
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
for (index in chain.indices) {
val entry = chain[index]
val effect = usable.firstOrNull { it.file == entry.file }
@@ -385,33 +449,6 @@ class SettingsFragmentPresenter(
}
)
}
add(
CardSetting(
titleId = R.string.post_processing_add,
expanded = shaderPickerOpen
) {
shaderPickerOpen = !shaderPickerOpen
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
if (shaderPickerOpen) {
for (choice in labels.indices) {
add(
RunnableSetting(
titleString = labels[choice],
descriptionString = summaries[choice],
isRunnable = true
) {
NativePostProcessing.append(files[choice], techniques[choice])
NativePostProcessing.store()
shaderPickerOpen = false
settingsViewModel.setReloadListAndNotifyDataset(true)
}
)
}
}
}
}
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.view.View
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxPresetBinding
import org.yuzu.yuzu_emu.features.settings.model.view.FxPresetSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
class FxPresetViewHolder(
val binding: ListItemSettingFxPresetBinding,
adapter: SettingsAdapter
) : SettingViewHolder(binding.root, adapter) {
private lateinit var setting: FxPresetSetting
override fun bind(item: SettingsItem) {
setting = item as FxPresetSetting
binding.presetName.text = item.title
binding.presetDescription.text = item.description
binding.presetDescription.setVisible(item.description.isNotEmpty())
binding.presetRow.setOnClickListener { setting.onApply.invoke() }
binding.presetDelete.setVisible(setting.deletable)
binding.presetDelete.setOnClickListener { setting.onDelete.invoke() }
}
override fun onClick(clicked: View) {}
override fun onLongClick(clicked: View): Boolean = true
}
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.ui.viewholder
import android.content.res.ColorStateList
import android.view.View
import com.google.android.material.color.MaterialColors
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ListItemSettingFxToolbarBinding
import org.yuzu.yuzu_emu.features.settings.model.view.FxToolbarSetting
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter
class FxToolbarViewHolder(
val binding: ListItemSettingFxToolbarBinding,
adapter: SettingsAdapter
) : SettingViewHolder(binding.root, adapter) {
private lateinit var setting: FxToolbarSetting
override fun bind(item: SettingsItem) {
setting = item as FxToolbarSetting
binding.fxAdd.setText(setting.addLabelId)
var addIcon = R.drawable.ic_add
if (setting.listOpen) {
addIcon = R.drawable.ic_clear
}
binding.fxAdd.setIconResource(addIcon)
binding.fxAdd.setOnClickListener { setting.onAdd.invoke() }
binding.fxPresets.text = setting.presetLabel
binding.fxPresets.setOnClickListener { setting.onPresets.invoke() }
binding.fxCreatePreset.isEnabled = setting.hasEffects
binding.fxCreatePreset.setOnClickListener {
adapter.onStringInputClick(setting.createPreset, bindingAdapterPosition)
}
val removeBackground = MaterialColors.getColor(
binding.fxRemoveAll,
com.google.android.material.R.attr.colorErrorContainer
)
val removeForeground = MaterialColors.getColor(
binding.fxRemoveAll,
com.google.android.material.R.attr.colorOnErrorContainer
)
binding.fxRemoveAll.backgroundTintList = ColorStateList.valueOf(removeBackground)
binding.fxRemoveAll.iconTint = ColorStateList.valueOf(removeForeground)
binding.fxRemoveAll.isEnabled = setting.hasEffects
binding.fxRemoveAll.setOnClickListener { setting.onRemoveAll.invoke() }
}
override fun onClick(clicked: View) {}
override fun onLongClick(clicked: View): Boolean = true
}
@@ -94,6 +94,7 @@ import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.NativeConfig
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
import org.yuzu.yuzu_emu.utils.NativePostProcessing
import org.yuzu.yuzu_emu.utils.ViewUtils
import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible
import org.yuzu.yuzu_emu.utils.collect
@@ -883,6 +884,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
if (shouldUseCustom) {
SettingsFile.loadCustomConfig(game!!)
}
refreshPostProcessing()
addQuickSettings()
}
}
@@ -1087,6 +1090,34 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
}
}
private fun withPerGameConfig(create: Boolean, action: () -> Unit) {
val target = game
var owned = false
if (target != null && !NativeConfig.isPerGameConfigLoaded()) {
if (create || SettingsFile.getCustomSettingsFile(target).exists()) {
SettingsFile.loadCustomConfig(target)
owned = true
}
}
action()
if (owned) {
NativeConfig.unloadPerGameConfig()
}
}
fun refreshPostProcessing() = withPerGameConfig(false) {
NativePostProcessing.reload()
}
fun persistPostProcessing() = withPerGameConfig(true) {
NativePostProcessing.persist()
}
fun editPostProcessing(action: () -> Unit) = withPerGameConfig(true) {
action()
NativePostProcessing.persist()
}
private fun addQuickSettings() {
binding.quickSettingsSheet.apply {
val container = binding.quickSettingsSheet.findViewById<ViewGroup>(R.id.quick_settings_container)
@@ -42,6 +42,10 @@ object NativePostProcessing {
external fun store()
external fun reload()
external fun clearChain()
external fun getPresetsJson(): String
external fun getActivePreset(): String
@@ -63,11 +67,7 @@ object NativePostProcessing {
external fun getPresetDirectory(): String
fun persist() {
store()
NativeConfig.saveGlobalConfig()
}
fun persistFor(perGame: Boolean) {
val perGame = NativeConfig.isPerGameConfigLoaded()
store()
if (perGame) {
NativeConfig.savePerGameConfig()
@@ -15,10 +15,22 @@
#include "frontend_common/config.h"
#include "frontend_common/settings_generator.h"
#include "native.h"
#ifdef HAS_RESHADE
#include "video_core/post_processing/fx_chain.h"
#endif
std::unique_ptr<AndroidConfig> global_config;
std::unique_ptr<AndroidConfig> per_game_config;
#ifdef HAS_RESHADE
static void ResetFxChainToGlobal() {
VideoCore::UseGlobalFxSettings();
VideoCore::FxChain::Instance().LoadFromSettings();
}
#else
static void ResetFxChainToGlobal() {}
#endif
template <typename T>
Settings::Setting<T>* getSetting(JNIEnv* env, jstring jkey) {
auto key = Common::Android::GetJString(env, jkey);
@@ -39,6 +51,7 @@ extern "C" {
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) {
global_config = std::make_unique<AndroidConfig>();
FrontendCommon::GenerateSettings();
ResetFxChainToGlobal();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) {
@@ -47,6 +60,7 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env,
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) {
global_config->AndroidConfig::ReloadAllValues();
ResetFxChainToGlobal();
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) {
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <array>
#include <memory>
#include <string>
#include <jni.h>
@@ -9,15 +10,29 @@
#include "common/android/android_common.h"
#ifdef HAS_RESHADE
#include "android_config.h"
#include "video_core/post_processing/fx_chain.h"
#include "common/settings.h"
#include "video_core/post_processing/fx_effect.h"
#include "video_core/post_processing/fx_preset.h"
extern std::unique_ptr<AndroidConfig> per_game_config;
#endif
namespace {
#ifdef HAS_RESHADE
bool EditingPerGame() {
return per_game_config != nullptr;
}
void BeginFxEdit() {
if (!EditingPerGame()) {
return;
}
VideoCore::UsePerGameFxSettings();
}
nlohmann::json SerializeUniform(const VideoCore::FxUniformDesc& uniform) {
nlohmann::json out;
out["name"] = uniform.name;
@@ -201,10 +216,26 @@ void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setValue(JNIEnv* env, jo
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_store(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
BeginFxEdit();
VideoCore::FxChain::Instance().StoreToSettings();
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_reload(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
if (!EditingPerGame()) {
VideoCore::UseGlobalFxSettings();
}
VideoCore::FxChain::Instance().LoadFromSettings();
#endif
}
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearChain(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
VideoCore::FxChain::Instance().Clear();
#endif
}
jstring Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_getShaderDirectory(JNIEnv* env,
jobject obj) {
#ifdef HAS_RESHADE
@@ -252,6 +283,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isPresetModified(JNI
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_applyPreset(JNIEnv* env, jobject obj,
jstring jname) {
#ifdef HAS_RESHADE
BeginFxEdit();
return static_cast<jboolean>(
VideoCore::ApplyFxPreset(Common::Android::GetJString(env, jname)));
#else
@@ -263,6 +295,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_savePreset(JNIEnv* e
jstring jname,
jstring jdescription) {
#ifdef HAS_RESHADE
BeginFxEdit();
return static_cast<jboolean>(
VideoCore::SaveFxPreset(Common::Android::GetJString(env, jname),
Common::Android::GetJString(env, jdescription)));
@@ -274,6 +307,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_savePreset(JNIEnv* e
jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_deletePreset(JNIEnv* env, jobject obj,
jstring jname) {
#ifdef HAS_RESHADE
BeginFxEdit();
return static_cast<jboolean>(
VideoCore::DeleteFxPreset(Common::Android::GetJString(env, jname)));
#else
@@ -283,6 +317,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_deletePreset(JNIEnv*
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_clearPreset(JNIEnv* env, jobject obj) {
#ifdef HAS_RESHADE
BeginFxEdit();
VideoCore::SetActiveFxPreset(std::string_view());
#endif
}
@@ -298,6 +333,7 @@ jboolean Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_isEnabled(JNIEnv* en
void Java_org_yuzu_yuzu_1emu_utils_NativePostProcessing_setEnabled(JNIEnv* env, jobject obj,
jboolean enabled) {
#ifdef HAS_RESHADE
BeginFxEdit();
Settings::values.post_shader_enabled.SetValue(enabled != JNI_FALSE);
#endif
}
@@ -5,16 +5,35 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/add_button"
style="@style/Widget.Material3.Button.TonalButton"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="4dp"
android:text="@string/post_processing_add"
app:icon="@drawable/ic_add" />
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/add_button"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/post_processing_add"
app:icon="@drawable/ic_add" />
<com.google.android.material.button.MaterialButton
android:id="@+id/remove_all_button"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:text="@string/post_processing_remove_all"
android:visibility="gone"
app:icon="@drawable/ic_delete" />
</LinearLayout>
<RadioGroup
android:id="@+id/add_choices"
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:background="@drawable/shader_card_background"
android:orientation="vertical">
<LinearLayout
android:id="@+id/preset_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/preset_name"
style="@style/TextAppearance.Material3.TitleSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/preset_description"
style="@style/TextAppearance.Material3.BodySmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="?attr/colorOnSurfaceVariant" />
</LinearLayout>
<ImageView
android:id="@+id/preset_delete"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:contentDescription="@string/post_processing_preset_delete"
android:focusable="true"
android:padding="8dp"
android:src="@drawable/ic_delete"
android:visibility="gone"
app:tint="?attr/colorOnSurfaceVariant" />
</LinearLayout>
</LinearLayout>
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="6dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/fx_add"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_processing_add"
app:icon="@drawable/ic_add" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fx_presets"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1.4"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/post_processing_presets"
app:icon="@drawable/ic_dropdown_arrow"
app:iconGravity="textEnd" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fx_create_preset"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:contentDescription="@string/post_processing_preset_new"
app:icon="@drawable/ic_add" />
<com.google.android.material.button.MaterialButton
android:id="@+id/fx_remove_all"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:contentDescription="@string/post_processing_remove_all"
app:icon="@drawable/ic_delete" />
</LinearLayout>
@@ -303,11 +303,17 @@
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
<string name="post_processing_add">Add effect</string>
<string name="post_processing_remove">Remove</string>
<string name="post_processing_hide_effects">Remove effects</string>
<string name="post_processing_open_list">Open list</string>
<string name="post_processing_close_list">Close list</string>
<string name="post_processing_remove_all">Remove effects</string>
<string name="post_processing_preset_locked">Preset active. Edit it in Post-Processing Effects before starting a game.</string>
<string name="post_processing_preset_modified">Preset active, with your own changes.</string>
<string name="post_processing_preset">Preset</string>
<string name="post_processing_presets">Presets</string>
<string name="post_processing_preset_new">New preset</string>
<string name="post_processing_preset_new_description">Saves the effects you have loaded, with their current values, as a preset you can pick later.</string>
<string name="post_processing_preset_name_invalid">Give the preset a name without an equals sign.</string>
<string name="post_processing_preset_none">No preset</string>
<string name="post_processing_preset_delete">Delete preset</string>
<string name="post_processing_preset_reset">Reset preset values</string>
<string name="post_processing_move_up">Move up</string>
<string name="post_processing_move_down">Move down</string>
+24 -12
View File
@@ -119,6 +119,26 @@ std::string SerializeFxChain(std::span<const FxChainEntry> entries) {
return out;
}
void UseGlobalFxSettings() {
Settings::values.post_shader_chain.SetGlobal(true);
Settings::values.post_shader_preset.SetGlobal(true);
Settings::values.post_shader_enabled.SetGlobal(true);
}
void UsePerGameFxSettings() {
const std::string chain = Settings::values.post_shader_chain.GetValue();
const std::string preset = Settings::values.post_shader_preset.GetValue();
const bool enabled = Settings::values.post_shader_enabled.GetValue();
Settings::values.post_shader_chain.SetGlobal(false);
Settings::values.post_shader_preset.SetGlobal(false);
Settings::values.post_shader_enabled.SetGlobal(false);
Settings::values.post_shader_chain.SetValue(chain);
Settings::values.post_shader_preset.SetValue(preset);
Settings::values.post_shader_enabled.SetValue(enabled);
}
FxChain& FxChain::Instance() {
static FxChain instance;
return instance;
@@ -260,19 +280,11 @@ void FxChain::LoadFromSettings() {
auto parsed = ParseFxChain(Settings::values.post_shader_chain.GetValue());
std::scoped_lock lock{mutex};
entries = std::move(parsed);
loaded = true;
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::EnsureLoadedFromSettings() {
{
std::scoped_lock lock{mutex};
if (loaded) {
return;
}
if (entries == parsed) {
return;
}
LoadFromSettings();
entries = std::move(parsed);
generation.fetch_add(1, std::memory_order_relaxed);
}
void FxChain::StoreToSettings() const {
+6 -3
View File
@@ -20,6 +20,8 @@ struct FxChainEntry {
std::string file;
std::string technique;
std::map<std::string, std::array<f32, 4>> values;
bool operator==(const FxChainEntry&) const = default;
};
struct FxChainSnapshot {
@@ -31,6 +33,10 @@ std::vector<FxChainEntry> ParseFxChain(std::string_view value);
std::string SerializeFxChain(std::span<const FxChainEntry> entries);
void UseGlobalFxSettings();
void UsePerGameFxSettings();
class FxChain {
public:
static FxChain& Instance();
@@ -65,8 +71,6 @@ public:
void LoadFromSettings();
void EnsureLoadedFromSettings();
void StoreToSettings() const;
void DropUnknownEntries();
@@ -76,7 +80,6 @@ private:
mutable std::mutex mutex;
std::vector<FxChainEntry> entries;
bool loaded{};
std::atomic<u64> generation{1};
};