Compare commits

..

9 Commits

Author SHA1 Message Date
Lars Müller
790efb06b5
Merge 743199f6a4b88f3355e66eb84ac57152dde5a374 into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1 2024-06-28 00:55:34 +02:00
grorp
9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00
Erich Schubert
514e106414
Fix missing newline before Markdown list (#14783)
Renders incorrectly e.g. on https://api.minetest.net/spatial-vectors/
2024-06-26 22:21:18 +02:00
grorp
4c001bd248
Make button sprites (scrollbar arrows) DPI-aware (#14772) 2024-06-26 15:25:27 +02:00
1F616EMO~nya
fb6ceb2664
Properly escape Markdown markups at minetest.string_to_area (#14774)
Co-authored-by: DS <ds.desour@proton.me>
2024-06-24 22:14:16 +02:00
aminothere
50da26da91
Document alternative 'persist' key in noise parameter table (#14762) 2024-06-24 22:12:42 +02:00
kromka-chleba
28857841aa
Fix math.round floating point bug (#14757) 2024-06-24 20:57:06 +01:00
rubenwardy
157d129e30
Fix unnecessary content refreshing (#14705) 2024-06-24 20:56:37 +01:00
Yoruma
9ab447843b
Clarify "dtime" in API (#14758) 2024-06-22 10:59:58 +02:00
24 changed files with 347 additions and 261 deletions

@ -240,12 +240,15 @@ function math.factorial(x)
return v return v
end end
function math.round(x) function math.round(x)
if x >= 0 then if x < 0 then
return math.floor(x + 0.5) local int = math.ceil(x)
local frac = x - int
return int - ((frac <= -0.5) and 1 or 0)
end end
return math.ceil(x - 0.5) local int = math.floor(x)
local frac = x - int
return int + ((frac >= 0.5) and 1 or 0)
end end
local formspec_escapes = { local formspec_escapes = {

@ -176,3 +176,17 @@ describe("formspec_escape", function()
assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\[")) assert.equal("\\[Hello\\\\\\[", core.formspec_escape("[Hello\\["))
end) end)
end) end)
describe("math", function()
it("round()", function()
assert.equal(0, math.round(0))
assert.equal(10, math.round(10.3))
assert.equal(11, math.round(10.5))
assert.equal(11, math.round(10.7))
assert.equal(-10, math.round(-10.3))
assert.equal(-11, math.round(-10.5))
assert.equal(-11, math.round(-10.7))
assert.equal(0, math.round(0.49999999999999994))
assert.equal(0, math.round(-0.49999999999999994))
end)
end)

@ -133,6 +133,8 @@ local function start_install(package, reason)
conf:set("release", package.release) conf:set("release", package.release)
conf:write() conf:write()
end end
pkgmgr.reload_by_type(package.type)
end end
end end
@ -146,7 +148,6 @@ local function start_install(package, reason)
start_install(next.package, next.reason) start_install(next.package, next.reason)
end end
ui.update() ui.update()
end end
@ -427,8 +428,9 @@ end
function contentdb.update_paths() function contentdb.update_paths()
pkgmgr.load_all()
local mod_hash = {} local mod_hash = {}
pkgmgr.refresh_globals()
for _, mod in pairs(pkgmgr.global_mods:get_list()) do for _, mod in pairs(pkgmgr.global_mods:get_list()) do
local cdb_id = pkgmgr.get_contentdb_id(mod) local cdb_id = pkgmgr.get_contentdb_id(mod)
if cdb_id then if cdb_id then
@ -437,7 +439,6 @@ function contentdb.update_paths()
end end
local game_hash = {} local game_hash = {}
pkgmgr.update_gamelist()
for _, game in pairs(pkgmgr.games) do for _, game in pairs(pkgmgr.games) do
local cdb_id = pkgmgr.get_contentdb_id(game) local cdb_id = pkgmgr.get_contentdb_id(game)
if cdb_id then if cdb_id then
@ -446,7 +447,7 @@ function contentdb.update_paths()
end end
local txp_hash = {} local txp_hash = {}
for _, txp in pairs(pkgmgr.get_texture_packs()) do for _, txp in pairs(pkgmgr.texture_packs) do
local cdb_id = pkgmgr.get_contentdb_id(txp) local cdb_id = pkgmgr.get_contentdb_id(txp)
if cdb_id then if cdb_id then
txp_hash[contentdb.aliases[cdb_id] or cdb_id] = txp txp_hash[contentdb.aliases[cdb_id] or cdb_id] = txp

@ -110,7 +110,7 @@ pkgmgr = {}
-- @param modpack Currently processing modpack or nil/"" if none (recursion) -- @param modpack Currently processing modpack or nil/"" if none (recursion)
function pkgmgr.get_mods(path, virtual_path, listing, modpack) function pkgmgr.get_mods(path, virtual_path, listing, modpack)
local mods = core.get_dir_list(path, true) local mods = core.get_dir_list(path, true)
local added = {}
for _, name in ipairs(mods) do for _, name in ipairs(mods) do
if name:sub(1, 1) ~= "." then if name:sub(1, 1) ~= "." then
local mod_path = path .. DIR_DELIM .. name local mod_path = path .. DIR_DELIM .. name
@ -120,6 +120,7 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
parent_dir = path, parent_dir = path,
} }
listing[#listing + 1] = toadd listing[#listing + 1] = toadd
added[#added + 1] = toadd
-- Get config file -- Get config file
local mod_conf local mod_conf
@ -150,8 +151,6 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
toadd.virtual_path = mod_virtual_path toadd.virtual_path = mod_virtual_path
toadd.type = "mod" toadd.type = "mod"
pkgmgr.update_translations({ toadd })
-- Check modpack.txt -- Check modpack.txt
-- Note: modpack.conf is already checked above -- Note: modpack.conf is already checked above
local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt") local modpackfile = io.open(mod_path .. DIR_DELIM .. "modpack.txt")
@ -171,6 +170,8 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
end end
end end
pkgmgr.update_translations(added)
if not modpack then if not modpack then
-- Sort all when the recursion is done -- Sort all when the recursion is done
table.sort(listing, function(a, b) table.sort(listing, function(a, b)
@ -180,12 +181,13 @@ function pkgmgr.get_mods(path, virtual_path, listing, modpack)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.get_texture_packs() function pkgmgr.reload_texture_packs()
local txtpath = core.get_texturepath() local txtpath = core.get_texturepath()
local txtpath_system = core.get_texturepath_share() local txtpath_system = core.get_texturepath_share()
local retval = {} local retval = {}
load_texture_packs(txtpath, retval) load_texture_packs(txtpath, retval)
-- on portable versions these two paths coincide. It avoids loading the path twice -- on portable versions these two paths coincide. It avoids loading the path twice
if txtpath ~= txtpath_system then if txtpath ~= txtpath_system then
load_texture_packs(txtpath_system, retval) load_texture_packs(txtpath_system, retval)
@ -197,11 +199,13 @@ function pkgmgr.get_texture_packs()
return a.title:lower() < b.title:lower() return a.title:lower() < b.title:lower()
end) end)
return retval pkgmgr.texture_packs = retval
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.get_all() function pkgmgr.get_all()
pkgmgr.load_all()
local result = {} local result = {}
for _, mod in pairs(pkgmgr.global_mods:get_list()) do for _, mod in pairs(pkgmgr.global_mods:get_list()) do
@ -210,7 +214,7 @@ function pkgmgr.get_all()
for _, game in pairs(pkgmgr.games) do for _, game in pairs(pkgmgr.games) do
result[#result + 1] = game result[#result + 1] = game
end end
for _, txp in pairs(pkgmgr.get_texture_packs()) do for _, txp in pairs(pkgmgr.texture_packs) do
result[#result + 1] = txp result[#result + 1] = txp
end end
@ -288,7 +292,7 @@ end
function pkgmgr.render_packagelist(render_list, use_technical_names, with_icon) function pkgmgr.render_packagelist(render_list, use_technical_names, with_icon)
if not render_list then if not render_list then
if not pkgmgr.global_mods then if not pkgmgr.global_mods then
pkgmgr.refresh_globals() pkgmgr.reload_global_mods()
end end
render_list = pkgmgr.global_mods render_list = pkgmgr.global_mods
end end
@ -549,6 +553,7 @@ function pkgmgr.get_worldconfig(worldpath)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Caller is responsible for reloading content types (see reload_by_type)
function pkgmgr.install_dir(expected_type, path, basename, targetpath) function pkgmgr.install_dir(expected_type, path, basename, targetpath)
assert(type(expected_type) == "string") assert(type(expected_type) == "string")
assert(type(path) == "string") assert(type(path) == "string")
@ -615,12 +620,6 @@ function pkgmgr.install_dir(expected_type, path, basename, targetpath)
fgettext_ne("Failed to install $1 to $2", basename, targetpath) fgettext_ne("Failed to install $1 to $2", basename, targetpath)
end end
if basefolder.type == "game" then
pkgmgr.update_gamelist()
else
pkgmgr.refresh_globals()
end
return targetpath, nil return targetpath, nil
end end
@ -742,7 +741,7 @@ function pkgmgr.comparemod(elem1,elem2)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.refresh_globals() function pkgmgr.reload_global_mods()
local function is_equal(element,uid) --uid match local function is_equal(element,uid) --uid match
if element.name == uid then if element.name == uid then
return true return true
@ -774,7 +773,7 @@ function pkgmgr.get_game_mods(gamespec, retval)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.update_gamelist() function pkgmgr.reload_games()
pkgmgr.games = core.get_games() pkgmgr.games = core.get_games()
table.sort(pkgmgr.games, function(a, b) table.sort(pkgmgr.games, function(a, b)
return a.title:lower() < b.title:lower() return a.title:lower() < b.title:lower()
@ -782,6 +781,32 @@ function pkgmgr.update_gamelist()
pkgmgr.update_translations(pkgmgr.games) pkgmgr.update_translations(pkgmgr.games)
end end
--------------------------------------------------------------------------------
function pkgmgr.reload_by_type(type)
if type == "game" then
pkgmgr.reload_games()
elseif type == "txp" then
pkgmgr.reload_texture_packs()
elseif type == "mod" or type == "modpack" then
pkgmgr.reload_global_mods()
else
error("Unknown package type: " .. type)
end
end
--------------------------------------------------------------------------------
function pkgmgr.load_all()
if not pkgmgr.global_mods then
pkgmgr.reload_global_mods()
end
if not pkgmgr.games then
pkgmgr.reload_games()
end
if not pkgmgr.texture_packs then
pkgmgr.reload_texture_packs()
end
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function pkgmgr.update_translations(list) function pkgmgr.update_translations(list)
for _, item in ipairs(list) do for _, item in ipairs(list) do
@ -831,4 +856,4 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- read initial data -- read initial data
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
pkgmgr.update_gamelist() pkgmgr.reload_games()

@ -46,6 +46,9 @@ local function reset()
function core.get_texturepath() function core.get_texturepath()
return txp_dir return txp_dir
end end
function core.get_texturepath_share()
return txp_dir
end
function core.get_modpath() function core.get_modpath()
return mods_dir return mods_dir
end end
@ -59,13 +62,6 @@ local function reset()
setfenv(loadfile("builtin/common/misc_helpers.lua"), env)() setfenv(loadfile("builtin/common/misc_helpers.lua"), env)()
setfenv(loadfile("builtin/mainmenu/content/pkgmgr.lua"), env)() setfenv(loadfile("builtin/mainmenu/content/pkgmgr.lua"), env)()
function env.pkgmgr.update_gamelist()
table.insert(calls, { "update_gamelist" })
end
function env.pkgmgr.refresh_globals()
table.insert(calls, { "refresh_globals" })
end
function env.assert_calls(list) function env.assert_calls(list)
assert.are.same(list, calls) assert.are.same(list, calls)
end end
@ -113,7 +109,6 @@ describe("install_dir", function()
env.assert_calls({ env.assert_calls({
{ "delete_dir", mods_dir .. "/mymod" }, { "delete_dir", mods_dir .. "/mymod" },
{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false }, { "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
{ "refresh_globals" },
}) })
end) end)
@ -129,7 +124,6 @@ describe("install_dir", function()
env.assert_calls({ env.assert_calls({
{ "delete_dir", mods_dir .. "/mymod" }, { "delete_dir", mods_dir .. "/mymod" },
{ "copy_dir", "/tmp/123", mods_dir .. "/mymod", false }, { "copy_dir", "/tmp/123", mods_dir .. "/mymod", false },
{ "refresh_globals" },
}) })
end) end)
@ -145,7 +139,6 @@ describe("install_dir", function()
env.assert_calls({ env.assert_calls({
{ "delete_dir", games_dir .. "/mygame" }, { "delete_dir", games_dir .. "/mygame" },
{ "copy_dir", "/tmp/123", games_dir .. "/mygame", false }, { "copy_dir", "/tmp/123", games_dir .. "/mygame", false },
{ "update_gamelist" },
}) })
end) end)
@ -161,7 +154,6 @@ describe("install_dir", function()
env.assert_calls({ env.assert_calls({
{ "delete_dir", mods_dir .. "/123" }, { "delete_dir", mods_dir .. "/123" },
{ "copy_dir", "/tmp/123", mods_dir .. "/123", false }, { "copy_dir", "/tmp/123", mods_dir .. "/123", false },
{ "refresh_globals" },
}) })
end) end)
@ -188,7 +180,6 @@ describe("install_dir", function()
env.assert_calls({ env.assert_calls({
{ "delete_dir", "/tmp/alt-target" }, { "delete_dir", "/tmp/alt-target" },
{ "copy_dir", "/tmp/123", "/tmp/alt-target", false }, { "copy_dir", "/tmp/123", "/tmp/alt-target", false },
{ "refresh_globals" },
}) })
end) end)
@ -238,6 +229,5 @@ describe("install_dir", function()
path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil) path, message = env.pkgmgr.install_dir("txp", "/tmp/123", "name", nil)
assert.is._not._nil(path) assert.is._not._nil(path)
assert.is._nil(message) assert.is._nil(message)
end) end)
end) end)

@ -72,9 +72,6 @@ end
local function has_packages_from_cdb() local function has_packages_from_cdb()
pkgmgr.refresh_globals()
pkgmgr.update_gamelist()
for _, content in pairs(pkgmgr.get_all()) do for _, content in pairs(pkgmgr.get_all()) do
if pkgmgr.get_contentdb_id(content) then if pkgmgr.get_contentdb_id(content) then
return true return true
@ -127,9 +124,6 @@ function update_detector.get_all()
return {} return {}
end end
pkgmgr.refresh_globals()
pkgmgr.update_gamelist()
local ret = {} local ret = {}
local all_content = pkgmgr.get_all() local all_content = pkgmgr.get_all()
for _, content in ipairs(all_content) do for _, content in ipairs(all_content) do

@ -37,11 +37,7 @@ local function delete_content_buttonhandler(this, fields)
gamedata.errormessage = fgettext_ne("pkgmgr: failed to delete \"$1\"", this.data.content.path) gamedata.errormessage = fgettext_ne("pkgmgr: failed to delete \"$1\"", this.data.content.path)
end end
if this.data.content.type == "game" then pkgmgr.reload_by_type(this.data.content.type)
pkgmgr.update_gamelist()
else
pkgmgr.refresh_globals()
end
else else
gamedata.errormessage = fgettext_ne("pkgmgr: invalid path \"$1\"", this.data.content.path) gamedata.errormessage = fgettext_ne("pkgmgr: invalid path \"$1\"", this.data.content.path)
end end

@ -45,7 +45,7 @@ local function rename_modpack_buttonhandler(this, fields)
local oldpath = this.data.mod.path local oldpath = this.data.mod.path
local targetpath = this.data.mod.parent_dir .. DIR_DELIM .. fields["te_modpack_name"] local targetpath = this.data.mod.parent_dir .. DIR_DELIM .. fields["te_modpack_name"]
os.rename(oldpath, targetpath) os.rename(oldpath, targetpath)
pkgmgr.refresh_globals() pkgmgr.reload_global_mods()
pkgmgr.selected_mod = pkgmgr.global_mods:get_current_index( pkgmgr.selected_mod = pkgmgr.global_mods:get_current_index(
pkgmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"])) pkgmgr.global_mods:raw_index_by_uid(fields["te_modpack_name"]))

@ -22,16 +22,27 @@ local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
"settings" .. DIR_DELIM .. "shadows_component.lua") "settings" .. DIR_DELIM .. "shadows_component.lua")
local loaded = false
local full_settings = settingtypes.parse_config_file(false, true) local full_settings
local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png")
local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png")
local all_pages = {} local all_pages = {}
local page_by_id = {} local page_by_id = {}
local filtered_pages = all_pages local filtered_pages = all_pages
local filtered_page_by_id = page_by_id local filtered_page_by_id = page_by_id
local function get_setting_info(name)
for _, entry in ipairs(full_settings) do
if entry.type ~= "category" and entry.name == name then
return entry
end
end
return nil
end
local function add_page(page) local function add_page(page)
assert(type(page.id) == "string") assert(type(page.id) == "string")
assert(type(page.title) == "string") assert(type(page.title) == "string")
@ -46,49 +57,6 @@ local function add_page(page)
end end
local change_keys = {
query_text = "Controls",
requires = {
keyboard_mouse = true,
},
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3)
return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8
end,
on_submit = function(self, fields)
if fields.btn_change_keys then
core.show_keys_menu()
end
end,
}
add_page({
id = "accessibility",
title = fgettext_ne("Accessibility"),
content = {
"language",
{ heading = fgettext_ne("General") },
"font_size",
"chat_font_size",
"gui_scaling",
"hud_scaling",
"show_nametag_backgrounds",
{ heading = fgettext_ne("Chat") },
"console_height",
"console_alpha",
"console_color",
{ heading = fgettext_ne("Controls") },
"autojump",
"safe_dig_and_place",
{ heading = fgettext_ne("Movement") },
"arm_inertia",
"view_bobbing_amount",
"fall_bobbing_amount",
},
})
local function load_settingtypes() local function load_settingtypes()
local page = nil local page = nil
local section = nil local section = nil
@ -129,94 +97,134 @@ local function load_settingtypes()
end end
end end
end end
load_settingtypes()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
do
local content = page_by_id.graphics_and_audio_shaders.content
local idx = table.indexof(content, "enable_dynamic_shadows")
table.insert(content, idx, shadows_component)
end
local function get_setting_info(name) local function load()
for _, entry in ipairs(full_settings) do if loaded then
if entry.type ~= "category" and entry.name == name then return
return entry end
end loaded = true
full_settings = settingtypes.parse_config_file(false, true)
local change_keys = {
query_text = "Controls",
requires = {
keyboard_mouse = true,
},
get_formspec = function(self, avail_w)
local btn_w = math.min(avail_w, 3)
return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8
end,
on_submit = function(self, fields)
if fields.btn_change_keys then
core.show_keys_menu()
end
end,
}
add_page({
id = "accessibility",
title = fgettext_ne("Accessibility"),
content = {
"language",
{ heading = fgettext_ne("General") },
"font_size",
"chat_font_size",
"gui_scaling",
"hud_scaling",
"show_nametag_backgrounds",
{ heading = fgettext_ne("Chat") },
"console_height",
"console_alpha",
"console_color",
{ heading = fgettext_ne("Controls") },
"autojump",
"safe_dig_and_place",
{ heading = fgettext_ne("Movement") },
"arm_inertia",
"view_bobbing_amount",
"fall_bobbing_amount",
},
})
load_settingtypes()
table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
do
local content = page_by_id.graphics_and_audio_shaders.content
local idx = table.indexof(content, "enable_dynamic_shadows")
table.insert(content, idx, shadows_component)
end end
return nil -- These must not be translated, as they need to show in the local
-- language no matter the user's current language.
-- This list must be kept in sync with src/unsupported_language_list.txt.
get_setting_info("language").option_labels = {
[""] = fgettext_ne("(Use system language)"),
--ar = " [ar]", blacklisted
be = "Беларуская [be]",
bg = "Български [bg]",
ca = "Català [ca]",
cs = "Česky [cs]",
cy = "Cymraeg [cy]",
da = "Dansk [da]",
de = "Deutsch [de]",
--dv = " [dv]", blacklisted
el = "Ελληνικά [el]",
en = "English [en]",
eo = "Esperanto [eo]",
es = "Español [es]",
et = "Eesti [et]",
eu = "Euskara [eu]",
fi = "Suomi [fi]",
fil = "Wikang Filipino [fil]",
fr = "Français [fr]",
gd = "Gàidhlig [gd]",
gl = "Galego [gl]",
--he = " [he]", blacklisted
--hi = " [hi]", blacklisted
hu = "Magyar [hu]",
id = "Bahasa Indonesia [id]",
it = "Italiano [it]",
ja = "日本語 [ja]",
jbo = "Lojban [jbo]",
kk = "Қазақша [kk]",
--kn = " [kn]", blacklisted
ko = "한국어 [ko]",
ky = "Kırgızca / Кыргызча [ky]",
lt = "Lietuvių [lt]",
lv = "Latviešu [lv]",
mn = "Монгол [mn]",
mr = "मराठी [mr]",
ms = "Bahasa Melayu [ms]",
--ms_Arab = " [ms_Arab]", blacklisted
nb = "Norsk Bokmål [nb]",
nl = "Nederlands [nl]",
nn = "Norsk Nynorsk [nn]",
oc = "Occitan [oc]",
pl = "Polski [pl]",
pt = "Português [pt]",
pt_BR = "Português do Brasil [pt_BR]",
ro = "Română [ro]",
ru = "Русский [ru]",
sk = "Slovenčina [sk]",
sl = "Slovenščina [sl]",
sr_Cyrl = "Српски [sr_Cyrl]",
sr_Latn = "Srpski (Latinica) [sr_Latn]",
sv = "Svenska [sv]",
sw = "Kiswahili [sw]",
--th = " [th]", blacklisted
tr = "Türkçe [tr]",
tt = "Tatarça [tt]",
uk = "Українська [uk]",
vi = "Tiếng Việt [vi]",
zh_CN = "中文 (简体) [zh_CN]",
zh_TW = "正體中文 (繁體) [zh_TW]",
}
end end
-- These must not be translated, as they need to show in the local
-- language no matter the user's current language.
-- This list must be kept in sync with src/unsupported_language_list.txt.
get_setting_info("language").option_labels = {
[""] = fgettext_ne("(Use system language)"),
--ar = " [ar]", blacklisted
be = "Беларуская [be]",
bg = "Български [bg]",
ca = "Català [ca]",
cs = "Česky [cs]",
cy = "Cymraeg [cy]",
da = "Dansk [da]",
de = "Deutsch [de]",
--dv = " [dv]", blacklisted
el = "Ελληνικά [el]",
en = "English [en]",
eo = "Esperanto [eo]",
es = "Español [es]",
et = "Eesti [et]",
eu = "Euskara [eu]",
fi = "Suomi [fi]",
fil = "Wikang Filipino [fil]",
fr = "Français [fr]",
gd = "Gàidhlig [gd]",
gl = "Galego [gl]",
--he = " [he]", blacklisted
--hi = " [hi]", blacklisted
hu = "Magyar [hu]",
id = "Bahasa Indonesia [id]",
it = "Italiano [it]",
ja = "日本語 [ja]",
jbo = "Lojban [jbo]",
kk = "Қазақша [kk]",
--kn = " [kn]", blacklisted
ko = "한국어 [ko]",
ky = "Kırgızca / Кыргызча [ky]",
lt = "Lietuvių [lt]",
lv = "Latviešu [lv]",
mn = "Монгол [mn]",
mr = "मराठी [mr]",
ms = "Bahasa Melayu [ms]",
--ms_Arab = " [ms_Arab]", blacklisted
nb = "Norsk Bokmål [nb]",
nl = "Nederlands [nl]",
nn = "Norsk Nynorsk [nn]",
oc = "Occitan [oc]",
pl = "Polski [pl]",
pt = "Português [pt]",
pt_BR = "Português do Brasil [pt_BR]",
ro = "Română [ro]",
ru = "Русский [ru]",
sk = "Slovenčina [sk]",
sl = "Slovenščina [sl]",
sr_Cyrl = "Српски [sr_Cyrl]",
sr_Latn = "Srpski (Latinica) [sr_Latn]",
sv = "Svenska [sv]",
sw = "Kiswahili [sw]",
--th = " [th]", blacklisted
tr = "Türkçe [tr]",
tt = "Tatarça [tt]",
uk = "Українська [uk]",
vi = "Tiếng Việt [vi]",
zh_CN = "中文 (简体) [zh_CN]",
zh_TW = "正體中文 (繁體) [zh_TW]",
}
-- See if setting matches keywords -- See if setting matches keywords
local function get_setting_match_weight(entry, query_keywords) local function get_setting_match_weight(entry, query_keywords)
local setting_score = 0 local setting_score = 0
@ -734,6 +742,7 @@ end
function create_settings_dlg() function create_settings_dlg()
load()
local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler)
dlg.data.page_id = update_filtered_pages("") dlg.data.page_id = update_filtered_pages("")

@ -439,9 +439,9 @@ function settingtypes.parse_config_file(read_all, parse_mods)
end end
-- Parse mods -- Parse mods
pkgmgr.load_all()
local mods_category_initialized = false local mods_category_initialized = false
local mods = {} local mods = pkgmgr.global_mods:get_list()
pkgmgr.get_mods(core.get_modpath(), "mods", mods)
table.sort(mods, function(a, b) return a.name < b.name end) table.sort(mods, function(a, b) return a.name < b.name end)
for _, mod in ipairs(mods) do for _, mod in ipairs(mods) do

@ -29,16 +29,11 @@ end
local packages_raw, packages local packages_raw, packages
local function update_packages() local function update_packages()
if not pkgmgr.global_mods then pkgmgr.load_all()
pkgmgr.refresh_globals()
end
if not pkgmgr.games then
pkgmgr.update_gamelist()
end
packages_raw = {} packages_raw = {}
table.insert_all(packages_raw, pkgmgr.games) table.insert_all(packages_raw, pkgmgr.games)
table.insert_all(packages_raw, pkgmgr.get_texture_packs()) table.insert_all(packages_raw, pkgmgr.texture_packs)
table.insert_all(packages_raw, pkgmgr.global_mods:get_list()) table.insert_all(packages_raw, pkgmgr.global_mods:get_list())
local function get_data() local function get_data()
@ -207,6 +202,7 @@ local function handle_doubleclick(pkg)
core.settings:set("texture_path", pkg.path) core.settings:set("texture_path", pkg.path)
end end
packages = nil packages = nil
pkgmgr.reload_texture_packs()
mm_game_theme.init() mm_game_theme.init()
mm_game_theme.set_engine() mm_game_theme.set_engine()
@ -271,6 +267,7 @@ local function handle_buttons(tabview, fields, tabname, tabdata)
core.settings:set("texture_path", txp_path) core.settings:set("texture_path", txp_path)
packages = nil packages = nil
pkgmgr.reload_texture_packs()
mm_game_theme.init() mm_game_theme.init()
mm_game_theme.set_engine() mm_game_theme.set_engine()

@ -314,7 +314,8 @@ Minetest namespace reference
Call these functions only at load time! Call these functions only at load time!
* `minetest.register_globalstep(function(dtime))` * `minetest.register_globalstep(function(dtime))`
* Called every client environment step, usually interval of 0.1s * Called every client environment step
* `dtime` is the time since last execution in seconds.
* `minetest.register_on_mods_loaded(function())` * `minetest.register_on_mods_loaded(function())`
* Called just after mods have finished loading. * Called just after mods have finished loading.
* `minetest.register_on_shutdown(function())` * `minetest.register_on_shutdown(function())`

@ -3902,6 +3902,7 @@ Operators
--------- ---------
Operators can be used if all of the involved vectors have metatables: Operators can be used if all of the involved vectors have metatables:
* `v1 == v2`: * `v1 == v2`:
* Returns whether `v1` and `v2` are identical. * Returns whether `v1` and `v2` are identical.
* `-v`: * `-v`:
@ -4007,8 +4008,9 @@ Helper functions
* X1, Y1, ... Z2 are coordinates * X1, Y1, ... Z2 are coordinates
* `relative_to`: Optional. If set to a position, each coordinate * `relative_to`: Optional. If set to a position, each coordinate
can use the tilde notation for relative positions can use the tilde notation for relative positions
* Tilde notation: "~": Relative coordinate * Tilde notation
"~<number>": Relative coordinate plus <number> * `"~"`: Relative coordinate
* `"~<number>"`: Relative coordinate plus `<number>`
* Example: `minetest.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})` * Example: `minetest.string_to_area("(1,2,3) (~5,~-5,~)", {x=10,y=10,z=10})`
returns `{x=1,y=2,z=3}, {x=15,y=5,z=10}` returns `{x=1,y=2,z=3}, {x=15,y=5,z=10}`
* `minetest.formspec_escape(string)`: returns a string * `minetest.formspec_escape(string)`: returns a string
@ -4346,6 +4348,8 @@ the previous octave.
This may need to be tuned when altering `lacunarity`; when doing so consider This may need to be tuned when altering `lacunarity`; when doing so consider
that a common medium value is 1 / lacunarity. that a common medium value is 1 / lacunarity.
Instead of `persistence`, the key `persist` may be used to the same effect.
### `lacunarity` ### `lacunarity`
Each additional octave has a 'wavelength' that is the 'wavelength' of the Each additional octave has a 'wavelength' that is the 'wavelength' of the
@ -5714,7 +5718,8 @@ Global callback registration functions
Call these functions only at load time! Call these functions only at load time!
* `minetest.register_globalstep(function(dtime))` * `minetest.register_globalstep(function(dtime))`
* Called every server step, usually interval of 0.1s * Called every server step, usually interval of 0.1s.
* `dtime` is the time since last execution in seconds.
* `minetest.register_on_mods_loaded(function())` * `minetest.register_on_mods_loaded(function())`
* Called after mods have finished loading and before the media is cached or the * Called after mods have finished loading and before the media is cached or the
aliases handled. aliases handled.

@ -200,7 +200,7 @@ class IGUIButton : public IGUIElement
\param loop: True if the animation should loop, false if not \param loop: True if the animation should loop, false if not
\param scale: True if the sprite should scale to button size, false if not */ \param scale: True if the sprite should scale to button size, false if not */
virtual void setSprite(EGUI_BUTTON_STATE state, s32 index, virtual void setSprite(EGUI_BUTTON_STATE state, s32 index,
video::SColor color = video::SColor(255, 255, 255, 255), bool loop = false, bool scale = false) = 0; video::SColor color = video::SColor(255, 255, 255, 255), bool loop = false) = 0;
//! Get the sprite-index for the given state or -1 when no sprite is set //! Get the sprite-index for the given state or -1 when no sprite is set
virtual s32 getSpriteIndex(EGUI_BUTTON_STATE state) const = 0; virtual s32 getSpriteIndex(EGUI_BUTTON_STATE state) const = 0;
@ -211,9 +211,6 @@ class IGUIButton : public IGUIElement
//! Returns if the sprite in the given state does loop //! Returns if the sprite in the given state does loop
virtual bool getSpriteLoop(EGUI_BUTTON_STATE state) const = 0; virtual bool getSpriteLoop(EGUI_BUTTON_STATE state) const = 0;
//! Returns if the sprite in the given state is scaled
virtual bool getSpriteScale(EGUI_BUTTON_STATE state) const = 0;
//! Sets if the button should behave like a push button. //! Sets if the button should behave like a push button.
/** Which means it can be in two states: Normal or Pressed. With a click on the button, /** Which means it can be in two states: Normal or Pressed. With a click on the button,
the user can change the state of the button. */ the user can change the state of the button. */

@ -374,6 +374,12 @@ const c8 *const GUISkinFontNames[EGDF_COUNT + 1] = {
class IGUISkin : virtual public IReferenceCounted class IGUISkin : virtual public IReferenceCounted
{ {
public: public:
//! returns display density scaling factor
virtual float getScale() const = 0;
//! sets display density scaling factor
virtual void setScale(float scale) = 0;
//! returns default color //! returns default color
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const = 0; virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const = 0;

@ -75,12 +75,11 @@ void CGUIButton::setSpriteBank(IGUISpriteBank *sprites)
SpriteBank = sprites; SpriteBank = sprites;
} }
void CGUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop, bool scale) void CGUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop)
{ {
ButtonSprites[(u32)state].Index = index; ButtonSprites[(u32)state].Index = index;
ButtonSprites[(u32)state].Color = color; ButtonSprites[(u32)state].Color = color;
ButtonSprites[(u32)state].Loop = loop; ButtonSprites[(u32)state].Loop = loop;
ButtonSprites[(u32)state].Scale = scale;
} }
//! Get the sprite-index for the given state or -1 when no sprite is set //! Get the sprite-index for the given state or -1 when no sprite is set
@ -101,12 +100,6 @@ bool CGUIButton::getSpriteLoop(EGUI_BUTTON_STATE state) const
return ButtonSprites[(u32)state].Loop; return ButtonSprites[(u32)state].Loop;
} }
//! Returns if the sprite in the given state is scaled
bool CGUIButton::getSpriteScale(EGUI_BUTTON_STATE state) const
{
return ButtonSprites[(u32)state].Scale;
}
//! called if an event happened. //! called if an event happened.
bool CGUIButton::OnEvent(const SEvent &event) bool CGUIButton::OnEvent(const SEvent &event)
{ {
@ -294,19 +287,26 @@ void CGUIButton::draw()
void CGUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di &center) void CGUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di &center)
{ {
u32 stateIdx = (u32)state; u32 stateIdx = (u32)state;
s32 spriteIdx = ButtonSprites[stateIdx].Index;
if (spriteIdx == -1)
return;
if (ButtonSprites[stateIdx].Index != -1) { u32 rectIdx = SpriteBank->getSprites()[spriteIdx].Frames[0].rectNumber;
if (ButtonSprites[stateIdx].Scale) { core::rect<s32> srcRect = SpriteBank->getPositions()[rectIdx];
const video::SColor colors[] = {ButtonSprites[stateIdx].Color, ButtonSprites[stateIdx].Color, ButtonSprites[stateIdx].Color, ButtonSprites[stateIdx].Color};
SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, AbsoluteRect, IGUISkin *skin = Environment->getSkin();
&AbsoluteClippingRect, colors, s32 scale = std::max(std::floor(skin->getScale()), 1.0f);
os::Timer::getTime() - startTime, ButtonSprites[stateIdx].Loop); core::rect<s32> rect(center, srcRect.getSize() * scale);
} else { rect -= rect.getSize() / 2;
SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, center,
&AbsoluteClippingRect, ButtonSprites[stateIdx].Color, startTime, os::Timer::getTime(), const video::SColor colors[] = {
ButtonSprites[stateIdx].Loop, true); ButtonSprites[stateIdx].Color,
} ButtonSprites[stateIdx].Color,
} ButtonSprites[stateIdx].Color,
ButtonSprites[stateIdx].Color,
};
SpriteBank->draw2DSprite(spriteIdx, rect, &AbsoluteClippingRect, colors,
os::Timer::getTime() - startTime, ButtonSprites[stateIdx].Loop);
} }
EGUI_BUTTON_IMAGE_STATE CGUIButton::getImageState(bool pressed) const EGUI_BUTTON_IMAGE_STATE CGUIButton::getImageState(bool pressed) const

@ -92,7 +92,7 @@ class CGUIButton : public IGUIButton
*/ */
virtual void setSprite(EGUI_BUTTON_STATE state, s32 index, virtual void setSprite(EGUI_BUTTON_STATE state, s32 index,
video::SColor color = video::SColor(255, 255, 255, 255), video::SColor color = video::SColor(255, 255, 255, 255),
bool loop = false, bool scale = false) override; bool loop = false) override;
//! Get the sprite-index for the given state or -1 when no sprite is set //! Get the sprite-index for the given state or -1 when no sprite is set
s32 getSpriteIndex(EGUI_BUTTON_STATE state) const override; s32 getSpriteIndex(EGUI_BUTTON_STATE state) const override;
@ -103,9 +103,6 @@ class CGUIButton : public IGUIButton
//! Returns if the sprite in the given state does loop //! Returns if the sprite in the given state does loop
bool getSpriteLoop(EGUI_BUTTON_STATE state) const override; bool getSpriteLoop(EGUI_BUTTON_STATE state) const override;
//! Returns if the sprite in the given state is scaled
bool getSpriteScale(EGUI_BUTTON_STATE state) const override;
//! Sets if the button should behave like a push button. Which means it //! Sets if the button should behave like a push button. Which means it
//! can be in two states: Normal or Pressed. With a click on the button, //! can be in two states: Normal or Pressed. With a click on the button,
//! the user can change the state of the button. //! the user can change the state of the button.
@ -158,19 +155,18 @@ class CGUIButton : public IGUIButton
struct ButtonSprite struct ButtonSprite
{ {
ButtonSprite() : ButtonSprite() :
Index(-1), Loop(false), Scale(false) Index(-1), Loop(false)
{ {
} }
bool operator==(const ButtonSprite &other) const bool operator==(const ButtonSprite &other) const
{ {
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale; return Index == other.Index && Color == other.Color && Loop == other.Loop;
} }
s32 Index; s32 Index;
video::SColor Color; video::SColor Color;
bool Loop; bool Loop;
bool Scale;
}; };
ButtonSprite ButtonSprites[EGBS_COUNT]; ButtonSprite ButtonSprites[EGBS_COUNT];

@ -24,6 +24,12 @@ class CGUISkin : public IGUISkin
//! destructor //! destructor
virtual ~CGUISkin(); virtual ~CGUISkin();
//! returns display density scaling factor
virtual float getScale() const override { return Scale; }
//! sets display density scaling factor
virtual void setScale(float scale) override { Scale = scale; }
//! returns default color //! returns default color
video::SColor getColor(EGUI_DEFAULT_COLOR color) const override; video::SColor getColor(EGUI_DEFAULT_COLOR color) const override;
@ -210,6 +216,7 @@ class CGUISkin : public IGUISkin
EGUI_SKIN_TYPE getType() const override; EGUI_SKIN_TYPE getType() const override;
private: private:
float Scale = 1.0f;
video::SColor Colors[EGDC_COUNT]; video::SColor Colors[EGDC_COUNT];
s32 Sizes[EGDS_COUNT]; s32 Sizes[EGDS_COUNT];
u32 Icons[EGDI_COUNT]; u32 Icons[EGDI_COUNT];

@ -129,9 +129,9 @@ EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEv
} }
#endif #endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key) bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{ {
switch (key) { switch (irrlichtKey) {
// keys which are known to have safe special character interpretation // keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!) // could need changes over time (removals and additions!)
case KEY_RETURN: case KEY_RETURN:
@ -189,24 +189,68 @@ bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
} }
} }
int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key) int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{ {
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode // special cases that always return a char regardless of how the SDL keycode
// looks // looks
switch (key) {
case KEY_RETURN: case KEY_RETURN:
case KEY_ESCAPE: case KEY_ESCAPE:
return (int)key; return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default: default:
break; break;
} }
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30 // SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup // https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
if (assumedChar & (1 << 30)) // This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0; return 0;
switch (key) { switch (irrlichtKey) {
case KEY_PRIOR: case KEY_PRIOR:
case KEY_NEXT: case KEY_NEXT:
case KEY_HOME: case KEY_HOME:
@ -218,7 +262,7 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
case KEY_NUMLOCK: case KEY_NUMLOCK:
return 0; return 0;
default: default:
return assumedChar; return sdlKey;
} }
} }
@ -825,7 +869,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN); irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0; irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0; irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key); irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent); postEventFromUser(irrevent);
} break; } break;

@ -273,10 +273,10 @@ class CIrrDeviceSDL : public CIrrDeviceStub
#endif #endif
// Check if a key is a known special character with no side effects on text boxes. // Check if a key is a known special character with no side effects on text boxes.
static bool keyIsKnownSpecial(EKEY_CODE key); static bool keyIsKnownSpecial(EKEY_CODE irrlichtKey);
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0). // Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key); static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus. // Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents(); void resetReceiveTextInputEvents();

@ -352,6 +352,7 @@ void ClientLauncher::config_guienv()
float density = rangelim(g_settings->getFloat("gui_scaling"), 0.5f, 20) * float density = rangelim(g_settings->getFloat("gui_scaling"), 0.5f, 20) *
RenderingEngine::getDisplayDensity(); RenderingEngine::getDisplayDensity();
skin->setScale(density);
skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density)); skin->setSize(gui::EGDS_CHECK_BOX_WIDTH, (s32)(17.0f * density));
skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(21.0f * density)); skin->setSize(gui::EGDS_SCROLLBAR_SIZE, (s32)(21.0f * density));
skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density)); skin->setSize(gui::EGDS_WINDOW_BUTTON_WIDTH, (s32)(15.0f * density));

@ -89,12 +89,11 @@ void GUIButton::setSpriteBank(IGUISpriteBank* sprites)
SpriteBank = sprites; SpriteBank = sprites;
} }
void GUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop, bool scale) void GUIButton::setSprite(EGUI_BUTTON_STATE state, s32 index, video::SColor color, bool loop)
{ {
ButtonSprites[(u32)state].Index = index; ButtonSprites[(u32)state].Index = index;
ButtonSprites[(u32)state].Color = color; ButtonSprites[(u32)state].Color = color;
ButtonSprites[(u32)state].Loop = loop; ButtonSprites[(u32)state].Loop = loop;
ButtonSprites[(u32)state].Scale = scale;
} }
//! Get the sprite-index for the given state or -1 when no sprite is set //! Get the sprite-index for the given state or -1 when no sprite is set
@ -115,12 +114,6 @@ bool GUIButton::getSpriteLoop(EGUI_BUTTON_STATE state) const
return ButtonSprites[(u32)state].Loop; return ButtonSprites[(u32)state].Loop;
} }
//! Returns if the sprite in the given state is scaled
bool GUIButton::getSpriteScale(EGUI_BUTTON_STATE state) const
{
return ButtonSprites[(u32)state].Scale;
}
//! called if an event happened. //! called if an event happened.
bool GUIButton::OnEvent(const SEvent& event) bool GUIButton::OnEvent(const SEvent& event)
{ {
@ -354,23 +347,26 @@ void GUIButton::draw()
void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center) void GUIButton::drawSprite(EGUI_BUTTON_STATE state, u32 startTime, const core::position2di& center)
{ {
u32 stateIdx = (u32)state; u32 stateIdx = (u32)state;
s32 spriteIdx = ButtonSprites[stateIdx].Index;
if (spriteIdx == -1)
return;
if (ButtonSprites[stateIdx].Index != -1) u32 rectIdx = SpriteBank->getSprites()[spriteIdx].Frames[0].rectNumber;
{ core::rect<s32> srcRect = SpriteBank->getPositions()[rectIdx];
if ( ButtonSprites[stateIdx].Scale )
{ IGUISkin *skin = Environment->getSkin();
const video::SColor colors[] = {ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color,ButtonSprites[stateIdx].Color}; s32 scale = std::max(std::floor(skin->getScale()), 1.0f);
SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, AbsoluteRect.UpperLeftCorner, core::rect<s32> rect(center, srcRect.getSize() * scale);
&AbsoluteClippingRect, colors[0], // FIXME: remove [0] rect -= rect.getSize() / 2;
porting::getTimeMs()-startTime, ButtonSprites[stateIdx].Loop);
} const video::SColor colors[] = {
else ButtonSprites[stateIdx].Color,
{ ButtonSprites[stateIdx].Color,
SpriteBank->draw2DSprite(ButtonSprites[stateIdx].Index, center, ButtonSprites[stateIdx].Color,
&AbsoluteClippingRect, ButtonSprites[stateIdx].Color, startTime, porting::getTimeMs(), ButtonSprites[stateIdx].Color,
ButtonSprites[stateIdx].Loop, true); };
} SpriteBank->draw2DSprite(spriteIdx, rect, &AbsoluteClippingRect, colors,
} porting::getTimeMs() - startTime, ButtonSprites[stateIdx].Loop);
} }
EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const EGUI_BUTTON_IMAGE_STATE GUIButton::getImageState(bool pressed) const

@ -92,7 +92,7 @@ class GUIButton : public gui::IGUIButton
*/ */
virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index, virtual void setSprite(gui::EGUI_BUTTON_STATE state, s32 index,
video::SColor color=video::SColor(255,255,255,255), video::SColor color=video::SColor(255,255,255,255),
bool loop=false, bool scale=false) override; bool loop=false) override;
//! Get the sprite-index for the given state or -1 when no sprite is set //! Get the sprite-index for the given state or -1 when no sprite is set
virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override; virtual s32 getSpriteIndex(gui::EGUI_BUTTON_STATE state) const override;
@ -103,9 +103,6 @@ class GUIButton : public gui::IGUIButton
//! Returns if the sprite in the given state does loop //! Returns if the sprite in the given state does loop
virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override; virtual bool getSpriteLoop(gui::EGUI_BUTTON_STATE state) const override;
//! Returns if the sprite in the given state is scaled
virtual bool getSpriteScale(gui::EGUI_BUTTON_STATE state) const override;
//! Sets if the button should behave like a push button. Which means it //! Sets if the button should behave like a push button. Which means it
//! can be in two states: Normal or Pressed. With a click on the button, //! can be in two states: Normal or Pressed. With a click on the button,
//! the user can change the state of the button. //! the user can change the state of the button.
@ -230,13 +227,12 @@ class GUIButton : public gui::IGUIButton
{ {
bool operator==(const ButtonSprite &other) const bool operator==(const ButtonSprite &other) const
{ {
return Index == other.Index && Color == other.Color && Loop == other.Loop && Scale == other.Scale; return Index == other.Index && Color == other.Color && Loop == other.Loop;
} }
s32 Index = -1; s32 Index = -1;
video::SColor Color; video::SColor Color;
bool Loop = false; bool Loop = false;
bool Scale = false;
}; };
ButtonSprite ButtonSprites[gui::EGBS_COUNT]; ButtonSprite ButtonSprites[gui::EGBS_COUNT];

@ -27,6 +27,12 @@ namespace gui
//! destructor //! destructor
virtual ~GUISkin(); virtual ~GUISkin();
//! returns display density scaling factor
virtual float getScale() const { return Scale; }
//! sets display density scaling factor
virtual void setScale(float scale) { Scale = scale; }
//! returns default color //! returns default color
virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const; virtual video::SColor getColor(EGUI_DEFAULT_COLOR color) const;
@ -292,6 +298,7 @@ namespace gui
private: private:
float Scale = 1.0f;
video::SColor Colors[EGDC_COUNT]; video::SColor Colors[EGDC_COUNT];
s32 Sizes[EGDS_COUNT]; s32 Sizes[EGDS_COUNT];
u32 Icons[EGDI_COUNT]; u32 Icons[EGDI_COUNT];