mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-09 13:36:35 +00:00
OH MY GOD THIS UI TORTURE
This commit is contained in:
@@ -341,17 +341,99 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
container.addView(itemView)
|
||||
}
|
||||
|
||||
fun addShaderCard(
|
||||
title: String,
|
||||
summary: String,
|
||||
container: ViewGroup,
|
||||
onRemove: () -> Unit
|
||||
): ViewGroup {
|
||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||
val itemView = inflater.inflate(R.layout.item_quick_settings_shader, container, false)
|
||||
|
||||
val headerView = itemView.findViewById<ViewGroup>(R.id.shader_header)
|
||||
val titleView = itemView.findViewById<TextView>(R.id.shader_title)
|
||||
val summaryView = itemView.findViewById<TextView>(R.id.shader_summary)
|
||||
val removeView = itemView.findViewById<android.widget.ImageView>(R.id.shader_remove)
|
||||
val expandIcon = itemView.findViewById<android.widget.ImageView>(R.id.shader_expand)
|
||||
val bodyView = itemView.findViewById<ViewGroup>(R.id.shader_body)
|
||||
|
||||
titleView.text = title
|
||||
if (summary.isEmpty()) {
|
||||
summaryView.visibility = View.GONE
|
||||
} else {
|
||||
summaryView.text = summary
|
||||
}
|
||||
|
||||
var isExpanded = false
|
||||
headerView.setOnClickListener {
|
||||
isExpanded = !isExpanded
|
||||
if (isExpanded) {
|
||||
bodyView.visibility = View.VISIBLE
|
||||
expandIcon.animate().rotation(180f).setDuration(200).start()
|
||||
} else {
|
||||
bodyView.visibility = View.GONE
|
||||
expandIcon.animate().rotation(0f).setDuration(200).start()
|
||||
}
|
||||
}
|
||||
|
||||
removeView.setOnClickListener {
|
||||
onRemove()
|
||||
}
|
||||
|
||||
container.addView(itemView)
|
||||
return bodyView
|
||||
}
|
||||
|
||||
fun addEffectPicker(
|
||||
container: ViewGroup,
|
||||
choices: List<String>,
|
||||
onPicked: (Int) -> Unit
|
||||
) {
|
||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||
val itemView = inflater.inflate(R.layout.item_quick_settings_add, container, false)
|
||||
|
||||
val button = itemView.findViewById<com.google.android.material.button.MaterialButton>(
|
||||
R.id.add_button
|
||||
)
|
||||
val choiceGroup = itemView.findViewById<RadioGroup>(R.id.add_choices)
|
||||
|
||||
choices.forEachIndexed { index, name ->
|
||||
val radioButton = com.google.android.material.radiobutton.MaterialRadioButton(
|
||||
emulationFragment.requireContext()
|
||||
)
|
||||
radioButton.text = name
|
||||
radioButton.id = View.generateViewId()
|
||||
radioButton.setPadding(16, 8, 16, 8)
|
||||
radioButton.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) {
|
||||
onPicked(index)
|
||||
}
|
||||
}
|
||||
choiceGroup.addView(radioButton)
|
||||
}
|
||||
|
||||
var isOpen = false
|
||||
button.setOnClickListener {
|
||||
isOpen = !isOpen
|
||||
if (isOpen) {
|
||||
choiceGroup.visibility = View.VISIBLE
|
||||
} else {
|
||||
choiceGroup.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
container.addView(itemView)
|
||||
}
|
||||
|
||||
fun addPostProcessing(container: ViewGroup, onStructureChanged: () -> Unit) {
|
||||
val usable = NativePostProcessing.catalog().filter { it.valid }
|
||||
if (usable.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val labels = mutableListOf(
|
||||
YuzuApplication.appContext.getString(R.string.post_processing_none)
|
||||
)
|
||||
val files = mutableListOf("")
|
||||
val techniques = mutableListOf("")
|
||||
val labels = mutableListOf<String>()
|
||||
val files = mutableListOf<String>()
|
||||
val techniques = mutableListOf<String>()
|
||||
|
||||
for (effect in usable) {
|
||||
for (technique in effect.techniques) {
|
||||
@@ -368,61 +450,37 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
||||
addDivider(container)
|
||||
|
||||
val chain = NativePostProcessing.chain()
|
||||
for (index in 0..chain.size) {
|
||||
var selected = 0
|
||||
var effect: NativePostProcessing.Effect? = null
|
||||
chain.forEachIndexed { index, entry ->
|
||||
val effect = usable.firstOrNull { it.file == entry.file }
|
||||
|
||||
if (index < chain.size) {
|
||||
val entry = chain[index]
|
||||
effect = usable.firstOrNull { it.file == entry.file }
|
||||
for (i in files.indices) {
|
||||
if (files[i] == entry.file && techniques[i] == entry.technique) {
|
||||
selected = i
|
||||
}
|
||||
var title = entry.file
|
||||
var summary = ""
|
||||
if (effect != null) {
|
||||
title = effect.label
|
||||
if (effect.techniques.size > 1) {
|
||||
title = effect.label + " \u00b7 " + entry.technique
|
||||
}
|
||||
summary = effect.description
|
||||
}
|
||||
|
||||
var title = YuzuApplication.appContext.getString(R.string.post_processing_add)
|
||||
if (index < chain.size) {
|
||||
title = YuzuApplication.appContext.getString(R.string.post_processing_effect)
|
||||
}
|
||||
|
||||
addChoice(title, container, labels, selected) { picked ->
|
||||
applyEffectPick(index, picked, files, techniques, chain.size)
|
||||
val body = addShaderCard(title, summary, container) {
|
||||
NativePostProcessing.remove(index)
|
||||
NativePostProcessing.persist()
|
||||
onStructureChanged()
|
||||
}
|
||||
|
||||
if (effect != null) {
|
||||
for (uniform in effect.uniforms) {
|
||||
addUniformSliders(container, index, uniform)
|
||||
addUniformSliders(body, index, uniform)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyEffectPick(
|
||||
index: Int,
|
||||
picked: Int,
|
||||
files: List<String>,
|
||||
techniques: List<String>,
|
||||
chainSize: Int
|
||||
) {
|
||||
if (index >= chainSize) {
|
||||
if (picked > 0) {
|
||||
NativePostProcessing.append(files[picked], techniques[picked])
|
||||
NativePostProcessing.persist()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (picked == 0) {
|
||||
NativePostProcessing.remove(index)
|
||||
addEffectPicker(container, labels) { picked ->
|
||||
NativePostProcessing.append(files[picked], techniques[picked])
|
||||
NativePostProcessing.persist()
|
||||
return
|
||||
onStructureChanged()
|
||||
}
|
||||
|
||||
NativePostProcessing.replace(index, files[picked], techniques[picked])
|
||||
NativePostProcessing.persist()
|
||||
}
|
||||
|
||||
private fun addUniformSliders(
|
||||
|
||||
+68
-9
@@ -48,6 +48,8 @@ class SettingsFragmentPresenter(
|
||||
) {
|
||||
private var settingsList = ArrayList<SettingsItem>()
|
||||
|
||||
private val expandedShaderSlots = mutableSetOf<Int>()
|
||||
|
||||
private val context get() = YuzuApplication.appContext
|
||||
|
||||
// Extension for altering settings list based on each setting's properties
|
||||
@@ -234,15 +236,42 @@ class SettingsFragmentPresenter(
|
||||
var summary = ""
|
||||
if (effect != null) {
|
||||
header = effect.label
|
||||
if (effect.techniques.size > 1) {
|
||||
header = effect.label + " \u00b7 " + entry.technique
|
||||
}
|
||||
summary = effect.description
|
||||
}
|
||||
add(HeaderSetting(titleString = header))
|
||||
|
||||
val isOpen = expandedShaderSlots.contains(index)
|
||||
var chevron = R.drawable.ic_arrow_forward
|
||||
if (isOpen) {
|
||||
chevron = R.drawable.ic_dropdown_arrow
|
||||
}
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
titleString = header,
|
||||
descriptionString = summary,
|
||||
isRunnable = true,
|
||||
iconId = chevron
|
||||
) {
|
||||
if (isOpen) {
|
||||
expandedShaderSlots.remove(index)
|
||||
} else {
|
||||
expandedShaderSlots.add(index)
|
||||
}
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
|
||||
if (!isOpen) {
|
||||
continue
|
||||
}
|
||||
|
||||
add(
|
||||
IntSingleChoiceSetting(
|
||||
buildSlotSelector(index, entry, files, techniques),
|
||||
titleId = R.string.post_processing_effect,
|
||||
descriptionString = summary,
|
||||
choices = labels.toTypedArray(),
|
||||
values = labels.indices.toList().toTypedArray()
|
||||
)
|
||||
@@ -262,6 +291,7 @@ class SettingsFragmentPresenter(
|
||||
) {
|
||||
NativePostProcessing.move(index, -1)
|
||||
NativePostProcessing.persist()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
@@ -274,6 +304,7 @@ class SettingsFragmentPresenter(
|
||||
) {
|
||||
NativePostProcessing.move(index, 1)
|
||||
NativePostProcessing.persist()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
@@ -295,24 +326,52 @@ class SettingsFragmentPresenter(
|
||||
) {
|
||||
NativePostProcessing.remove(index)
|
||||
NativePostProcessing.persist()
|
||||
expandedShaderSlots.clear()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
RunnableSetting(
|
||||
IntSingleChoiceSetting(
|
||||
buildAddSelector(files, techniques),
|
||||
titleId = R.string.post_processing_add,
|
||||
isRunnable = true
|
||||
) {
|
||||
NativePostProcessing.append(files[0], techniques[0])
|
||||
NativePostProcessing.persist()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
choices = labels.toTypedArray(),
|
||||
values = labels.indices.toList().toTypedArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildAddSelector(
|
||||
files: List<String>,
|
||||
techniques: List<String>
|
||||
): AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val key = "fx_add"
|
||||
|
||||
override fun getInt(needsGlobal: Boolean): Int = -1
|
||||
|
||||
override fun setInt(value: Int) {
|
||||
if (value < 0 || value >= files.size) {
|
||||
return
|
||||
}
|
||||
NativePostProcessing.append(files[value], techniques[value])
|
||||
NativePostProcessing.persist()
|
||||
settingsViewModel.setReloadListAndNotifyDataset(true)
|
||||
}
|
||||
|
||||
override val defaultValue = -1
|
||||
override fun getValueAsString(needsGlobal: Boolean): String = ""
|
||||
override fun reset() {}
|
||||
override val isRuntimeModifiable = true
|
||||
override val pairedSettingKey = ""
|
||||
override val isSwitchable = false
|
||||
override val isSaveable = true
|
||||
override var global: Boolean
|
||||
get() = true
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
private fun buildSlotSelector(
|
||||
index: Int,
|
||||
entry: NativePostProcessing.ChainEntry,
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<solid android:color="?attr/colorSurfaceVariant" />
|
||||
|
||||
<corners android:radius="16dp" />
|
||||
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?attr/colorOutline" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/add_button"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
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" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/add_choices"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="24dp"
|
||||
android:paddingEnd="24dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,82 @@
|
||||
<?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/shader_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<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/shader_title"
|
||||
style="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/shader_summary"
|
||||
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/shader_remove"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_delete"
|
||||
android:contentDescription="@string/post_processing_remove"
|
||||
app:tint="?attr/colorOnSurfaceVariant" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/shader_expand"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="4dp"
|
||||
android:src="@drawable/ic_dropdown_arrow"
|
||||
android:contentDescription=""
|
||||
app:tint="?attr/colorPrimary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/shader_body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -302,7 +302,6 @@
|
||||
<string name="post_processing_description">ReShade FX effects applied after rendering</string>
|
||||
<string name="post_processing_per_game_description">Configure the effect chain for this game</string>
|
||||
<string name="post_processing_effect">Effect</string>
|
||||
<string name="post_processing_none">None</string>
|
||||
<string name="post_processing_add">Add effect</string>
|
||||
<string name="post_processing_remove">Remove</string>
|
||||
<string name="post_processing_move_up">Move up</string>
|
||||
|
||||
@@ -5,13 +5,15 @@
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include <QAction>
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFrame>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSlider>
|
||||
@@ -83,16 +85,23 @@ ConfigurePostProcessing::ConfigurePostProcessing(QWidget* parent) : QDialog(pare
|
||||
auto* actions = new QHBoxLayout();
|
||||
|
||||
auto* add_button = new QPushButton(tr("Add Effect"), this);
|
||||
connect(add_button, &QPushButton::clicked, this, [this]() {
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
if (!effect.Valid()) {
|
||||
continue;
|
||||
}
|
||||
VideoCore::FxChain::Instance().Append(effect.file, effect.techniques.front());
|
||||
ApplyStructuralChange();
|
||||
return;
|
||||
auto* add_menu = new QMenu(add_button);
|
||||
for (const auto& effect : VideoCore::GetFxCatalog()) {
|
||||
if (!effect.Valid()) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
for (const auto& technique : effect.techniques) {
|
||||
QAction* action = add_menu->addAction(SlotLabel(effect, technique));
|
||||
action->setToolTip(QString::fromStdString(effect.description));
|
||||
const std::string file = effect.file;
|
||||
const std::string name = technique;
|
||||
connect(action, &QAction::triggered, this, [this, file, name]() {
|
||||
VideoCore::FxChain::Instance().Append(file, name);
|
||||
ApplyStructuralChange();
|
||||
});
|
||||
}
|
||||
}
|
||||
add_button->setMenu(add_menu);
|
||||
actions->addWidget(add_button);
|
||||
|
||||
actions->addStretch();
|
||||
@@ -235,11 +244,21 @@ void ConfigurePostProcessing::BuildUniformWidget(QWidget* parent, QVBoxLayout* l
|
||||
}
|
||||
|
||||
QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainEntry& entry) {
|
||||
auto* group = new QGroupBox(slots_container);
|
||||
auto* group = new QFrame(slots_container);
|
||||
group->setObjectName(QStringLiteral("fxSlot"));
|
||||
group->setStyleSheet(QStringLiteral(
|
||||
"QFrame#fxSlot { background-color: palette(alternate-base);"
|
||||
" border: 1px solid palette(mid); border-radius: 6px; }"));
|
||||
auto* layout = new QVBoxLayout(group);
|
||||
|
||||
auto* header = new QHBoxLayout();
|
||||
|
||||
auto* toggle = new QToolButton(group);
|
||||
toggle->setArrowType(Qt::RightArrow);
|
||||
toggle->setAutoRaise(true);
|
||||
toggle->setCheckable(true);
|
||||
header->addWidget(toggle);
|
||||
|
||||
auto* combo = new QComboBox(group);
|
||||
PopulateEffectCombo(combo, entry);
|
||||
connect(combo, &QComboBox::currentIndexChanged, this, [this, index, combo](int) {
|
||||
@@ -292,13 +311,28 @@ QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainE
|
||||
|
||||
layout->addLayout(header);
|
||||
|
||||
auto* body = new QWidget(group);
|
||||
auto* body_layout = new QVBoxLayout(body);
|
||||
body_layout->setContentsMargins(0, 0, 0, 0);
|
||||
body->setVisible(false);
|
||||
layout->addWidget(body);
|
||||
|
||||
connect(toggle, &QToolButton::toggled, this, [toggle, body](bool open) {
|
||||
body->setVisible(open);
|
||||
if (open) {
|
||||
toggle->setArrowType(Qt::DownArrow);
|
||||
} else {
|
||||
toggle->setArrowType(Qt::RightArrow);
|
||||
}
|
||||
});
|
||||
|
||||
const VideoCore::FxEffectDesc* effect = VideoCore::FindFxEffect(entry.file);
|
||||
if (effect == nullptr) {
|
||||
auto* missing =
|
||||
new QLabel(tr("Effect '%1' was not found.").arg(QString::fromStdString(entry.file)),
|
||||
group);
|
||||
missing->setWordWrap(true);
|
||||
layout->addWidget(missing);
|
||||
body_layout->addWidget(missing);
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -306,7 +340,7 @@ QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainE
|
||||
auto* failed = new QLabel(
|
||||
tr("Effect failed to compile:\n%1").arg(QString::fromStdString(effect->error)), group);
|
||||
failed->setWordWrap(true);
|
||||
layout->addWidget(failed);
|
||||
body_layout->addWidget(failed);
|
||||
return group;
|
||||
}
|
||||
|
||||
@@ -317,10 +351,10 @@ QWidget* ConfigurePostProcessing::BuildSlot(int index, const VideoCore::FxChainE
|
||||
if (!current_category.empty()) {
|
||||
auto* category = new QLabel(QString::fromStdString(current_category), group);
|
||||
category->setStyleSheet(QStringLiteral("font-weight: bold;"));
|
||||
layout->addWidget(category);
|
||||
body_layout->addWidget(category);
|
||||
}
|
||||
}
|
||||
BuildUniformWidget(group, layout, index, uniform);
|
||||
BuildUniformWidget(body, body_layout, index, uniform);
|
||||
}
|
||||
|
||||
return group;
|
||||
|
||||
Reference in New Issue
Block a user