From 021f333ec2272a29e349df9a07e40e9a8c0f5dca Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sun, 13 Sep 2020 01:20:02 +1000 Subject: [PATCH] Qt: Post processing UI --- src/duckstation-qt/CMakeLists.txt | 5 + src/duckstation-qt/displaysettingswidget.cpp | 21 +++ src/duckstation-qt/displaysettingswidget.h | 1 + src/duckstation-qt/displaysettingswidget.ui | 174 +++++++++++------- src/duckstation-qt/duckstation-qt.vcxproj | 9 + .../postprocessingchainconfigwidget.cpp | 174 ++++++++++++++++++ .../postprocessingchainconfigwidget.h | 45 +++++ .../postprocessingchainconfigwidget.ui | 130 +++++++++++++ .../postprocessingshaderconfigwidget.cpp | 0 .../postprocessingshaderconfigwidget.h | 0 .../resources/icons/edit-clear-16.png | Bin 0 -> 912 bytes .../resources/icons/edit-clear-16@2x.png | Bin 0 -> 1788 bytes .../resources/icons/go-down-16.png | Bin 0 -> 745 bytes .../resources/icons/go-down-16@2x.png | Bin 0 -> 1592 bytes .../resources/icons/go-up-16.png | Bin 0 -> 755 bytes .../resources/icons/go-up-16@2x.png | Bin 0 -> 1340 bytes src/duckstation-qt/resources/resources.qrc | 6 + 17 files changed, 496 insertions(+), 69 deletions(-) create mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.cpp create mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.h create mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.ui create mode 100644 src/duckstation-qt/postprocessingshaderconfigwidget.cpp create mode 100644 src/duckstation-qt/postprocessingshaderconfigwidget.h create mode 100644 src/duckstation-qt/resources/icons/edit-clear-16.png create mode 100644 src/duckstation-qt/resources/icons/edit-clear-16@2x.png create mode 100644 src/duckstation-qt/resources/icons/go-down-16.png create mode 100644 src/duckstation-qt/resources/icons/go-down-16@2x.png create mode 100644 src/duckstation-qt/resources/icons/go-up-16.png create mode 100644 src/duckstation-qt/resources/icons/go-up-16@2x.png diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 7a156faf5..3d265f20b 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -54,6 +54,11 @@ set(SRCS mainwindow.ui memorycardsettingswidget.cpp memorycardsettingswidget.h + postprocessingchainconfigwidget.cpp + postprocessingchainconfigwidget.h + postprocessingchainconfigwidget.ui + postprocessingshaderconfigwidget.cpp + postprocessingshaderconfigwidget.h qtdisplaywidget.cpp qtdisplaywidget.h qthostinterface.cpp diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp index 7946140ca..2c3f5243f 100644 --- a/src/duckstation-qt/displaysettingswidget.cpp +++ b/src/duckstation-qt/displaysettingswidget.cpp @@ -1,9 +1,11 @@ #include "displaysettingswidget.h" #include "core/gpu.h" #include "core/settings.h" +#include "postprocessingchainconfigwidget.h" #include "qtutils.h" #include "settingsdialog.h" #include "settingwidgetbinder.h" +#include // For enumerating adapters. #include "frontend-common/vulkan_host_display.h" @@ -45,6 +47,25 @@ DisplaySettingsWidget::DisplaySettingsWidget(QtHostInterface* host_interface, QW &DisplaySettingsWidget::onGPUAdapterIndexChanged); populateGPUAdapters(); + { + std::string post_chain = g_host_interface->GetStringSettingValue("Display", "PostProcessChain"); + if (!post_chain.empty() && !m_ui.postChain->setConfigString(post_chain)) + { + QMessageBox::critical(this, tr("Error"), + tr("The current post-processing chain is invalid, it has been reset. Any changes made will " + "overwrite the existing config.")); + } + } + connect(m_ui.postChain, &PostProcessingChainConfigWidget::chainConfigStringChanged, + [this](const std::string& new_config) { + if (new_config.empty()) + m_host_interface->RemoveSettingValue("Display", "PostProcessChain"); + else + m_host_interface->SetStringSettingValue("Display", "PostProcessChain", new_config.c_str()); + + m_host_interface->applySettings(); + }); + dialog->registerWidgetHelp( m_ui.renderer, tr("Renderer"), Settings::GetRendererDisplayName(Settings::DEFAULT_GPU_RENDERER), tr("Chooses the backend to use for rendering the console/game visuals.
Depending on your system and hardware, " diff --git a/src/duckstation-qt/displaysettingswidget.h b/src/duckstation-qt/displaysettingswidget.h index 9a941ace1..a940b0486 100644 --- a/src/duckstation-qt/displaysettingswidget.h +++ b/src/duckstation-qt/displaysettingswidget.h @@ -5,6 +5,7 @@ #include "ui_displaysettingswidget.h" class QtHostInterface; +class PostProcessingChainConfigWidget; class SettingsDialog; class DisplaySettingsWidget : public QWidget diff --git a/src/duckstation-qt/displaysettingswidget.ui b/src/duckstation-qt/displaysettingswidget.ui index 22a5eb30b..79cfcaaa6 100644 --- a/src/duckstation-qt/displaysettingswidget.ui +++ b/src/duckstation-qt/displaysettingswidget.ui @@ -26,86 +26,90 @@ 0 - + - + Basic - - + + - + - Renderer: + Renderer: - + - + - + - Adapter: + Adapter: - + - - - - - - - - - Screen Display - - - - - - Aspect Ratio: - - - - - - - - - - Crop: - - - - - + - + - Linear Upscaling + VSync - + - - - - Integer Upscaling - - - - - - - VSync - - - - + - - + + + + + Screen Display + + + + + + Aspect Ratio: + + + + + + + + + + Crop: + + + + + + + + + + + + Linear Upscaling + + + + + + + Integer Upscaling + + + + + + + + + On-Screen Display @@ -149,21 +153,53 @@ - + + + + Post-Processing Chain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + - + Qt::Vertical - - + + - 20 - 40 + 20 + 40 - + - + + + + PostProcessingChainConfigWidget + QWidget +
postprocessingchainconfigwidget.h
+ 1 +
+
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index ad2d52bf4..9598d1651 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -56,6 +56,8 @@ + + @@ -84,6 +86,8 @@ + + @@ -143,6 +147,9 @@ Document + + Document + @@ -169,6 +176,8 @@ + + diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.cpp b/src/duckstation-qt/postprocessingchainconfigwidget.cpp new file mode 100644 index 000000000..1968b9140 --- /dev/null +++ b/src/duckstation-qt/postprocessingchainconfigwidget.cpp @@ -0,0 +1,174 @@ +#include "postprocessingchainconfigwidget.h" +#include "frontend-common/postprocessing_chain.h" +#include +#include +#include + +PostProcessingChainConfigWidget::PostProcessingChainConfigWidget(QWidget* parent) : QWidget(parent) +{ + m_ui.setupUi(this); + connectUi(); + updateButtonStates(); +} + +PostProcessingChainConfigWidget::~PostProcessingChainConfigWidget() = default; + +void PostProcessingChainConfigWidget::connectUi() +{ + connect(m_ui.add, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onAddButtonClicked); + connect(m_ui.remove, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onRemoveButtonClicked); + connect(m_ui.clear, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onClearButtonClicked); + connect(m_ui.moveUp, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onMoveUpButtonClicked); + connect(m_ui.moveDown, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onMoveDownButtonClicked); + connect(m_ui.shaderSettings, &QPushButton::clicked, this, + &PostProcessingChainConfigWidget::onShaderConfigButtonClicked); + connect(m_ui.shaders, &QListWidget::itemSelectionChanged, this, &PostProcessingChainConfigWidget::updateButtonStates); + + m_ui.loadPreset->setEnabled(false); + m_ui.savePreset->setEnabled(false); +} + +bool PostProcessingChainConfigWidget::setConfigString(const std::string_view& config_string) +{ + if (!m_chain.CreateFromString(config_string)) + return false; + + updateList(); + return true; +} + +std::optional PostProcessingChainConfigWidget::getSelectedIndex() const +{ + QList selected_items = m_ui.shaders->selectedItems(); + return selected_items.empty() ? std::nullopt : + std::optional(selected_items.first()->data(Qt::UserRole).toUInt()); +} + +void PostProcessingChainConfigWidget::updateList() +{ + m_ui.shaders->clear(); + + for (u32 i = 0; i < m_chain.GetStageCount(); i++) + { + const FrontendCommon::PostProcessingShader& shader = m_chain.GetShaderStage(i); + + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(shader.GetName()), m_ui.shaders); + item->setData(Qt::UserRole, QVariant(i)); + } + + updateButtonStates(); +} + +void PostProcessingChainConfigWidget::configChanged() +{ + if (m_chain.IsEmpty()) + chainConfigStringChanged(std::string()); + else + chainConfigStringChanged(m_chain.GetConfigString()); +} + +void PostProcessingChainConfigWidget::updateButtonStates() +{ + std::optional index = getSelectedIndex(); + m_ui.remove->setEnabled(index.has_value()); + m_ui.clear->setEnabled(!m_chain.IsEmpty()); + m_ui.shaderSettings->setEnabled(index.has_value()); + + if (index.has_value()) + { + m_ui.moveUp->setEnabled(index.value() > 0); + m_ui.moveDown->setEnabled(index.value() < (m_chain.GetStageCount() - 1u)); + } + else + { + m_ui.moveUp->setEnabled(false); + m_ui.moveDown->setEnabled(false); + } +} + +void PostProcessingChainConfigWidget::onAddButtonClicked() +{ + QMenu menu; + + const std::vector shaders(FrontendCommon::PostProcessingChain::GetAvailableShaderNames()); + if (shaders.empty()) + { + menu.addAction(tr("No Shaders Available"))->setEnabled(false); + } + else + { + for (const std::string& shader : shaders) + { + QAction* action = menu.addAction(QString::fromStdString(shader)); + connect(action, &QAction::triggered, [this, &shader]() { + if (!m_chain.AddStage(shader)) + { + QMessageBox::critical(this, tr("Error"), tr("Failed to add shader. The log may contain more information.")); + return; + } + + updateList(); + configChanged(); + }); + } + } + + menu.exec(QCursor::pos()); +} + +void PostProcessingChainConfigWidget::onRemoveButtonClicked() +{ + QList selected_items = m_ui.shaders->selectedItems(); + if (selected_items.empty()) + return; + + QListWidgetItem* item = selected_items.first(); + u32 index = item->data(Qt::UserRole).toUInt(); + if (index < m_chain.GetStageCount()) + { + m_chain.RemoveStage(index); + updateList(); + configChanged(); + } +} + +void PostProcessingChainConfigWidget::onClearButtonClicked() +{ + if (QMessageBox::question(this, tr("Question"), tr("Are you sure you want to clear all shader stages?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + m_chain.ClearStages(); + updateList(); + configChanged(); + } +} + +void PostProcessingChainConfigWidget::onMoveUpButtonClicked() +{ + std::optional index = getSelectedIndex(); + if (index.has_value()) + { + m_chain.MoveStageUp(index.value()); + updateList(); + configChanged(); + } +} + +void PostProcessingChainConfigWidget::onMoveDownButtonClicked() +{ + std::optional index = getSelectedIndex(); + if (index.has_value()) + { + m_chain.MoveStageDown(index.value()); + updateList(); + configChanged(); + } +} + +void PostProcessingChainConfigWidget::onShaderConfigButtonClicked() +{ + std::optional index = getSelectedIndex(); + if (index.has_value()) + { + } +} diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.h b/src/duckstation-qt/postprocessingchainconfigwidget.h new file mode 100644 index 000000000..3e15ca14a --- /dev/null +++ b/src/duckstation-qt/postprocessingchainconfigwidget.h @@ -0,0 +1,45 @@ +#pragma once +#include "common/types.h" +#include "ui_postprocessingchainconfigwidget.h" +#include "frontend-common/postprocessing_chain.h" +#include +#include +#include +#include + +namespace FrontendCommon { +class PostProcessingChain; +} + +class PostProcessingChainConfigWidget : public QWidget +{ + Q_OBJECT + +public: + PostProcessingChainConfigWidget(QWidget* parent); + ~PostProcessingChainConfigWidget(); + + bool setConfigString(const std::string_view& config_string); + +Q_SIGNALS: + void chainConfigStringChanged(const std::string& new_config_string); + +private Q_SLOTS: + void onAddButtonClicked(); + void onRemoveButtonClicked(); + void onClearButtonClicked(); + void onMoveUpButtonClicked(); + void onMoveDownButtonClicked(); + void onShaderConfigButtonClicked(); + void updateButtonStates(); + +private: + void connectUi(); + std::optional getSelectedIndex() const; + void updateList(); + void configChanged(); + + Ui::PostProcessingChainConfigWidget m_ui; + + FrontendCommon::PostProcessingChain m_chain; +}; diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.ui b/src/duckstation-qt/postprocessingchainconfigwidget.ui new file mode 100644 index 000000000..2f9647492 --- /dev/null +++ b/src/duckstation-qt/postprocessingchainconfigwidget.ui @@ -0,0 +1,130 @@ + + + PostProcessingChainConfigWidget + + + + 0 + 0 + 497 + 151 + + + + Form + + + + + + + + Add + + + + :/icons/list-add.png:/icons/list-add.png + + + + + + + Remove + + + + :/icons/list-remove.png:/icons/list-remove.png + + + + + + + Clear + + + + :/icons/edit-clear-16.png:/icons/edit-clear-16.png + + + + + + + Move Up + + + + :/icons/go-up-16.png:/icons/go-up-16.png + + + + + + + Move Down + + + + :/icons/go-down-16.png:/icons/go-down-16.png + + + + + + + Shader Settings... + + + + :/icons/preferences-system@2x.png:/icons/preferences-system@2x.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Load Preset + + + + + + + Save Preset + + + + + + + + + + 0 + 50 + + + + + + + + + + + diff --git a/src/duckstation-qt/postprocessingshaderconfigwidget.cpp b/src/duckstation-qt/postprocessingshaderconfigwidget.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/duckstation-qt/postprocessingshaderconfigwidget.h b/src/duckstation-qt/postprocessingshaderconfigwidget.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/duckstation-qt/resources/icons/edit-clear-16.png b/src/duckstation-qt/resources/icons/edit-clear-16.png new file mode 100644 index 0000000000000000000000000000000000000000..44bb5d77de80a98828531b8f799c79a9c136a698 GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>lgAQBf zNI|#|az-QPy%W|C&j%e_rI-TpKMG)BVK}zl_}W3+6I-48%0a<_2JFokX4Ww{7@-NH zaCi2)9jX&qUoHIhV(t+O?bqiD|Gw~jd)?&QBcC5n0zcnzd2>bO=0TA!w++ACv3!0` z|NsANjk*)i=ID|jzhDNw)xC^`|1z{+A8PsY-9>Nbqnmuoe*HQoy8YkZ z56d}I{`@?z%2@i)((doe6z*3)w=r{wANyR+!!4uT@%QijG<|t~E@|t_zwRydvD0G{ zRj{o0FgMmwmSAG#WZrWjYZ=fQ#w2fdm)d0;1x^Av>?NMQuI!IFE7bkrOeCu{q^%VFf{kC zpI`5g5K!QeF+=#wjSn1$J?3i&a5Jp_D1St`+lwFQZq*Xkh?11Vl2ohYqEsNoU}Ruu zscT@NYitl=Xk=wzWMyKeZD3$!U@$@S$vG4ax%nxXX_dG&JoJbu0%~CJboFyt=akR{ E0JnBZEC2ui literal 0 HcmV?d00001 diff --git a/src/duckstation-qt/resources/icons/edit-clear-16@2x.png b/src/duckstation-qt/resources/icons/edit-clear-16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3450882d73ed6a688f331acea2e96d9948e0fe58 GIT binary patch literal 1788 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 SYG*fr(}kz2pUXO@geCyCBl!0K literal 0 HcmV?d00001 diff --git a/src/duckstation-qt/resources/icons/go-down-16@2x.png b/src/duckstation-qt/resources/icons/go-down-16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..84d64c2d73f01308fb7ace3fcb586dff718b981e GIT binary patch literal 1592 zcmZ`&dpOg382<_z)0)c=)f1IlZc{|Vj5fJ6_sf#8#kL4j*l)98${}?stsaVUmto9Y zC!~^1IxeMioTr=a$*FX5o}5aYdCp%wo%4C#@B4Yb@B4n9_j%s$^DPVt*o{OOBLDzM zUmrq*9kt+hx~Un$1r;Kff!|g9CG8s~5J#of@DJvQgFo;K5Tb zzRDuyuXRsWP5@r~)fEb8pU@JU4#Nw8U#@;itt*ZT$ESgS0;BA9e_SSPuh9IoJZe`C z%r#SuS?+lL4vmnD3=dzCE8)t8^V|GL1qPNe|0cfk?iuQZ{SoA%P3Ar;Tlf(k97M5# z&M0%R4q4gWe*%F9I0$YZqxNI9XX>J8^ryPpg6a`|xdJMaA1=9>eD219qHFOdug0Fa z7B8MSF6iEq*-l_fafe!*lbSFLktMCplv2GevO@1*qdl*K$d-CAMONfWbYi1jM7d5* zmw#&Wj)P71(bdLbm)10mAIs_T%V>3HH9IltEogNnL1mhaV+C2AKAbkMG>I##*)g%v zmR@g8t=&kj)DI|yHH_u+JG>z&K23s67H>;zw4sa4Vr$n2U(~E0&4b!;X;K$f>$W6` zO?;zCMD^;>DvgjzjihEf*>7c~Lx)NRlg|w%oVXgr>+*uS+%h_yS?zXI@mg}dR%AUQ zLTQ&ZV;T%uQcQ{jlhkZ~R2EcyiklQ5{8cvJWI zFMel7D2zTm=73JpuwKdt`pBru+3~0Y*L4ocbq|f}^Czr-9`{ci(oG&gr;h4F6Q+5i zZme5IWxvMphA}0R(aX!rcDaS0l<&PiJtQC$aOK_d*VFH3XJ_Z;XBO_IuH ztUKvZSC%5cl(uCvfdIXG+zw4IslrhkaQvC!J0!)a6NNS8O{UyuFB&G9W96lT zm410{F;#)%Ej=5WXWBbCO7o5ue`=ay#q?z@ynn~rKoMJc)WSqioiI%(6=8MuMCO@aNm~V=*cPhrU@x=2bptEW?n2VRGet4Iof}z`Q*;y9cBH! z1@$G}rzI6R_{#2*`hwp6bwXh!wYO*jV?X!d*sy5k{IOJ)q;*2O_lXI?QYHK*R?f{F z;#q9rD0p1?PWh^r`oZhZtbNO1ZhQvROh2(xt{1){yH|@2FfNOR@k{{1DrDp$%RR~n zn57fR>G8C5IyUYwT}faY#=*rF<7A6*2*bEw9d}}#T&yt|ECz#H+j8SOK{6{ofx-QM TK_Jl}TPXm(!~jBz7xl14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>`T@Ck7RDsZl3cW7X6 zDQBoy;So1k)3T7EXt7K2I=hHo1)yy9e4D~mw%)Bm@l*A?cf}Sgw{~dYi0D(9bRaWl zk%dzYgLkXQ^aF(%^Np>`7^3>snm0yNuko$i;+VhGI%|Q2V;#G56HCDwQ`Z)j!u4j( z4J<|L%z(y1fO$4c`Ff{M^SX!|B zgf0B>TXV;e!&cCEOPgkai06r#<=Tq(wx7qT?&8RQ}+IJk$1(B%b)h{D=d0e^sMYT z8<%L^%NMU+y?Ct1$ssJGq$W}2{0L|UW0JSKOMLOgKwTh*y~NYkmHiPb6Qh)+bhJMx zNP0Y7977~7Cnp?Wl$aUt^Nh|KjT*BuZazO1JMryHr<(HXJ2mv*KX&bwpM$_LaiMMH z1tmpgf`Y9djvk41}z_!crpyI+rFAnFahn1BU zFA5c4T4gA>vG8KntAKTF%cgDXTQ_fCcptHiB=@13VJPy>UftDnm{r-UW|x#I>} literal 0 HcmV?d00001 diff --git a/src/duckstation-qt/resources/icons/go-up-16@2x.png b/src/duckstation-qt/resources/icons/go-up-16@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..18bbd26a460e0ae74a2715032878ec64c1fc411e GIT binary patch literal 1340 zcmV-C1;hG@P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Rd1pyZh5ei;FwEzGFKuJVFR9M69mQ8F_RTRg6 z=f3x*)RuNEEp0)BKwJ5+e3MeGW&DZ|i3Ut{LyQ|WF$$@P1fvTRHzp<|CT>hjTxnw1 zsUcuU{Adv|7*mtNqERgUu!Je4G8AU!-FsZTnL4%<+gi{C=jP;|#k=?azvukVy$^2N ze{9}t7q+$G#drGeF52k6ANYavZ1*v=K6V=tG+=O0PDg?~#4wCoS|CbsN) zYC&bZX#4%UH@md|=`AZk%K`t9^$lC!uU)h-e&+lM#8Yosb>mX4I~zOKVZXkq1ey;N z)z#JB^VychmW8K>j^l8QUc118^6H>0R=IB8s)65nJGJ|!5NK|vqPn7{bH@YEEa^Vq zfpuA|hB&ocNcFK|(duHGHQP!XM>_hByZ$-Vz@`@nO3UI$cRl#r(w^igtP25<+yLkr z{E9Uz*H%?k)EsJkl_hgZpsXtP@zW2#xPI_*kEFvfK*r$#Lvz6VR$fw^O!cF=5m{jjXcS+`T;fc!Lmt}t_~xO8;Ga#Wrpyn)S4vAtI6eFgMj>slaVay1C4&jTsGur1rz|O1 z$J(=c;|Un_#6f##I`_b|o>2iT8IqY^jIS8aASSAFL_l#i`p|d+lMnju z2q2IOhrw8kCwRVKyhv~;@v~hDfCJaT30u4iJYbs9{FnGyfrxDdLHy z$XsN=2Vy{=mH?-y1LqXUrQF%*3U)5%s-U2X0TuuukOeZicv=MVz%d{IVrnrlQ$o$W z28pC5{OcsyD{vq1o#o1j?@lHKPee5LAR0tf zGBFGcUxhYtm2MP;3TabZ#oSxXD zG7XIs0(&`NGX;UX+X+O%>F9c<{UDRHKa(5iw*6o1Pr4+`=S6;Xw*UYDC3HntbYx+4 zWjbSWWnpw>05UK!I4v+WEif@uGBY|cG&(UiD=;uRFfh&(j4c2F03~!qSaf7zbY(hi yZ)9m^c>ppnF*q$SH7zhPR5CLlF0000icons/duck.png icons/duck_128.png icons/duck_64.png + icons/edit-clear-16.png + icons/edit-clear-16@2x.png icons/edit-find.png icons/flag-eu.png icons/flag-eu@2x.png @@ -43,6 +45,10 @@ icons/flag-us@2x.png icons/folder-open.png icons/folder-open@2x.png + icons/go-down-16.png + icons/go-down-16@2x.png + icons/go-up-16.png + icons/go-up-16@2x.png icons/input-gaming.png icons/input-gaming@2x.png icons/list-add.png