// Copyright 2014 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include #include #include #include #include #include "Common/StringUtil.h" #include "DolphinWX/PostProcessingConfigDiag.h" #include "VideoCommon/RenderBase.h" PostProcessingConfigDiag::PostProcessingConfigDiag(wxWindow* parent, const std::string& shader) : wxDialog(parent, wxID_ANY, _("Post Processing Shader Configuration")), m_shader(shader) { // Depending on if we are running already, either use the one from the videobackend // or generate our own. if (g_renderer && g_renderer->GetPostProcessor()) { m_post_processor = g_renderer->GetPostProcessor()->GetConfig(); } else { m_post_processor = new PostProcessingShaderConfiguration(); m_post_processor->LoadShader(m_shader); } // Create our UI classes const PostProcessingShaderConfiguration::ConfigMap& config_map = m_post_processor->GetOptions(); std::vector> config_groups; config_groups.reserve(config_map.size()); m_config_map.reserve(config_map.size()); for (const auto& it : config_map) { std::unique_ptr group; if (it.second.m_type == PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_BOOL) { group = std::make_unique(ConfigGrouping::WidgetType::TYPE_TOGGLE, it.second.m_gui_name, it.first, it.second.m_dependent_option, &it.second); } else { group = std::make_unique(ConfigGrouping::WidgetType::TYPE_SLIDER, it.second.m_gui_name, it.first, it.second.m_dependent_option, &it.second); } m_config_map[it.first] = group.get(); config_groups.emplace_back(std::move(group)); } // Arrange our vectors based on dependency for (auto& group : config_groups) { const std::string& parent_name = group->GetParent(); if (parent_name.empty()) { // It doesn't have a parent, just push it to the vector m_config_groups.emplace_back(std::move(group)); } else { // Since it depends on a different object, push it to a parent's object m_config_map[parent_name]->AddChild(std::move(group)); } } config_groups.clear(); // Full of null unique_ptrs now config_groups.shrink_to_fit(); const int space5 = FromDIP(5); const int space10 = FromDIP(10); // Generate our UI wxNotebook* const notebook = new wxNotebook(this, wxID_ANY); wxPanel* const page_general = new wxPanel(notebook); wxFlexGridSizer* const szr_general = new wxFlexGridSizer(2, space5, space5); // Now let's actually populate our window with our information bool add_general_page = false; for (const auto& it : m_config_groups) { if (it->HasChildren()) { // Options with children get their own tab wxPanel* const page_option = new wxPanel(notebook); wxBoxSizer* const wrap_sizer = new wxBoxSizer(wxVERTICAL); wxFlexGridSizer* const szr_option = new wxFlexGridSizer(2, space10, space5); it->GenerateUI(this, page_option, szr_option); // Add all the children for (const auto& child : it->GetChildren()) { child->GenerateUI(this, page_option, szr_option); } wrap_sizer->AddSpacer(space5); wrap_sizer->Add(szr_option, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); wrap_sizer->AddSpacer(space5); page_option->SetSizerAndFit(wrap_sizer); notebook->AddPage(page_option, it->GetGUIName()); } else { // Options with no children go in to the general tab if (!add_general_page) { // Make it so it doesn't show up if there aren't any options without children. add_general_page = true; } it->GenerateUI(this, page_general, szr_general); } } if (add_general_page) { wxBoxSizer* const wrap_sizer = new wxBoxSizer(wxVERTICAL); wrap_sizer->AddSpacer(space5); wrap_sizer->Add(szr_general, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); wrap_sizer->AddSpacer(space5); page_general->SetSizerAndFit(wrap_sizer); notebook->InsertPage(0, page_general, _("General")); } // Close Button wxStdDialogButtonSizer* const btn_strip = CreateStdDialogButtonSizer(wxOK | wxNO_DEFAULT); btn_strip->GetAffirmativeButton()->SetLabel(_("Close")); SetEscapeId(wxID_OK); // Treat closing the window by 'X' or hitting escape as 'OK' wxBoxSizer* const szr_main = new wxBoxSizer(wxVERTICAL); szr_main->AddSpacer(space5); szr_main->Add(notebook, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); szr_main->AddSpacer(space5); szr_main->Add(btn_strip, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); szr_main->AddSpacer(space5); szr_main->SetMinSize(FromDIP(wxSize(400, -1))); SetLayoutAdaptationMode(wxDIALOG_ADAPTATION_MODE_ENABLED); SetLayoutAdaptationLevel(wxDIALOG_ADAPTATION_STANDARD_SIZER); SetSizerAndFit(szr_main); Center(); SetFocus(); UpdateWindowUI(); } PostProcessingConfigDiag::~PostProcessingConfigDiag() { m_post_processor->SaveOptionsConfiguration(); if (g_renderer && g_renderer->GetPostProcessor()) m_post_processor = nullptr; else delete m_post_processor; } void PostProcessingConfigDiag::ConfigGrouping::GenerateUI(PostProcessingConfigDiag* dialog, wxWindow* parent, wxFlexGridSizer* sizer) { if (m_type == WidgetType::TYPE_TOGGLE) { m_option_checkbox = new wxCheckBox(parent, wxID_ANY, m_gui_name); m_option_checkbox->SetValue(m_config_option->m_bool_value); m_option_checkbox->Bind(wxEVT_CHECKBOX, &PostProcessingConfigDiag::Event_CheckBox, dialog, wxID_ANY, wxID_ANY, new UserEventData(m_option)); sizer->Add(m_option_checkbox); sizer->AddStretchSpacer(); } else { size_t vector_size = 0; if (m_config_option->m_type == PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER) vector_size = m_config_option->m_integer_values.size(); else vector_size = m_config_option->m_float_values.size(); wxFlexGridSizer* const szr_values = new wxFlexGridSizer(vector_size + 1); wxStaticText* const option_static_text = new wxStaticText(parent, wxID_ANY, m_gui_name); sizer->Add(option_static_text); for (size_t i = 0; i < vector_size; ++i) { // wxSlider uses a signed integer for it's minimum and maximum values // This won't work for floats. // Let's determine how many steps we can take and use that instead. int steps = 0; int current_value = 0; std::string string_value; if (m_config_option->m_type == PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER) { // Find out our range by taking the max subtracting the minimum. double range = m_config_option->m_integer_max_values[i] - m_config_option->m_integer_min_values[i]; // How many steps we have is the range divided by the step interval configured. // This may not be 100% spot on accurate since developers can have odd stepping intervals // set. // Round up so if it is outside our range, then set it to the minimum or maximum steps = std::ceil(range / (double)m_config_option->m_integer_step_values[i]); // Default value is just the currently set value here current_value = m_config_option->m_integer_values[i]; string_value = std::to_string(m_config_option->m_integer_values[i]); } else { // Same as above but with floats float range = m_config_option->m_float_max_values[i] - m_config_option->m_float_min_values[i]; steps = std::ceil(range / m_config_option->m_float_step_values[i]); // We need to convert our default float value from a float to the nearest step value range current_value = (m_config_option->m_float_values[i] / m_config_option->m_float_step_values[i]); string_value = std::to_string(m_config_option->m_float_values[i]); } DolphinSlider* slider = new DolphinSlider(parent, wxID_ANY, current_value, 0, steps, wxDefaultPosition, parent->FromDIP(wxSize(200, -1)), wxSL_HORIZONTAL | wxSL_BOTTOM); wxTextCtrl* text_ctrl = new wxTextCtrl(parent, wxID_ANY, string_value); // Disable the textctrl, it's only there to show the absolute value from the slider text_ctrl->Disable(); // wxWidget takes over the pointer provided to it in the event handler. // This won't be a memory leak, it'll be destroyed on dialog close. slider->Bind(wxEVT_SLIDER, &PostProcessingConfigDiag::Event_Slider, dialog, wxID_ANY, wxID_ANY, new UserEventData(m_option)); m_option_sliders.push_back(slider); m_option_text_ctrls.push_back(text_ctrl); } if (vector_size == 1) { szr_values->Add(m_option_sliders[0], 0, wxALIGN_CENTER_VERTICAL); szr_values->Add(m_option_text_ctrls[0], 0, wxALIGN_CENTER_VERTICAL); sizer->Add(szr_values); } else { wxFlexGridSizer* const szr_inside = new wxFlexGridSizer(2); for (size_t i = 0; i < vector_size; ++i) { szr_inside->Add(m_option_sliders[i], 0, wxALIGN_CENTER_VERTICAL); szr_inside->Add(m_option_text_ctrls[i], 0, wxALIGN_CENTER_VERTICAL); } szr_values->Add(szr_inside); sizer->Add(szr_values); } } } void PostProcessingConfigDiag::ConfigGrouping::EnableDependentChildren(bool enable) { // Enable or disable all the children for (auto& it : m_children) { if (it->m_type == ConfigGrouping::WidgetType::TYPE_TOGGLE) { it->m_option_checkbox->Enable(enable); } else { for (auto& slider : it->m_option_sliders) slider->Enable(enable); } // Set this objects children as well it->EnableDependentChildren(enable); } } void PostProcessingConfigDiag::Event_CheckBox(wxCommandEvent& ev) { UserEventData* config_option = (UserEventData*)ev.GetEventUserData(); ConfigGrouping* config = m_config_map[config_option->GetData()]; m_post_processor->SetOptionb(config->GetOption(), ev.IsChecked()); config->EnableDependentChildren(ev.IsChecked()); ev.Skip(); } void PostProcessingConfigDiag::Event_Slider(wxCommandEvent& ev) { UserEventData* config_option = (UserEventData*)ev.GetEventUserData(); ConfigGrouping* config = m_config_map[config_option->GetData()]; const auto& option_data = m_post_processor->GetOption(config->GetOption()); size_t vector_size = 0; if (option_data.m_type == PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER) vector_size = option_data.m_integer_values.size(); else vector_size = option_data.m_float_values.size(); for (size_t i = 0; i < vector_size; ++i) { // Got to do this garbage again. // Convert current step in to the real range value int current_step = config->GetSliderValue(i); std::string string_value; if (option_data.m_type == PostProcessingShaderConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER) { s32 value = option_data.m_integer_step_values[i] * current_step + option_data.m_integer_min_values[i]; m_post_processor->SetOptioni(config->GetOption(), i, value); string_value = std::to_string(value); } else { float value = option_data.m_float_step_values[i] * current_step + option_data.m_float_min_values[i]; m_post_processor->SetOptionf(config->GetOption(), i, value); string_value = std::to_string(value); } // Update the text box to include the new value config->SetSliderText(i, string_value); } ev.Skip(); }