#include "graphics_tuner.hpp" #include "button.hpp" #include "dusk/config.hpp" #include "dusk/logging.h" #include "dusk/settings.h" #include "m_Do/m_Do_audio.h" #include #include #include #include #include namespace dusk::ui { namespace { const Rml::String kDocumentSource = R"RML( )RML"; Rml::String format_internal_resolution(int value) { u32 width = 0; u32 height = 0; AuroraGetRenderSize(&width, &height); if (value <= 0) { return fmt::format("Auto ({}×{})", width, height); } return fmt::format("{}× ({}×{})", value, width, height); } Rml::String format_resampler(int value) { switch (static_cast(value)) { case Resampler::Bilinear: return "Bilinear"; case Resampler::Area: return "Area"; default: return ""; } } Rml::String format_post_process_mode(int value) { switch (static_cast(value)) { case BloomMode::Off: return "Off"; case BloomMode::Classic: return "Classic"; case BloomMode::Dusk: return "Dusklight"; default: return ""; } } Rml::String format_times(int value) { return fmt::format("{}×", value); } Rml::String format_percent(int value) { return fmt::format("{}%", value); } Rml::String format_bool(int value) { return value ? "On" : "Off"; } template int read_cvar(const ConfigVar& var) { if constexpr (std::is_same_v) { return static_cast(var.getValue() * 100.0f + 0.5f); } else { return static_cast(var.getValue()); } } template void write_cvar(ConfigVar& var, int value) { if constexpr (std::is_same_v) { var.setValue(static_cast(value) / 100.0f); } else if constexpr (std::is_same_v) { var.setValue(static_cast(value)); } else { var.setValue(static_cast(value)); } } template const GraphicsSetting& bind(Min min, Max max, Def def, int step, Rml::String (*label)(int), bool watchSize = false) { static const GraphicsSetting desc{ .min = static_cast(min), .max = static_cast(max), .defaultValue = static_cast(def), .step = step, .watchesRenderSize = watchSize, .read = []() -> int { return read_cvar(Var()); }, .write = [](int value) { write_cvar(Var(), value); }, .label = label, .cvarName = []() -> const char* { return Var().getName(); }, .isModified = []() -> bool { return Var().getValue() != Var().getDefaultValue(); }, }; return desc; } Rml::Element* create_stepped_carousel_root(Rml::Element* parent) { auto* doc = parent->GetOwnerDocument(); auto root = doc->CreateElement("stepped-carousel"); root->SetAttribute("tabindex", "0"); return parent->AppendChild(std::move(root)); } Rml::Element* create_stepped_carousel_arrow( Rml::Element* parent, const Rml::String& className, const Rml::String& label) { auto* button = append(parent, "button"); button->SetClass("stepped-carousel-arrow", true); button->SetClass(className, true); append_text(button, label); return button; } void update_carousel_arrow_color(Rml::Element* arrow, bool dim) { const Rml::Colourb& color = Rml::Colourb(255, 255, 255, dim ? 128 : 255); arrow->SetProperty(Rml::PropertyId::Color, Rml::Property(color, Rml::Unit::COLOUR)); } } // namespace const GraphicsSetting& GraphicsSetting::of(GraphicsOption option) { switch (option) { case GraphicsOption::InternalResolution: return bind<[]() -> auto& { return getSettings().game.internalResolutionScale; }>( 0, 12, 0, 1, format_internal_resolution, true); case GraphicsOption::ShadowResolution: return bind<[]() -> auto& { return getSettings().game.shadowResolutionMultiplier; }>( 1, 8, 1, 1, format_times); case GraphicsOption::Resampler: return bind<[]() -> auto& { return getSettings().game.resampler; }>( Resampler::Bilinear, Resampler::Area, Resampler::Bilinear, 1, format_resampler); case GraphicsOption::BloomMode: return bind<[]() -> auto& { return getSettings().game.bloomMode; }>( BloomMode::Off, BloomMode::Dusk, BloomMode::Classic, 1, format_post_process_mode); case GraphicsOption::BloomMultiplier: return bind<[]() -> auto& { return getSettings().game.bloomMultiplier; }>( 0, 100, 100, 10, format_percent); case GraphicsOption::DepthOfFieldMode: return bind<[]() -> auto& { return getSettings().game.depthOfFieldMode; }>( DepthOfFieldMode::Off, DepthOfFieldMode::Dusk, DepthOfFieldMode::Classic, 1, format_post_process_mode); case GraphicsOption::TextureReplacements: return bind<[]() -> auto& { return getSettings().game.enableTextureReplacements; }>( 0, 1, 0, 1, format_bool); } DuskLog.error("{} is an invalid GraphicsOption", static_cast(option)); abort(); } SteppedCarousel::SteppedCarousel(Rml::Element* parent, Props props) : Component(create_stepped_carousel_root(parent)), mProps(std::move(props)) { mPrevElem = create_stepped_carousel_arrow(mRoot, "prev", "\uE5CB"); mValueElem = append(mRoot, "stepped-carousel-value"); mNextElem = create_stepped_carousel_arrow(mRoot, "next", "\uE5CC"); listen(mPrevElem, Rml::EventId::Click, [this](Rml::Event&) { handle_nav_command(NavCommand::Left); }); listen(mNextElem, Rml::EventId::Click, [this](Rml::Event&) { handle_nav_command(NavCommand::Right); }); listen(mRoot, Rml::EventId::Keydown, [this](Rml::Event& event) { const auto cmd = map_nav_event(event); if (cmd != NavCommand::None && handle_nav_command(cmd)) { event.StopPropagation(); } }); } bool SteppedCarousel::focus() { return Component::focus(); } void SteppedCarousel::update() {} void SteppedCarousel::refresh() { if (mValueElem == nullptr) { return; } const int value = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max); if (mProps.formatValue) { set_text_content(mValueElem, mProps.formatValue(value)); } else { set_text_content(mValueElem, std::to_string(value)); } update_carousel_arrow_color(mPrevElem, value == mProps.min); update_carousel_arrow_color(mNextElem, value == mProps.max); } bool SteppedCarousel::handle_nav_command(NavCommand cmd) { if (cmd == NavCommand::Left) { const int value = mProps.getValue ? mProps.getValue() : 0; apply(std::clamp(value - mProps.step, mProps.min, mProps.max)); return true; } if (cmd == NavCommand::Right) { const int value = mProps.getValue ? mProps.getValue() : 0; apply(std::clamp(value + mProps.step, mProps.min, mProps.max)); return true; } return false; } void SteppedCarousel::apply(int value) { const int nextValue = std::clamp(value, mProps.min, mProps.max); const int currentValue = std::clamp(mProps.getValue ? mProps.getValue() : 0, mProps.min, mProps.max); if (nextValue == currentValue) { return; } mDoAud_seStartMenu(kSoundItemChange); if (mProps.onChange) { mProps.onChange(nextValue); } } GraphicsTuner::GraphicsTuner(GraphicsTunerProps props) : Document(kDocumentSource, false, DocumentScope::GraphicsTuner), mSetting(GraphicsSetting::of(props.option)) { if (mDocument == nullptr) { return; } if (auto* title = mDocument->GetElementById("title")) { append_text(title, props.title); } if (auto* description = mDocument->GetElementById("description")) { append_text(description, props.helpText); } if (auto* carouselParent = mDocument->GetElementById("carousel-container")) { mCarousel = &add_component(carouselParent, SteppedCarousel::Props{ .min = mSetting.min, .max = mSetting.max, .step = mSetting.step, .getValue = [this] { return mSetting.read(); }, .onChange = [this](int value) { mSetting.set(value); }, .formatValue = [this](int value) { return mSetting.label(value); }, }); } if (auto* footer = mDocument->GetElementById("footer")) { auto& returnButton = add_component