mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
Merge 1ed64e4065 into dacebf94bc
This commit is contained in:
commit
6f4e55a6e6
27 changed files with 355 additions and 129 deletions
|
|
@ -16,6 +16,17 @@ gui.str(text: str, context: str) -> str
|
|||
-- Returns size of the main container (window).
|
||||
gui.get_viewport() -> {int, int}
|
||||
|
||||
-- Returns current GUI scale: window pixels per one UI unit (0.5..4).
|
||||
-- Set by the display.gui-scale setting (auto mode fits the window size).
|
||||
gui.get_scale() -> number
|
||||
|
||||
-- Returns GUI scale used in auto mode (fits the current window size).
|
||||
gui.get_max_scale() -> int
|
||||
|
||||
-- Returns cursor position in UI units (unlike input.get_mouse_pos,
|
||||
-- which returns window pixels). Use it to position UI elements.
|
||||
gui.get_cursor_pos() -> {number, number}
|
||||
|
||||
-- Returns environment (global variables table) of the specified document.
|
||||
gui.get_env(document: str) -> table
|
||||
```
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ end, document.root)
|
|||
input.get_mouse_pos() --> {int, int}
|
||||
```
|
||||
|
||||
Returns cursor screen position.
|
||||
Returns cursor screen position in window pixels.
|
||||
To get position in UI units (affected by GUI scale) use `gui.get_cursor_pos()`.
|
||||
|
||||
```lua
|
||||
input.get_mouse_delta() --> {int, int}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,17 @@ gui.str(text: string, context: string) -> string
|
|||
-- Возвращает размер главного контейнера (окна).
|
||||
gui.get_viewport() -> {number, number}
|
||||
|
||||
-- Возвращает текущий масштаб интерфейса: пикселей окна в одной единице UI (0.5..4).
|
||||
-- Задаётся настройкой display.gui-scale (в режиме Авто подбирается под размер окна).
|
||||
gui.get_scale() -> number
|
||||
|
||||
-- Возвращает масштаб интерфейса, который выбирается в режиме Авто (помещается в текущее окно).
|
||||
gui.get_max_scale() -> int
|
||||
|
||||
-- Возвращает позицию курсора в единицах UI (в отличие от input.get_mouse_pos,
|
||||
-- возвращающей пиксели окна). Используйте для позиционирования элементов UI.
|
||||
gui.get_cursor_pos() -> {number, number}
|
||||
|
||||
-- Возвращает окружение (глобальные переменные) указанного документа.
|
||||
gui.get_env(document: string) -> table
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ input.set_enabled(bindname: string, flag: boolean)
|
|||
|
||||
## Курсор мыши
|
||||
```lua
|
||||
-- Возвращает позицию курсора на экране.
|
||||
-- Возвращает позицию курсора на экране в пикселях окна.
|
||||
-- Для позиции в единицах UI (с учётом масштаба) используйте gui.get_cursor_pos().
|
||||
input.get_mouse_pos() -> {number, number}
|
||||
|
||||
-- Возращает дельту позиции курсора.
|
||||
|
|
|
|||
|
|
@ -1,28 +1,7 @@
|
|||
function create_trackbar_setting(id, name, step, postfix)
|
||||
local info = app.get_setting_info(id)
|
||||
postfix = postfix or ""
|
||||
document.root:add(gui.template("track_setting", {
|
||||
id=id,
|
||||
name=gui.str(name, "settings"),
|
||||
value=app.get_setting(id),
|
||||
min=info.min,
|
||||
max=info.max,
|
||||
step=step,
|
||||
postfix=postfix
|
||||
}))
|
||||
update_trackbar_label(app.get_setting(id), id, name, postfix)
|
||||
end
|
||||
|
||||
function update_trackbar_label(x, id, name, postfix)
|
||||
app.set_setting(id, x)
|
||||
-- updating label
|
||||
document[id..".L"].text = string.format(
|
||||
"%s: %s%s",
|
||||
gui.str(name, "settings"),
|
||||
app.str_setting(id),
|
||||
postfix
|
||||
)
|
||||
end
|
||||
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
|
||||
|
||||
function update_checkbox_setting(id, value)
|
||||
app.set_setting(id, value)
|
||||
|
|
@ -41,12 +20,8 @@ function update_checkbox_setting(id, value)
|
|||
end
|
||||
end
|
||||
|
||||
function create_checkbox(id, name, tooltip)
|
||||
tooltip = tooltip or ''
|
||||
document.root:add(string.format(
|
||||
"<checkbox consumer='function(x) update_checkbox_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
|
||||
id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
|
||||
))
|
||||
local function create_audio_checkbox(id, name, tooltip)
|
||||
settings.create_checkbox(id, name, tooltip, "update_checkbox_setting")
|
||||
update_checkbox_setting(id, app.get_setting(id))
|
||||
end
|
||||
|
||||
|
|
@ -78,6 +53,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("<label context='settings'>@Microphone</label>")
|
||||
document.root:add("<select id='input_device_select' "..
|
||||
|
|
@ -100,6 +76,6 @@ function on_open()
|
|||
selectbox.value = app.get_setting("audio.input-device")
|
||||
end
|
||||
|
||||
create_checkbox("audio.recording-enabled", "Microphone access", "audio.recording-enabled.tooltip")
|
||||
create_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip")
|
||||
create_audio_checkbox("audio.recording-enabled", "Microphone access", "audio.recording-enabled.tooltip")
|
||||
create_audio_checkbox("audio.acoustic-effects", "Acoustic effects", "audio.acoustic-effects.tooltip")
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,54 +9,24 @@ tostring_overrides["display.framerate"] = function(x)
|
|||
end
|
||||
end
|
||||
|
||||
function create_trackbar_setting(id, name, step, postfix, tooltip, changeonrelease)
|
||||
local info = app.get_setting_info(id)
|
||||
postfix = postfix or ""
|
||||
tooltip = tooltip or ""
|
||||
changeonrelease = changeonrelease or ""
|
||||
document.root:add(gui.template("track_setting", {
|
||||
id=id,
|
||||
name=gui.str(name, "settings"),
|
||||
value=app.get_setting(id),
|
||||
min=info.min,
|
||||
max=info.max,
|
||||
step=step,
|
||||
postfix=postfix,
|
||||
tooltip=tooltip,
|
||||
changeonrelease=changeonrelease
|
||||
}))
|
||||
update_trackbar_label(app.get_setting(id), id, name, postfix)
|
||||
end
|
||||
|
||||
function update_trackbar_label(x, id, name, postfix)
|
||||
local str
|
||||
local func = tostring_overrides[id]
|
||||
if func then
|
||||
str = func(x)
|
||||
else
|
||||
str = app.str_setting(id)
|
||||
tostring_overrides["display.gui-scale"] = function(x)
|
||||
if x == 0 then
|
||||
-- the scale chosen automatically for the current window
|
||||
return string.format("%s (%s)", gui.str("Auto"), tostring(gui.get_scale()))
|
||||
end
|
||||
-- updating label
|
||||
document[id..".L"].text = string.format(
|
||||
"%s: %s%s",
|
||||
gui.str(name, "settings"),
|
||||
str,
|
||||
postfix
|
||||
)
|
||||
end
|
||||
|
||||
function create_checkbox(id, name, tooltip)
|
||||
tooltip = tooltip or ''
|
||||
document.root:add(string.format(
|
||||
"<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
|
||||
id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
|
||||
))
|
||||
return tostring(x)
|
||||
end
|
||||
|
||||
local settings = require "core:settings_common".new(document, {app=app, tostring_overrides=tostring_overrides})
|
||||
-- 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
|
||||
create_checkbox = settings.create_checkbox
|
||||
|
||||
function on_open()
|
||||
create_trackbar_setting("camera.fov", "FOV", 1, "°")
|
||||
create_trackbar_setting("display.framerate", "Framerate", 1, "", "", true)
|
||||
create_trackbar_setting("display.gui-scale", "GUI Scale", 0.5, "", "display.gui-scale.tooltip", true)
|
||||
|
||||
document.root:add(string.format(
|
||||
"<select context='settings' onselect='function(opt) app.set_setting(\"display.window-mode\", tonumber(opt)) end' selected='%s'>"..
|
||||
|
|
|
|||
|
|
@ -1,39 +1,8 @@
|
|||
function create_trackbar_setting(id, name, step, postfix, tooltip, changeonrelease)
|
||||
local info = app.get_setting_info(id)
|
||||
postfix = postfix or ""
|
||||
tooltip = tooltip or ""
|
||||
changeonrelease = changeonrelease or ""
|
||||
document.root:add(gui.template("track_setting", {
|
||||
id=id,
|
||||
name=gui.str(name, "settings"),
|
||||
value=app.get_setting(id),
|
||||
min=info.min,
|
||||
max=info.max,
|
||||
step=step,
|
||||
postfix=postfix,
|
||||
tooltip=tooltip,
|
||||
changeonrelease=changeonrelease
|
||||
}))
|
||||
update_trackbar_label(app.get_setting(id), id, name, postfix)
|
||||
end
|
||||
|
||||
function update_trackbar_label(x, id, name, postfix)
|
||||
-- updating label
|
||||
document[id..".L"].text = string.format(
|
||||
"%s: %s%s",
|
||||
gui.str(name, "settings"),
|
||||
app.str_setting(id),
|
||||
postfix
|
||||
)
|
||||
end
|
||||
|
||||
function create_checkbox(id, name, tooltip)
|
||||
tooltip = tooltip or ''
|
||||
document.root:add(string.format(
|
||||
"<checkbox consumer='function(x) app.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
|
||||
id, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
|
||||
))
|
||||
end
|
||||
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
|
||||
create_checkbox = settings.create_checkbox
|
||||
|
||||
function on_open()
|
||||
create_trackbar_setting("chunks.load-distance", "Load Distance", 1)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ function reset_audio()
|
|||
reset_setting("audio.volume-ui")
|
||||
reset_setting("audio.volume-ambient")
|
||||
reset_setting("audio.volume-music")
|
||||
reset_setting("audio.volume-contrast")
|
||||
end
|
||||
|
||||
function reset_display()
|
||||
|
|
|
|||
87
res/modules/settings_common.lua
Normal file
87
res/modules/settings_common.lua
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
-- Common helpers for settings pages (res/layouts/pages/settings_*.xml.lua).
|
||||
--
|
||||
-- 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
|
||||
-- create_trackbar_setting = settings.create_trackbar_setting
|
||||
-- update_trackbar_label = settings.update_trackbar_label
|
||||
-- create_checkbox = settings.create_checkbox
|
||||
|
||||
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 = {}
|
||||
|
||||
--- Update trackbar label (called by the track_setting template)
|
||||
function this.update_trackbar_label(x, id, name, postfix)
|
||||
local str
|
||||
local func = tostring_overrides[id]
|
||||
if func then
|
||||
str = func(x)
|
||||
else
|
||||
str = app.str_setting(id)
|
||||
end
|
||||
document[id..".L"].text = string.format(
|
||||
"%s: %s%s",
|
||||
gui.str(name, "settings"),
|
||||
str,
|
||||
postfix
|
||||
)
|
||||
end
|
||||
|
||||
--- Add a trackbar bound to the setting
|
||||
--- @param changeonrelease apply value only when mouse is released
|
||||
function this.create_trackbar_setting(
|
||||
id, name, step, postfix, tooltip, changeonrelease
|
||||
)
|
||||
local info = app.get_setting_info(id)
|
||||
postfix = postfix or ""
|
||||
tooltip = tooltip or ""
|
||||
changeonrelease = changeonrelease or ""
|
||||
document.root:add(gui.template("track_setting", {
|
||||
id=id,
|
||||
name=gui.str(name, "settings"),
|
||||
value=app.get_setting(id),
|
||||
min=info.min,
|
||||
max=info.max,
|
||||
step=step,
|
||||
postfix=postfix,
|
||||
tooltip=tooltip,
|
||||
changeonrelease=changeonrelease
|
||||
}))
|
||||
this.update_trackbar_label(app.get_setting(id), id, name, postfix)
|
||||
end
|
||||
|
||||
--- Add a checkbox bound to the setting
|
||||
--- @param consumer name of a global page function(id, value) called on
|
||||
--- change instead of just setting the value (optional)
|
||||
function this.create_checkbox(id, name, tooltip, consumer)
|
||||
tooltip = tooltip or ""
|
||||
local action
|
||||
if consumer then
|
||||
action = string.format("%s(\"%s\", x)", consumer, id)
|
||||
else
|
||||
action = string.format("app.set_setting(\"%s\", x)", id)
|
||||
end
|
||||
document.root:add(string.format(
|
||||
"<checkbox consumer='function(x) %s end' checked='%s' tooltip='%s'>%s</checkbox>",
|
||||
action, app.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
|
||||
))
|
||||
end
|
||||
|
||||
return this
|
||||
end
|
||||
|
||||
return settings_common
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ Dependencies=Зависимости
|
|||
Description=Описание
|
||||
Converting world...=Выполняется конвертация мира...
|
||||
Unlimited=Неограниченно
|
||||
Auto=Авто
|
||||
Chat=Чат
|
||||
Console=Консоль
|
||||
Log=Лог
|
||||
|
|
@ -39,7 +40,9 @@ pack.remove-confirm=Удалить весь поставляемый паком/
|
|||
|
||||
# Подсказки
|
||||
audio.acoustic-effects.tooltip=Акустические эффекты могут не поддерживаться системой
|
||||
audio.volume-contrast.tooltip=Меньше - громкие звуки приглушаются до уровня тихих (ночной режим)
|
||||
audio.recording-enabled.tooltip=Включает функции записи звука
|
||||
display.gui-scale.tooltip=Размер интерфейса: Авто или 0.5-4. В режиме Авто меню уменьшается, если не помещается в окно
|
||||
graphics.enable-fog.tooltip=Отображать туман / дымку
|
||||
graphics.gamma.tooltip=Кривая яркости освещения
|
||||
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
|
||||
|
|
@ -95,6 +98,7 @@ settings.Fog Curve=Кривая Тумана
|
|||
settings.FOV=Поле Зрения
|
||||
settings.Fullscreen=Полный экран
|
||||
settings.Framerate=Частота кадров
|
||||
settings.GUI Scale=Масштаб интерфейса
|
||||
settings.Gamma=Гамма
|
||||
settings.Language=Язык
|
||||
settings.Load Distance=Дистанция Загрузки
|
||||
|
|
@ -102,6 +106,7 @@ settings.Load Speed=Скорость Загрузки
|
|||
settings.Master Volume=Общая Громкость
|
||||
settings.Mouse Sensitivity=Чувствительность Мыши
|
||||
settings.Music=Музыка
|
||||
settings.Volume Contrast=Контраст громкости
|
||||
settings.Regular Sounds=Обычные Звуки
|
||||
settings.UI Sounds=Звуки Интерфейса
|
||||
settings.V-Sync=Вертикальная Синхронизация
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "audio.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
|
@ -25,6 +26,64 @@ namespace {
|
|||
util::ObjectsKeeper objects_keeper {};
|
||||
std::unique_ptr<InputDevice> input_device = nullptr;
|
||||
static bool input_enabled = false;
|
||||
|
||||
/// @brief Volume contrast setting value (1.0 - no compression)
|
||||
float volume_contrast = 1.0f;
|
||||
/// @brief RMS level (0..1) that louder sounds are lowered to
|
||||
/// when contrast is 0
|
||||
constexpr float REFERENCE_RMS = 0.1f;
|
||||
|
||||
struct LoudnessEntry {
|
||||
std::weak_ptr<PCM> pcm;
|
||||
float rms;
|
||||
};
|
||||
std::unordered_map<const Sound*, LoudnessEntry> loudness_cache;
|
||||
}
|
||||
|
||||
/// @brief Calculate root mean square level of PCM data
|
||||
/// @return level in range [0.0, 1.0]
|
||||
static float calc_rms(const PCM& pcm) {
|
||||
double sum = 0.0;
|
||||
size_t count = 0;
|
||||
if (pcm.bitsPerSample == 16) {
|
||||
count = pcm.data.size() / sizeof(int16_t);
|
||||
auto samples = reinterpret_cast<const int16_t*>(pcm.data.data());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
double value = samples[i] / 32768.0;
|
||||
sum += value * value;
|
||||
}
|
||||
} else {
|
||||
count = pcm.data.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
double value = (static_cast<uint8_t>(pcm.data[i]) - 128) / 128.0;
|
||||
sum += value * value;
|
||||
}
|
||||
}
|
||||
return count ? static_cast<float>(std::sqrt(sum / count)) : 0.0f;
|
||||
}
|
||||
|
||||
/// @brief Get volume multiplier for a sound according to volume contrast
|
||||
/// setting. Loud sounds are lowered, quiet ones are never amplified
|
||||
static float get_contrast_gain(const Sound* sound) {
|
||||
if (volume_contrast >= 1.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
auto pcm = sound->getPCM();
|
||||
if (pcm == nullptr) {
|
||||
return 1.0f;
|
||||
}
|
||||
float rms;
|
||||
auto found = loudness_cache.find(sound);
|
||||
if (found != loudness_cache.end() && found->second.pcm.lock() == pcm) {
|
||||
rms = found->second.rms;
|
||||
} else {
|
||||
rms = calc_rms(*pcm);
|
||||
loudness_cache[sound] = {pcm, rms};
|
||||
}
|
||||
if (rms <= REFERENCE_RMS) {
|
||||
return 1.0f;
|
||||
}
|
||||
return std::pow(REFERENCE_RMS / rms, 1.0f - volume_contrast);
|
||||
}
|
||||
|
||||
Channel::Channel(std::string name, bool effects)
|
||||
|
|
@ -196,6 +255,9 @@ void audio::initialize(
|
|||
audio::get_channel(channel.name)->setVolume(value * value);
|
||||
}, true));
|
||||
}
|
||||
objects_keeper.keepAlive(settings.volumeContrast.observe([](auto value) {
|
||||
volume_contrast = value;
|
||||
}, true));
|
||||
objects_keeper.keepAlive(settings.acousticEffects.observe([=](bool value) {
|
||||
if (value) return;
|
||||
backend->setAcoustics(audio::Acoustics {});
|
||||
|
|
@ -395,7 +457,7 @@ speakerid_t audio::play(
|
|||
speakerid_t id = nextId++;
|
||||
speakers.try_emplace(id, std::move(speaker_ptr));
|
||||
speaker->setPosition(position);
|
||||
speaker->setVolume(volume);
|
||||
speaker->setVolume(volume * get_contrast_gain(sound));
|
||||
speaker->setPitch(pitch);
|
||||
speaker->setLoop(loop);
|
||||
speaker->setRelative(relative);
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ void Hud::update(bool visible) {
|
|||
element.getNode()->setVisible(visible);
|
||||
}
|
||||
|
||||
const auto& windowSize = engine.getWindow().getSize();
|
||||
const auto windowSize = gui.getContainer()->getSize();
|
||||
glm::vec2 caSize = contentAccessPanel->getSize();
|
||||
contentAccessPanel->setVisible(inventoryView != nullptr && showContentPanel);
|
||||
contentAccessPanel->setSize(glm::vec2(caSize.x, windowSize.y));
|
||||
|
|
@ -626,7 +626,8 @@ void Hud::setDebug(bool flag) {
|
|||
void Hud::draw(const DrawContext& ctx){
|
||||
const auto& viewport = ctx.getViewport();
|
||||
|
||||
updateElementsPosition(viewport);
|
||||
// UI layout is calculated in GUI units (window size / GUI scale)
|
||||
updateElementsPosition(gui.getContainer()->getSize());
|
||||
|
||||
uicamera->setFov(viewport.y);
|
||||
uicamera->setAspectRatio(viewport.x / static_cast<float>(viewport.y));
|
||||
|
|
@ -653,7 +654,7 @@ void Hud::draw(const DrawContext& ctx){
|
|||
}
|
||||
}
|
||||
|
||||
void Hud::updateElementsPosition(const glm::uvec2& viewport) {
|
||||
void Hud::updateElementsPosition(const glm::vec2& viewport) {
|
||||
if (inventoryOpen) {
|
||||
float caWidth = inventoryView && showContentPanel
|
||||
? contentAccess->getSize().x
|
||||
|
|
@ -698,7 +699,7 @@ void Hud::updateElementsPosition(const glm::uvec2& viewport) {
|
|||
}
|
||||
}
|
||||
if (exchangeSlot != nullptr) {
|
||||
exchangeSlot->setPos(input.getCursor().pos);
|
||||
exchangeSlot->setPos(gui.getCursor().pos);
|
||||
}
|
||||
hotbarView->setPos(glm::vec2(viewport.x / 2, viewport.y - 65));
|
||||
hotbarView->setSelected(player.getChosenSlot());
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class Hud : public util::ObjectsKeeper {
|
|||
std::shared_ptr<gui::InventoryView> createHotbar();
|
||||
|
||||
void processInput(bool visible);
|
||||
void updateElementsPosition(const glm::uvec2& viewport);
|
||||
void updateElementsPosition(const glm::vec2& viewport);
|
||||
void updateHotbarControl();
|
||||
void cleanup();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -157,8 +157,12 @@ void DrawContext::setBlendMode(BlendMode mode) {
|
|||
set_blend_mode(mode);
|
||||
}
|
||||
|
||||
void DrawContext::setUiScale(float scale) {
|
||||
uiScale = scale;
|
||||
}
|
||||
|
||||
void DrawContext::setScissors(const glm::vec4& area) {
|
||||
window.pushScissor(area);
|
||||
window.pushScissor(area * uiScale);
|
||||
scissorsCount++;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class DrawContext {
|
|||
BlendMode blendMode = BlendMode::normal;
|
||||
int scissorsCount = 0;
|
||||
float lineWidth = 1.0f;
|
||||
float uiScale = 1.0f;
|
||||
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
|
||||
public:
|
||||
DrawContext(
|
||||
|
|
@ -46,5 +47,9 @@ public:
|
|||
void setBlendMode(BlendMode mode);
|
||||
void setScissors(const glm::vec4& area);
|
||||
void setLineWidth(float width);
|
||||
|
||||
/// @brief Set scale of UI coordinates (UI units -> window pixels).
|
||||
/// Affects setScissors only
|
||||
void setUiScale(float scale);
|
||||
void useTexture(int target, const Bindable* texture);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "GUI.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "assets/Assets.hpp"
|
||||
|
|
@ -88,13 +89,65 @@ void GUI::onAssetsLoad(Assets* assets) {
|
|||
assets->store(rootDocument, "core:root");
|
||||
}
|
||||
|
||||
/// @brief Minimal UI size (in UI units) that GUI scale must keep, so
|
||||
/// menus always fit the window. Larger scale values are limited to it
|
||||
static constexpr glm::uvec2 MIN_UI_SIZE {640, 480};
|
||||
static constexpr int MAX_SCALE = 4;
|
||||
|
||||
int GUI::calcMaxScale(const glm::uvec2& viewport) {
|
||||
int maxFit = 1;
|
||||
while (maxFit < MAX_SCALE &&
|
||||
viewport.x / (maxFit + 1) >= MIN_UI_SIZE.x &&
|
||||
viewport.y / (maxFit + 1) >= MIN_UI_SIZE.y) {
|
||||
maxFit++;
|
||||
}
|
||||
return maxFit;
|
||||
}
|
||||
|
||||
float GUI::calcScale(const glm::uvec2& viewport) const {
|
||||
float value = engine.getSettings().display.guiScale.get();
|
||||
float result = value <= 0.0f
|
||||
? static_cast<float>(calcMaxScale(viewport)) // auto
|
||||
: value;
|
||||
|
||||
// explicitly set scale is applied as is; in auto mode an open menu page
|
||||
// must fit the window
|
||||
auto& page = menu->getCurrent();
|
||||
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);
|
||||
fit = std::floor(fit * 2.0f) / 2.0f; // step 0.5
|
||||
result = std::min(result, std::max(fit, 0.5f));
|
||||
}
|
||||
}
|
||||
// keep UI size at least 1 unit
|
||||
float limit = static_cast<float>(std::min(viewport.x, viewport.y));
|
||||
return std::max(0.5f, std::min(result, std::max(limit, 0.5f)));
|
||||
}
|
||||
|
||||
float GUI::getScale() const {
|
||||
return scale;
|
||||
}
|
||||
|
||||
int GUI::getMaxScale() const {
|
||||
return calcMaxScale(engine.getWindow().getSize());
|
||||
}
|
||||
|
||||
CursorState GUI::getCursor() const {
|
||||
auto cursor = input.getCursor();
|
||||
cursor.pos /= scale;
|
||||
cursor.delta /= scale;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void GUI::resetTooltip() {
|
||||
tooltipTimer = 0.0f;
|
||||
tooltip->setVisible(false);
|
||||
}
|
||||
|
||||
void GUI::updateTooltip(float delta) {
|
||||
const auto& cursor = input.getCursor();
|
||||
const auto cursor = getCursor();
|
||||
if (hover == nullptr || !hover->isInside(cursor.pos)) {
|
||||
return resetTooltip();
|
||||
}
|
||||
|
|
@ -229,7 +282,7 @@ void GUI::actFocused() {
|
|||
focus->keyPressed(key);
|
||||
}
|
||||
|
||||
const auto& cursor = input.getCursor();
|
||||
const auto cursor = getCursor();
|
||||
if (!cursor.locked) {
|
||||
if (input.clicked(Mousecode::BUTTON_1) &&
|
||||
(input.jclicked(Mousecode::BUTTON_1) || cursor.delta.x ||
|
||||
|
|
@ -242,7 +295,8 @@ void GUI::actFocused() {
|
|||
}
|
||||
|
||||
void GUI::act(float delta, const glm::uvec2& vp) {
|
||||
container->setSize(vp);
|
||||
scale = calcScale(vp);
|
||||
container->setSize(glm::vec2(vp) / scale);
|
||||
for (auto& pair : frames) {
|
||||
pair.second->act(delta);
|
||||
}
|
||||
|
|
@ -250,7 +304,7 @@ void GUI::act(float delta, const glm::uvec2& vp) {
|
|||
|
||||
updateTooltip(delta);
|
||||
|
||||
const auto& cursor = input.getCursor();
|
||||
const auto cursor = getCursor();
|
||||
if (!cursor.locked && activeFrame) {
|
||||
actMouse(*activeFrame, delta, cursor);
|
||||
} else {
|
||||
|
|
@ -278,8 +332,11 @@ void GUI::postAct() {
|
|||
|
||||
void GUI::draw(const DrawContext& pctx, Assets& assets) {
|
||||
auto ctx = pctx.sub(batch2D.get());
|
||||
ctx.setUiScale(scale);
|
||||
|
||||
auto& viewport = ctx.getViewport();
|
||||
// viewport in UI units
|
||||
const glm::vec2 viewport =
|
||||
glm::vec2(ctx.getViewport()) / scale;
|
||||
|
||||
auto& page = menu->getCurrent();
|
||||
if (page.panel) {
|
||||
|
|
@ -289,15 +346,18 @@ void GUI::draw(const DrawContext& pctx, Assets& assets) {
|
|||
panel->cropToContent();
|
||||
}
|
||||
}
|
||||
menu->setPos((glm::vec2(viewport) - menu->getSize()) / 2.0f);
|
||||
menu->setPos((viewport - menu->getSize()) / 2.0f);
|
||||
uicamera->setFov(viewport.y);
|
||||
uicamera->setAspectRatio(viewport.x / static_cast<float>(viewport.y));
|
||||
uicamera->setAspectRatio(viewport.x / viewport.y);
|
||||
|
||||
auto uishader = assets.get<Shader>("ui");
|
||||
uishader->use();
|
||||
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);
|
||||
|
|
@ -339,6 +399,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<UINode> GUI::getFocused() const {
|
||||
|
|
|
|||
|
|
@ -86,12 +86,18 @@ namespace gui {
|
|||
std::unordered_map<std::string, std::shared_ptr<Frame>> frames;
|
||||
vec2supplier cursorLocator;
|
||||
|
||||
/// @brief Current GUI scale (UI units -> window pixels)
|
||||
float scale = 1.0f;
|
||||
|
||||
float tooltipTimer = 0.0f;
|
||||
float doubleClickTimer = 0.0f;
|
||||
float doubleClickDelay = 0.5f;
|
||||
bool doubleClicked = false;
|
||||
bool debug = false;
|
||||
|
||||
static int calcMaxScale(const glm::uvec2& viewport);
|
||||
float calcScale(const glm::uvec2& viewport) const;
|
||||
|
||||
void actMouse(Frame& frame, float delta, const CursorState& cursor);
|
||||
void actFocused();
|
||||
void updateTooltip(float delta);
|
||||
|
|
@ -125,6 +131,15 @@ namespace gui {
|
|||
|
||||
void postAct();
|
||||
|
||||
/// @brief Get current GUI scale: window pixels per one UI unit
|
||||
float getScale() const;
|
||||
|
||||
/// @brief Get GUI scale used in auto mode (fits the current window size)
|
||||
int getMaxScale() const;
|
||||
|
||||
/// @brief Get cursor state in UI coordinates
|
||||
CursorState getCursor() const;
|
||||
|
||||
/// @brief Add element to the main container
|
||||
/// @param node UI element
|
||||
void add(std::shared_ptr<UINode> node);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ void InventoryBuilder::addGrid(
|
|||
if (addpanel) {
|
||||
auto panel =
|
||||
std::make_shared<gui::Container>(gui, glm::vec2(width, height));
|
||||
view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f));
|
||||
view->setColor(InventoryView::DEFAULT_COLOR);
|
||||
view->add(panel, pos);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,8 @@ namespace gui {
|
|||
|
||||
static const int SLOT_INTERVAL = 4;
|
||||
static const int SLOT_SIZE = ITEM_ICON_SIZE;
|
||||
/// @brief Default background color (can be overridden by 'color' attribute)
|
||||
static inline const glm::vec4 DEFAULT_COLOR {0.122f, 0.122f, 0.122f, 0.878f};
|
||||
};
|
||||
|
||||
class InventoryBuilder {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ void ModelViewer::act(float delta) {
|
|||
grabbing = true;
|
||||
}
|
||||
if (grabbing && input.clicked(Mousecode::BUTTON_3)) {
|
||||
auto cursor = input.getCursor();
|
||||
auto cursor = gui.getCursor();
|
||||
if (input.pressed(Keycode::LEFT_SHIFT)) {
|
||||
center -= camera.right * (cursor.delta.x / size.x) * distance;
|
||||
center += camera.up * (cursor.delta.y / size.y) * distance;
|
||||
|
|
|
|||
|
|
@ -860,7 +860,7 @@ static std::shared_ptr<UINode> read_inventory(
|
|||
UiXmlReader& reader, const xml::xmlelement& element
|
||||
) {
|
||||
auto view = std::make_shared<InventoryView>(reader.getGUI());
|
||||
view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f)); // TODO: fixme
|
||||
view->setColor(InventoryView::DEFAULT_COLOR);
|
||||
reader.addIgnore("slot");
|
||||
reader.addIgnore("slots-grid");
|
||||
reader.readUINode(reader, element, *view);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
|||
builder.add("volume-ui", &settings.audio.volumeUI);
|
||||
builder.add("volume-ambient", &settings.audio.volumeAmbient);
|
||||
builder.add("volume-music", &settings.audio.volumeMusic);
|
||||
builder.add("volume-contrast", &settings.audio.volumeContrast);
|
||||
builder.add("input-device", &settings.audio.inputDevice);
|
||||
builder.add("acoustic-effects", &settings.audio.acousticEffects);
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
|
|||
builder.add("limit-fps-iconified", &settings.display.limitFpsIconified);
|
||||
builder.add("window-mode", &settings.display.windowMode);
|
||||
builder.add("adaptive-menu-fps", &settings.display.adaptiveFpsInMenu);
|
||||
builder.add("gui-scale", &settings.display.guiScale);
|
||||
|
||||
builder.addSection("camera");
|
||||
builder.add("sensitivity", &settings.camera.sensitivity);
|
||||
|
|
|
|||
|
|
@ -1067,6 +1067,20 @@ static int l_gui_getviewport(lua::State* L) {
|
|||
return lua::pushvec2(L, engine->getGUI().getContainer()->getSize());
|
||||
}
|
||||
|
||||
static int l_gui_get_scale(lua::State* L) {
|
||||
return lua::pushnumber(L, engine->getGUI().getScale());
|
||||
}
|
||||
|
||||
static int l_gui_get_max_scale(lua::State* L) {
|
||||
return lua::pushinteger(L, engine->getGUI().getMaxScale());
|
||||
}
|
||||
|
||||
static int l_gui_get_cursor_pos(lua::State* L) {
|
||||
if (engine->isHeadless())
|
||||
return 0;
|
||||
return lua::pushvec2(L, engine->getGUI().getCursor().pos);
|
||||
}
|
||||
|
||||
static int l_gui_clear_markup(lua::State* L) {
|
||||
auto lang = lua::require_string(L, 1);
|
||||
std::string text = lua::require_string(L, 2);
|
||||
|
|
@ -1247,6 +1261,9 @@ static int l_screenshot(lua::State* L) {
|
|||
const luaL_Reg guilib[] = {
|
||||
{"screenshot", lua::wrap<l_screenshot>},
|
||||
{"get_viewport", lua::wrap<l_gui_getviewport>},
|
||||
{"get_scale", lua::wrap<l_gui_get_scale>},
|
||||
{"get_max_scale", lua::wrap<l_gui_get_max_scale>},
|
||||
{"get_cursor_pos", lua::wrap<l_gui_get_cursor_pos>},
|
||||
{"getattr", lua::wrap<l_gui_getattr>},
|
||||
{"setattr", lua::wrap<l_gui_setattr>},
|
||||
{"get_env", lua::wrap<l_gui_get_env>},
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ struct AudioSettings {
|
|||
NumberSetting volumeUI {1.0f, 0.0f, 1.0f, setting_format::percent};
|
||||
NumberSetting volumeAmbient {1.0f, 0.0f, 1.0f, setting_format::percent};
|
||||
NumberSetting volumeMusic {1.0f, 0.0f, 1.0f, setting_format::percent};
|
||||
/// @brief Volume contrast: 100% - sounds are played as is,
|
||||
/// 0% - loud sounds are lowered to the level of quiet ones ("night mode")
|
||||
NumberSetting volumeContrast {1.0f, 0.0f, 1.0f, setting_format::percent};
|
||||
|
||||
StringSetting inputDevice {""};
|
||||
|
||||
|
|
@ -38,6 +41,8 @@ struct DisplaySettings {
|
|||
FlagSetting limitFpsIconified {false};
|
||||
/// @brief Adaptive framerate in menu (experimental)
|
||||
FlagSetting adaptiveFpsInMenu {false};
|
||||
/// @brief GUI scale factor (0 - auto, 0.5..4 - explicit scale)
|
||||
NumberSetting guiScale {0.0f, 0.0f, 4.0f};
|
||||
};
|
||||
|
||||
struct ChunksSettings {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue