From 708c276787dd7f0a4ba2023aca0f645688866706 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 3 Oct 2026 12:56:54 +0000 Subject: [PATCH 1/6] Add GUI scale setting and volume contrast setting GUI scale (#737): - add display.gui-scale setting (0 - auto, 1..4 - fixed integer scale) - auto mode picks the largest scale keeping at least 640x480 UI units - apply scale in GUI (root size, camera, cursor, tooltips, scissors) - HUD layout and ModelViewer use scaled coordinates - add GUI Scale trackbar to display settings Volume contrast (#399): - add audio.volume-contrast setting (100% - unchanged sounds, 0% - loud sounds are lowered to the level of quiet ones) - loudness (RMS) of a sound is calculated once and cached - add trackbar to audio settings and reset page --- res/layouts/pages/settings_audio.xml.lua | 7 ++- res/layouts/pages/settings_display.xml.lua | 9 +++ res/layouts/pages/settings_reset.xml.lua | 1 + res/texts/ru_RU.txt | 5 ++ src/audio/audio.cpp | 64 +++++++++++++++++++++- src/frontend/hud.cpp | 9 +-- src/frontend/hud.hpp | 2 +- src/graphics/core/DrawContext.cpp | 6 +- src/graphics/core/DrawContext.hpp | 5 ++ src/graphics/ui/GUI.cpp | 46 +++++++++++++--- src/graphics/ui/GUI.hpp | 12 ++++ src/graphics/ui/elements/ModelViewer.cpp | 2 +- src/io/settings_io.cpp | 2 + src/settings.hpp | 5 ++ 14 files changed, 158 insertions(+), 17 deletions(-) diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index 97628d1e8..6720496df 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -1,6 +1,7 @@ -function create_trackbar_setting(id, name, step, postfix) +function create_trackbar_setting(id, name, step, postfix, tooltip) local info = app.get_setting_info(id) postfix = postfix or "" + tooltip = tooltip or "" document.root:add(gui.template("track_setting", { id=id, name=gui.str(name, "settings"), @@ -8,7 +9,8 @@ function create_trackbar_setting(id, name, step, postfix) min=info.min, max=info.max, step=step, - postfix=postfix + postfix=postfix, + tooltip=gui.str(tooltip, "settings") })) update_trackbar_label(app.get_setting(id), id, name, postfix) end @@ -78,6 +80,7 @@ function on_open() create_trackbar_setting("audio.volume-ui", "UI Sounds", 0.01) create_trackbar_setting("audio.volume-ambient", "Ambient", 0.01) create_trackbar_setting("audio.volume-music", "Music", 0.01) + create_trackbar_setting("audio.volume-contrast", "Volume Contrast", 0.01, "", "audio.volume-contrast.tooltip") document.root:add("") document.root:add("".. diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua index 6ab08312b..6e645b184 100644 --- a/res/layouts/pages/settings_graphics.xml.lua +++ b/res/layouts/pages/settings_graphics.xml.lua @@ -1,4 +1,4 @@ -local settings = require "core:settings_common".new(document, {}) +local settings = require "core:settings_common".new(document, {app=app}) -- templates call these functions by name, so they must be global create_trackbar_setting = settings.create_trackbar_setting update_trackbar_label = settings.update_trackbar_label diff --git a/res/modules/settings_common.lua b/res/modules/settings_common.lua index aa635d99a..886c4bfc8 100644 --- a/res/modules/settings_common.lua +++ b/res/modules/settings_common.lua @@ -3,6 +3,7 @@ -- Usage in a page script: -- -- local settings = require "core:settings_common".new(document, { +-- app = app, -- tostring_overrides = {["display.framerate"] = function(x) ... end} -- }) -- -- templates call these functions by name, so they must be global @@ -15,9 +16,11 @@ local settings_common = {} --- Create helpers bound to the page document. --- @param document the page document --- @param options table (optional) with fields: +--- app: the page's `app` library (not visible inside modules) --- tostring_overrides: {[setting_id] = function(value) -> string} function settings_common.new(document, options) options = options or {} + local app = options.app local tostring_overrides = options.tostring_overrides or {} local this = {} diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index dab7dd06b..68f654d98 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -43,7 +43,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/ audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой audio.volume-contrast.tooltip=Меньше - громкие звуки приглушаются до уровня тихих (ночной режим) audio.recording-enabled.tooltip=Включает функции записи звука -display.gui-scale.tooltip=Размер интерфейса: Авто или 1-4. Слишком большой масштаб уменьшается под размер окна +display.gui-scale.tooltip=Размер интерфейса: Авто или 0.5-4. Меню уменьшается, если не помещается в окно graphics.enable-fog.tooltip=Отображать туман / дымку graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту diff --git a/src/graphics/core/Batch2D.cpp b/src/graphics/core/Batch2D.cpp index 33265b49f..ab09955fe 100644 --- a/src/graphics/core/Batch2D.cpp +++ b/src/graphics/core/Batch2D.cpp @@ -44,6 +44,10 @@ void Batch2D::vertex( float u, float v, float r, float g, float b, float a ) { + if (pixelSnap > 0.0f) { + x = std::round(x * pixelSnap) / pixelSnap; + y = std::round(y * pixelSnap) / pixelSnap; + } buffer[index].position = {x, y}; buffer[index].uv = {u * region.getWidth() + region.u1, v * region.getHeight() + region.v1}; buffer[index].color = {r, g, b, a}; @@ -54,6 +58,9 @@ void Batch2D::vertex( glm::vec2 uvpoint, float r, float g, float b, float a ) { + if (pixelSnap > 0.0f) { + point = glm::round(point * pixelSnap) / pixelSnap; + } buffer[index].position = point; buffer[index].uv = {uvpoint.x * region.getWidth() + region.u1, uvpoint.y * region.getHeight() + region.v1}; buffer[index].color = {r, g, b, a}; @@ -83,6 +90,10 @@ void Batch2D::setRegion(UVRegion region) { this->region = region; } +void Batch2D::setPixelSnap(float pixelsPerUnit) { + pixelSnap = pixelsPerUnit; +} + void Batch2D::point(float x, float y, float r, float g, float b, float a){ if (index + 6 >= capacity) flush(); diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index ec181a6fb..9154c9212 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -33,6 +33,8 @@ class Batch2D : public Flushable { const Texture* currentTexture; DrawPrimitive primitive = DrawPrimitive::triangle; UVRegion region {0.0f, 0.0f, 1.0f, 1.0f}; + /// @brief Vertex positions are rounded to 1/pixelSnap (0 - disabled) + float pixelSnap = 0.0f; void setPrimitive(DrawPrimitive primitive); @@ -56,6 +58,11 @@ public: void texture(const Texture* texture); void untexture(); void setRegion(UVRegion region); + + /// @brief Align vertices to the pixel grid (fixes artifacts of textured + /// quads on fractional UI scale) + /// @param pixelsPerUnit window pixels per one unit, 0 - disable + void setPixelSnap(float pixelsPerUnit); void sprite(float x, float y, float w, float h, const UVRegion& region, glm::vec4 tint); void sprite(float x, float y, float w, float h, int atlasRes, int index, glm::vec4 tint); void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint); diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index dbdf8ece4..871cc702f 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -1,6 +1,7 @@ #include "GUI.hpp" #include +#include #include #include "assets/Assets.hpp" @@ -103,16 +104,28 @@ int GUI::calcMaxScale(const glm::uvec2& viewport) { return maxFit; } -int GUI::calcScale(const glm::uvec2& viewport) const { - int maxFit = calcMaxScale(viewport); - int value = engine.getSettings().display.guiScale.get(); - if (value <= 0) { - return maxFit; // auto +float GUI::calcScale(const glm::uvec2& viewport) const { + float value = engine.getSettings().display.guiScale.get(); + float result = value <= 0.0f + ? static_cast(calcMaxScale(viewport)) // auto + : value; + + // while a menu page is open, it must fit the window + auto& page = menu->getCurrent(); + if (page.panel) { + auto size = page.panel->getSize(); + if (size.x > 0.0f && size.y > 0.0f) { + float fit = std::min(viewport.x / size.x, viewport.y / size.y); + fit = std::floor(fit * 2.0f) / 2.0f; // step 0.5 + result = std::min(result, std::max(fit, 0.5f)); + } } - return std::min(value, maxFit); + // keep UI size at least 1 unit + float limit = static_cast(std::min(viewport.x, viewport.y)); + return std::max(0.5f, std::min(result, std::max(limit, 0.5f))); } -int GUI::getScale() const { +float GUI::getScale() const { return scale; } @@ -122,8 +135,8 @@ int GUI::getMaxScale() const { CursorState GUI::getCursor() const { auto cursor = input.getCursor(); - cursor.pos /= static_cast(scale); - cursor.delta /= static_cast(scale); + cursor.pos /= scale; + cursor.delta /= scale; return cursor; } @@ -282,7 +295,7 @@ void GUI::actFocused() { void GUI::act(float delta, const glm::uvec2& vp) { scale = calcScale(vp); - container->setSize(glm::vec2(vp) / static_cast(scale)); + container->setSize(glm::vec2(vp) / scale); for (auto& pair : frames) { pair.second->act(delta); } @@ -318,11 +331,11 @@ void GUI::postAct() { void GUI::draw(const DrawContext& pctx, Assets& assets) { auto ctx = pctx.sub(batch2D.get()); - ctx.setUiScale(static_cast(scale)); + ctx.setUiScale(scale); // viewport in UI units const glm::vec2 viewport = - glm::vec2(ctx.getViewport()) / static_cast(scale); + glm::vec2(ctx.getViewport()) / scale; auto& page = menu->getCurrent(); if (page.panel) { @@ -341,6 +354,9 @@ void GUI::draw(const DrawContext& pctx, Assets& assets) { uishader->uniformMatrix("u_projview", uicamera->getProjView()); batch2D->begin(); + // fractional scale puts quad edges at pixel centers, which makes + // atlas sampling bleed into neighbour glyphs + batch2D->setPixelSnap(scale != std::floor(scale) ? scale : 0.0f); for (auto& [outputTexture, frame] : frames) { frame->updateOutput(assets); frame->draw(ctx, assets); @@ -382,6 +398,7 @@ void GUI::draw(const DrawContext& pctx, Assets& assets) { batch2D->setColor(0, 255, 0); batch2D->lineRect(pos.x, pos.y, size.x-1, size.y-1); } + batch2D->setPixelSnap(0.0f); } std::shared_ptr GUI::getFocused() const { diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index d40edb3e1..4a0d610f9 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -87,7 +87,7 @@ namespace gui { vec2supplier cursorLocator; /// @brief Current GUI scale (UI units -> window pixels) - int scale = 1; + float scale = 1.0f; float tooltipTimer = 0.0f; float doubleClickTimer = 0.0f; @@ -96,8 +96,7 @@ namespace gui { bool debug = false; static int calcMaxScale(const glm::uvec2& viewport); - int calcScale(const glm::uvec2& viewport) const; - CursorState getScaledCursor() const; + float calcScale(const glm::uvec2& viewport) const; void actMouse(Frame& frame, float delta, const CursorState& cursor); void actFocused(); @@ -133,9 +132,9 @@ namespace gui { void postAct(); /// @brief Get current GUI scale: window pixels per one UI unit - int getScale() const; + float getScale() const; - /// @brief Get maximal GUI scale that fits the current window size + /// @brief Get GUI scale used in auto mode (fits the current window size) int getMaxScale() const; /// @brief Get cursor state in UI coordinates diff --git a/src/logic/scripting/lua/libs/libgui.cpp b/src/logic/scripting/lua/libs/libgui.cpp index 5625c7456..340afd918 100644 --- a/src/logic/scripting/lua/libs/libgui.cpp +++ b/src/logic/scripting/lua/libs/libgui.cpp @@ -1068,7 +1068,7 @@ static int l_gui_getviewport(lua::State* L) { } static int l_gui_get_scale(lua::State* L) { - return lua::pushinteger(L, engine->getGUI().getScale()); + return lua::pushnumber(L, engine->getGUI().getScale()); } static int l_gui_get_max_scale(lua::State* L) { diff --git a/src/settings.hpp b/src/settings.hpp index 478395ecb..88567f386 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -41,8 +41,8 @@ struct DisplaySettings { FlagSetting limitFpsIconified {false}; /// @brief Adaptive framerate in menu (experimental) FlagSetting adaptiveFpsInMenu {false}; - /// @brief GUI scale factor (0 - auto, 1..4 - integer scale limited by window size) - IntegerSetting guiScale {0, 0, 4}; + /// @brief GUI scale factor (0 - auto, 0.5..4 - explicit scale) + NumberSetting guiScale {0.0f, 0.0f, 4.0f}; }; struct ChunksSettings { From 11b44b78f7357c43cf61eba617844205065ff39c Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 3 Oct 2026 13:31:38 +0000 Subject: [PATCH 5/6] Apply explicit GUI scale as is Explicitly chosen scale is no longer reduced to fit the open menu page (2 stayed 1.5 in settings). Fitting to the window remains in auto mode. --- res/layouts/pages/settings_display.xml.lua | 8 ++------ res/texts/ru_RU.txt | 1 - src/graphics/ui/GUI.cpp | 5 +++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/res/layouts/pages/settings_display.xml.lua b/res/layouts/pages/settings_display.xml.lua index e7f58f8ef..446d6b4b3 100644 --- a/res/layouts/pages/settings_display.xml.lua +++ b/res/layouts/pages/settings_display.xml.lua @@ -11,12 +11,8 @@ end tostring_overrides["display.gui-scale"] = function(x) if x == 0 then - return string.format("%s (%d)", gui.str("Auto"), gui.get_max_scale()) - end - local effective = gui.get_scale() - if effective ~= x then - -- menu is limited by window size - return string.format("%s (%s %s)", tostring(x), gui.str("Effective"), tostring(effective)) + -- the scale chosen automatically for the current window + return string.format("%s (%s)", gui.str("Auto"), tostring(gui.get_scale())) end return tostring(x) end diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index 68f654d98..bf5032042 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -13,7 +13,6 @@ Description=Описание Converting world...=Выполняется конвертация мира... Unlimited=Неограниченно Auto=Авто -Effective=действует Chat=Чат Console=Консоль Log=Лог diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 871cc702f..d74c5bda6 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -110,9 +110,10 @@ float GUI::calcScale(const glm::uvec2& viewport) const { ? static_cast(calcMaxScale(viewport)) // auto : value; - // while a menu page is open, it must fit the window + // explicitly set scale is applied as is; in auto mode an open menu page + // must fit the window auto& page = menu->getCurrent(); - if (page.panel) { + if (value <= 0.0f && page.panel) { auto size = page.panel->getSize(); if (size.x > 0.0f && size.y > 0.0f) { float fit = std::min(viewport.x / size.x, viewport.y / size.y); From 1ed64e4065db14d79c68ad18045c6beb6d960ecc Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Sat, 3 Oct 2026 17:20:44 +0200 Subject: [PATCH 6/6] Add English tooltips for GUI scale and volume contrast settings --- res/texts/en_US.txt | 2 ++ res/texts/ru_RU.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 294758a7e..476663adb 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -18,7 +18,9 @@ devtools.output=Output # Tooltips audio.acoustic-effects.tooltip=Acoustic effects may not be supported by your system +audio.volume-contrast.tooltip=Lower value - loud sounds are lowered to the level of quiet ones (night mode) audio.recording-enabled.tooltip=Enables audio recording features +display.gui-scale.tooltip=GUI size: Auto or 0.5-4. In auto mode the menu is reduced to fit the window graphics.enable-fog.tooltip=Enables fog graphics.gamma.tooltip=Lighting brightness curve graphics.backlight.tooltip=Backlight to prevent total darkness diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index bf5032042..e78147d26 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -42,7 +42,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/ audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой audio.volume-contrast.tooltip=Меньше - громкие звуки приглушаются до уровня тихих (ночной режим) audio.recording-enabled.tooltip=Включает функции записи звука -display.gui-scale.tooltip=Размер интерфейса: Авто или 0.5-4. Меню уменьшается, если не помещается в окно +display.gui-scale.tooltip=Размер интерфейса: Авто или 0.5-4. В режиме Авто меню уменьшается, если не помещается в окно graphics.enable-fog.tooltip=Отображать туман / дымку graphics.gamma.tooltip=Кривая яркости освещения graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту