#include "postprocessorhud.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../mwrender/postprocessor.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" namespace MWGui { void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch) { if (MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) return; MyGUI::ListBox::onKeyButtonPressed(key, ch); } PostProcessorHud::PostProcessorHud() : WindowBase("openmw_postprocessor_hud.layout") { getWidget(mTabConfiguration, "TabConfiguration"); getWidget(mActiveList, "ActiveList"); getWidget(mInactiveList, "InactiveList"); getWidget(mConfigLayout, "ConfigLayout"); getWidget(mFilter, "Filter"); getWidget(mButtonActivate, "ButtonActivate"); getWidget(mButtonDeactivate, "ButtonDeactivate"); getWidget(mButtonUp, "ButtonUp"); getWidget(mButtonDown, "ButtonDown"); mButtonActivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyActivatePressed); mButtonDeactivate->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyDeactivatePressed); mButtonUp->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderUpPressed); mButtonDown->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyShaderDownPressed); mActiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); mInactiveList->eventKeyButtonPressed += MyGUI::newDelegate(this, &PostProcessorHud::notifyKeyButtonPressed); mActiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); mInactiveList->eventListChangePosition += MyGUI::newDelegate(this, &PostProcessorHud::notifyListChangePosition); mFilter->eventEditTextChange += MyGUI::newDelegate(this, &PostProcessorHud::notifyFilterChanged); mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &PostProcessorHud::notifyWindowResize); mShaderInfo = mConfigLayout->createWidget("HeaderText", {}, MyGUI::Align::Default); mShaderInfo->setUserString("VStretch", "true"); mShaderInfo->setUserString("HStretch", "true"); mShaderInfo->setTextAlign(MyGUI::Align::Left | MyGUI::Align::Top); mShaderInfo->setEditReadOnly(true); mShaderInfo->setEditWordWrap(true); mShaderInfo->setEditMultiLine(true); mShaderInfo->setNeedMouseFocus(false); mConfigLayout->setVisibleVScroll(true); mConfigArea = mConfigLayout->createWidget("", {}, MyGUI::Align::Default); mConfigLayout->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); mConfigArea->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); } void PostProcessorHud::notifyFilterChanged(MyGUI::EditBox* sender) { updateTechniques(); } void PostProcessorHud::notifyWindowResize(MyGUI::Window* sender) { layout(); } void PostProcessorHud::notifyResetButtonClicked(MyGUI::Widget* sender) { for (size_t i = 1; i < mConfigArea->getChildCount(); ++i) { if (auto* child = dynamic_cast(mConfigArea->getChildAt(i))) child->toDefault(); } } void PostProcessorHud::notifyListChangePosition(MyGUI::ListBox* sender, size_t index) { if (sender == mActiveList) mInactiveList->clearIndexSelected(); else if (sender == mInactiveList) mActiveList->clearIndexSelected(); if (index >= sender->getItemCount()) return; updateConfigView(sender->getItemNameAt(index)); } void PostProcessorHud::toggleTechnique(bool enabled) { auto* list = enabled ? mInactiveList : mActiveList; size_t selected = list->getIndexSelected(); if (selected != MyGUI::ITEM_NONE) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); mOverrideHint = list->getItemNameAt(selected); auto technique = *list->getItemDataAt>(selected); if (technique->getDynamic()) return; if (enabled) processor->enableTechnique(technique); else processor->disableTechnique(technique); processor->saveChain(); } } void PostProcessorHud::notifyActivatePressed(MyGUI::Widget* sender) { toggleTechnique(true); } void PostProcessorHud::notifyDeactivatePressed(MyGUI::Widget* sender) { toggleTechnique(false); } void PostProcessorHud::moveShader(Direction direction) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); size_t selected = mActiveList->getIndexSelected(); if (selected == MyGUI::ITEM_NONE) return; int index = direction == Direction::Up ? static_cast(selected) - 1 : selected + 1; index = std::clamp(index, 0, mActiveList->getItemCount() - 1); if (static_cast(index) != selected) { auto technique = *mActiveList->getItemDataAt>(selected); if (technique->getDynamic()) return; if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error) processor->saveChain(); } } void PostProcessorHud::notifyShaderUpPressed(MyGUI::Widget* sender) { moveShader(Direction::Up); } void PostProcessorHud::notifyShaderDownPressed(MyGUI::Widget* sender) { moveShader(Direction::Down); } void PostProcessorHud::notifyKeyButtonPressed(MyGUI::Widget* sender, MyGUI::KeyCode key, MyGUI::Char ch) { MyGUI::ListBox* list = static_cast(sender); if (list->getIndexSelected() == MyGUI::ITEM_NONE) return; if (key == MyGUI::KeyCode::ArrowLeft && list == mActiveList) { if (MyGUI::InputManager::getInstance().isShiftPressed()) { toggleTechnique(false); } else { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mInactiveList); mActiveList->clearIndexSelected(); select(mInactiveList, 0); } } else if (key == MyGUI::KeyCode::ArrowRight && list == mInactiveList) { if (MyGUI::InputManager::getInstance().isShiftPressed()) { toggleTechnique(true); } else { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mActiveList); mInactiveList->clearIndexSelected(); select(mActiveList, 0); } } else if (list == mActiveList && MyGUI::InputManager::getInstance().isShiftPressed() && (key == MyGUI::KeyCode::ArrowUp || key == MyGUI::KeyCode::ArrowDown)) { moveShader(key == MyGUI::KeyCode::ArrowUp ? Direction::Up : Direction::Down); } } void PostProcessorHud::onOpen() { toggleMode(Settings::ShaderManager::Mode::Debug); updateTechniques(); } void PostProcessorHud::onClose() { toggleMode(Settings::ShaderManager::Mode::Normal); } void PostProcessorHud::layout() { constexpr int padding = 12; constexpr int padding2 = padding * 2; mShaderInfo->setCoord( padding, padding, mConfigLayout->getSize().width - padding2 - padding, mShaderInfo->getTextSize().height); int totalHeight = mShaderInfo->getTop() + mShaderInfo->getTextSize().height + padding; mConfigArea->setCoord({ padding, totalHeight, mShaderInfo->getSize().width, mConfigLayout->getHeight() }); int childHeights = 0; MyGUI::EnumeratorWidgetPtr enumerator = mConfigArea->getEnumerator(); while (enumerator.next()) { enumerator.current()->setCoord(padding, childHeights + padding, mShaderInfo->getSize().width - padding2, enumerator.current()->getHeight()); childHeights += enumerator.current()->getHeight() + padding; } totalHeight += childHeights; mConfigArea->setSize(mConfigArea->getWidth(), childHeights); mConfigLayout->setCanvasSize(mConfigLayout->getWidth() - padding2, totalHeight); mConfigLayout->setSize(mConfigLayout->getWidth(), mConfigLayout->getParentSize().height - padding2); } void PostProcessorHud::notifyMouseWheel(MyGUI::Widget* sender, int rel) { int offset = mConfigLayout->getViewOffset().top + rel * 0.3; if (offset > 0) mConfigLayout->setViewOffset(MyGUI::IntPoint(0, 0)); else mConfigLayout->setViewOffset(MyGUI::IntPoint(0, static_cast(offset))); } void PostProcessorHud::select(ListWrapper* list, size_t index) { list->setIndexSelected(index); notifyListChangePosition(list, index); } void PostProcessorHud::toggleMode(Settings::ShaderManager::Mode mode) { Settings::ShaderManager::get().setMode(mode); MWBase::Environment::get().getWorld()->getPostProcessor()->toggleMode(); if (!isVisible()) return; if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) updateConfigView(mInactiveList->getItemNameAt(mInactiveList->getIndexSelected())); else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) updateConfigView(mActiveList->getItemNameAt(mActiveList->getIndexSelected())); } void PostProcessorHud::updateConfigView(const std::string& name) { auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); auto technique = processor->loadTechnique(name); if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) return; while (mConfigArea->getChildCount() > 0) MyGUI::Gui::getInstance().destroyWidget(mConfigArea->getChildAt(0)); mShaderInfo->setCaption({}); std::ostringstream ss; const std::string_view NA = "#{Interface:NotAvailableShort}"; const char endl = '\n'; std::string_view author = technique->getAuthor().empty() ? NA : technique->getAuthor(); std::string_view version = technique->getVersion().empty() ? NA : technique->getVersion(); std::string_view description = technique->getDescription().empty() ? NA : technique->getDescription(); auto serializeBool = [](bool value) { return value ? "#{sYes}" : "#{sNo}"; }; const auto flags = technique->getFlags(); const auto flag_interior = serializeBool(!(flags & fx::Technique::Flag_Disable_Interiors)); const auto flag_exterior = serializeBool(!(flags & fx::Technique::Flag_Disable_Exteriors)); const auto flag_underwater = serializeBool(!(flags & fx::Technique::Flag_Disable_Underwater)); const auto flag_abovewater = serializeBool(!(flags & fx::Technique::Flag_Disable_Abovewater)); switch (technique->getStatus()) { case fx::Technique::Status::Success: case fx::Technique::Status::Uncompiled: { if (technique->getDynamic()) ss << "#{fontcolourhtml=header}#{PostProcessing:ShaderLocked}: #{fontcolourhtml=normal} " "#{PostProcessing:ShaderLockedDescription}" << endl << endl; ss << "#{fontcolourhtml=header}#{PostProcessing:Author}: #{fontcolourhtml=normal} " << author << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:Version}: #{fontcolourhtml=normal} " << version << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:Description}: #{fontcolourhtml=normal} " << description << endl << endl << "#{fontcolourhtml=header}#{PostProcessing:InInteriors}: #{fontcolourhtml=normal} " << flag_interior << "#{fontcolourhtml=header} #{PostProcessing:InExteriors}: #{fontcolourhtml=normal} " << flag_exterior << "#{fontcolourhtml=header} #{PostProcessing:Underwater}: #{fontcolourhtml=normal} " << flag_underwater << "#{fontcolourhtml=header} #{PostProcessing:Abovewater}: #{fontcolourhtml=normal} " << flag_abovewater; break; } case fx::Technique::Status::Parse_Error: ss << "#{fontcolourhtml=negative}Shader Compile Error: #{fontcolourhtml=normal} <" << std::string(technique->getName()) << "> failed to compile." << endl << endl << technique->getLastError(); break; case fx::Technique::Status::File_Not_exists: break; } mShaderInfo->setCaptionWithReplacing(ss.str()); if (Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug) { if (technique->getUniformMap().size() > 0) { MyGUI::Button* resetButton = mConfigArea->createWidget("MW_Button", { 0, 0, 0, 24 }, MyGUI::Align::Default); resetButton->setCaptionWithReplacing("#{PostProcessing:ResetShader}"); resetButton->setTextAlign(MyGUI::Align::Center); resetButton->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); resetButton->eventMouseButtonClick += MyGUI::newDelegate(this, &PostProcessorHud::notifyResetButtonClicked); } for (const auto& uniform : technique->getUniformMap()) { if (!uniform->mStatic || uniform->mSamplerType) continue; if (!uniform->mHeader.empty()) { Gui::AutoSizedTextBox* divider = mConfigArea->createWidget( "MW_UniformGroup", { 0, 0, 0, 34 }, MyGUI::Align::Default); divider->setNeedMouseFocus(false); divider->setCaptionWithReplacing(uniform->mHeader); } fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget( "MW_UniformEdit", { 0, 0, 0, 22 }, MyGUI::Align::Default); uwidget->init(uniform); uwidget->getLabel()->eventMouseWheel += MyGUI::newDelegate(this, &PostProcessorHud::notifyMouseWheel); } } layout(); } void PostProcessorHud::updateTechniques() { if (!isVisible()) return; std::string hint; ListWrapper* hintWidget = nullptr; if (mInactiveList->getIndexSelected() != MyGUI::ITEM_NONE) { hint = mInactiveList->getItemNameAt(mInactiveList->getIndexSelected()); hintWidget = mInactiveList; } else if (mActiveList->getIndexSelected() != MyGUI::ITEM_NONE) { hint = mActiveList->getItemNameAt(mActiveList->getIndexSelected()); hintWidget = mActiveList; } mInactiveList->removeAllItems(); mActiveList->removeAllItems(); auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); for (const auto& [name, _] : processor->getTechniqueMap()) { auto technique = processor->loadTechnique(name); if (!technique) continue; if (!technique->getHidden() && !processor->isTechniqueEnabled(technique) && name.find(mFilter->getCaption()) != std::string::npos) mInactiveList->addItem(name, technique); } for (auto technique : processor->getTechniques()) { if (!technique->getHidden()) mActiveList->addItem(technique->getName(), technique); } auto tryFocus = [this](ListWrapper* widget, const std::string& hint) { size_t index = widget->findItemIndexWith(hint); if (index != MyGUI::ITEM_NONE) { MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(widget); select(widget, index); } }; if (!mOverrideHint.empty()) { tryFocus(mActiveList, mOverrideHint); tryFocus(mInactiveList, mOverrideHint); mOverrideHint.clear(); } else if (hintWidget && !hint.empty()) tryFocus(hintWidget, hint); } void PostProcessorHud::registerMyGUIComponents() { MyGUI::FactoryManager& factory = MyGUI::FactoryManager::getInstance(); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); factory.registerFactory("Widget"); } }