Initial commit.
282
init.lua
Normal file
|
|
@ -0,0 +1,282 @@
|
||||||
|
|
||||||
|
menupath = core.settings:get("main_menu_script"):match("(.*/)[^/]+.lua$") or
|
||||||
|
core.get_builtin_path().."/"
|
||||||
|
|
||||||
|
default_textures = minetest.get_texturepath_share().."/base/pack/"
|
||||||
|
|
||||||
|
version = minetest.get_version()
|
||||||
|
|
||||||
|
minetest.async_jobs = {}
|
||||||
|
|
||||||
|
local function handle_job(jobid, serialized_retval)
|
||||||
|
local retval = minetest.deserialize(serialized_retval)
|
||||||
|
assert(type(minetest.async_jobs[jobid]) == "function")
|
||||||
|
minetest.async_jobs[jobid](retval)
|
||||||
|
minetest.async_jobs[jobid] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.async_event_handler = handle_job
|
||||||
|
|
||||||
|
function minetest.handle_async(func, parameter, callback)
|
||||||
|
-- Serialize parameters
|
||||||
|
local serialized_param = minetest.serialize(parameter)
|
||||||
|
|
||||||
|
if serialized_param == nil then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local jobid = minetest.do_async_callback(func, serialized_param)
|
||||||
|
|
||||||
|
minetest.async_jobs[jobid] = callback
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
dofile(menupath.."imfs.lua")
|
||||||
|
dofile(menupath.."theme.lua")
|
||||||
|
dofile(menupath.."package.lua")
|
||||||
|
dofile(menupath.."syntax.lua")
|
||||||
|
|
||||||
|
|
||||||
|
window = minetest.get_window_info()
|
||||||
|
size = window.max_formspec_size
|
||||||
|
|
||||||
|
function pixel_to_formspec(pixel)
|
||||||
|
return tonumber(pixel) / 80 * window.real_gui_scaling
|
||||||
|
end
|
||||||
|
|
||||||
|
local style_no_background = {
|
||||||
|
bgimg = default_textures.."blank.png",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
content_offset = "0,0",
|
||||||
|
textcolor = theme.styles.text_color,
|
||||||
|
}
|
||||||
|
styles_no_background = {
|
||||||
|
default = style_no_background,
|
||||||
|
hovered = style_no_background,
|
||||||
|
pressed = style_no_background,
|
||||||
|
}
|
||||||
|
|
||||||
|
style_borderless = {
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#ffff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
bgcolor = theme.styles.container.bg_color,
|
||||||
|
}
|
||||||
|
style_borderless_alt = {
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#ffff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
bgcolor = theme.styles.container.bg_color_alt,
|
||||||
|
}
|
||||||
|
style_borderless_hovered = {
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#ffff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
local return_to = "meta"
|
||||||
|
|
||||||
|
function file_exists(path)
|
||||||
|
local f = io.open(path)
|
||||||
|
if f then
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function read_file(path)
|
||||||
|
local f = io.open(path)
|
||||||
|
if f then
|
||||||
|
local data = f:read("a")
|
||||||
|
f:close()
|
||||||
|
if data == "" then return end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function format_inline_markdown(str)
|
||||||
|
local out = {}
|
||||||
|
local i = 1
|
||||||
|
while true do
|
||||||
|
local code_start, code_end, code = str:find("`(.-)`", i)
|
||||||
|
if not code_start then break end
|
||||||
|
out[#out + 1] = str:sub(i, code_start - 1)
|
||||||
|
:gsub("([_*])%1(.-)%1%1", "<b>%2</b>")
|
||||||
|
:gsub("([_*])(.-)%1", "<i>%2</i>")
|
||||||
|
:gsub("%[(.-)%]%((.-)%)", "<action name=none>%1</action>")
|
||||||
|
out[#out + 1] = "<mono><style color="
|
||||||
|
out[#out + 1] = theme.styles.code_color
|
||||||
|
out[#out + 1] = ">"
|
||||||
|
out[#out + 1] = code
|
||||||
|
out[#out + 1] = "</style></mono>"
|
||||||
|
i = code_end + 1
|
||||||
|
end
|
||||||
|
out[#out + 1] = str:sub(i)
|
||||||
|
:gsub("([_*])%1(.-)%1%1", "<b>%2</b>")
|
||||||
|
:gsub("([_*])(.-)%1", "<i>%2</i>")
|
||||||
|
:gsub("%[(.-)%]%((.-)%)", "<action name=none>%1</action>")
|
||||||
|
return table.concat(out)
|
||||||
|
end
|
||||||
|
|
||||||
|
function markdown_to_hypertext(md)
|
||||||
|
md = core.hypertext_escape(md.."\n")
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
local state = "doc"
|
||||||
|
for line in md:gmatch("(.-)\n") do
|
||||||
|
if state == "code" then
|
||||||
|
if line:match("^```") then
|
||||||
|
state = "doc"
|
||||||
|
local code = table.concat(out, "\n")
|
||||||
|
if out.__lang == "lua" then
|
||||||
|
code = syntax_highlight(code)
|
||||||
|
else
|
||||||
|
code = "<style color="..theme.styles.code_color..">"..code.."</style>"
|
||||||
|
end
|
||||||
|
out = out.__parent
|
||||||
|
out[#out + 1] = "<mono>"
|
||||||
|
out[#out + 1] = code
|
||||||
|
out[#out + 1] = "</mono>"
|
||||||
|
else
|
||||||
|
out[#out + 1] = line
|
||||||
|
out[#out + 1] = "\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local level, heading = line:match("^(#+) (.+)")
|
||||||
|
if heading then
|
||||||
|
out[#out + 1] = "<style size="
|
||||||
|
out[#out + 1] = 18 * (1 + 1 / #level)
|
||||||
|
out[#out + 1] = " color="
|
||||||
|
out[#out + 1] = theme.styles.heading_color
|
||||||
|
out[#out + 1] = ">"
|
||||||
|
out[#out + 1] = format_inline_markdown(heading)
|
||||||
|
out[#out + 1] = "</style>"
|
||||||
|
out[#out + 1] = "\n"
|
||||||
|
else
|
||||||
|
local bullet, item = line:match("^%s-([*-]) (.+)")
|
||||||
|
if bullet then
|
||||||
|
out[#out + 1] = " • "
|
||||||
|
out[#out + 1] = format_inline_markdown(item)
|
||||||
|
out[#out + 1] = "\n"
|
||||||
|
else
|
||||||
|
local code, lang = line:match("^(```)(%a*)")
|
||||||
|
if code then
|
||||||
|
state = "code"
|
||||||
|
out = {__parent = out, __lang = lang}
|
||||||
|
else
|
||||||
|
out[#out + 1] = format_inline_markdown(line)
|
||||||
|
out[#out + 1] = "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return table.concat(out)
|
||||||
|
-- return md:gsub("\n%s*####%s*([^\n]-)\n", "\n<b>%1</b>\n")
|
||||||
|
-- :gsub("\n%s*###%s*([^\n]-)\n", "\n<big>%1</big>\n")
|
||||||
|
-- :gsub("([^\n]-)\n%-+\n", "\n<big>%1</big>\n")
|
||||||
|
-- :gsub("\n%s*##%s*([^\n]-)\n", "\n<bigger>%1</bigger>\n")
|
||||||
|
-- :gsub("\n%s*#%s*([^\n]-)\n", "\n<style size=48>%1</style>\n")
|
||||||
|
-- :gsub("%*%*([^`\n]-)%*%*", "<b>%1</b>")
|
||||||
|
-- :gsub("%*([^`\n]-)%*", "<i>%1</i>")
|
||||||
|
-- :gsub("__([^`\n]-)__", "<b>%1</b>")
|
||||||
|
-- :gsub("\n_([^`\n]-)_", "<i>%1</i>")
|
||||||
|
-- :gsub("%*%s+(.-)\n", "• %1\n")
|
||||||
|
---- :gsub("```%a-\n(.-)```", "<mono><style color="..theme.code_color..">%1</style></mono>")
|
||||||
|
---- :gsub("`([^`]-)`", "<mono><style color="..theme.code_color..">%1</style></mono>")
|
||||||
|
-- -- Since we can't (and shouldn't) display images over HTTPS at all, simply get rid of them.
|
||||||
|
-- :gsub("%[?!%[[^%]]-%]%([^\n]+%)", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.set_formspec_prepend("\
|
||||||
|
style[*;textcolor=#aaa]\
|
||||||
|
style_type[field,pwdfield;border=false]\
|
||||||
|
style_type[image_button;border=false]\
|
||||||
|
style_type[button;border=false;bgimg="..theme.get_background_image("button")..";bgimg_middle=8,8;textcolor="..theme.styles.button.text_color.."]\
|
||||||
|
style_type[button:hovered;border=false;bgimg="..theme.get_background_image("button", "hovered")..";bgimg_middle=8,8]\
|
||||||
|
style_type[button:pressed;border=false;bgimg="..theme.get_background_image("button", "active")..";bgimg_middle=8,8]\
|
||||||
|
style_type[scrollbar;border=false;bgimg="..theme.get_background_image("scrollbar")..";bgimg_middle=8;padding=0,8;fgimg="..theme.get_image("scrollbar", "thumb")..";size=32]\
|
||||||
|
style[nobg,nobg:hovered,nobg:focused,nobg:hovered+focused;border=false;bgimg="..default_textures.."blank.png;bgimg_middle=0;content_offset=0,0]\
|
||||||
|
")
|
||||||
|
|
||||||
|
local meta_menu = dofile(menupath.."views/meta_menu.lua")
|
||||||
|
local servers_menu = dofile(menupath.."views/servers.lua")
|
||||||
|
local content_menu = dofile(menupath.."views/content.lua")
|
||||||
|
local online_content_menu = dofile(menupath.."views/online_content.lua")
|
||||||
|
local settings_menu = dofile(menupath.."views/settings.lua")
|
||||||
|
local about_menu = dofile(menupath.."views/about.lua")
|
||||||
|
|
||||||
|
function show_meta_menu()
|
||||||
|
return_to = "meta"
|
||||||
|
local last_game = minetest.settings:get("menu_last_game")
|
||||||
|
local last_game_index = 1
|
||||||
|
local games = minetest.get_games()
|
||||||
|
table.sort(games, function(a, b)
|
||||||
|
return (a.title or a.name) < (b.title or b.name)
|
||||||
|
end)
|
||||||
|
for i, x in ipairs(games) do
|
||||||
|
if x.id == last_game then last_game_index = i end
|
||||||
|
end
|
||||||
|
imfs.show(meta_menu, {
|
||||||
|
games = games,
|
||||||
|
scroll_pos = imfs.state(last_game_index)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_servers_menu()
|
||||||
|
return_to = "servers"
|
||||||
|
imfs.show(servers_menu, {
|
||||||
|
scroll_pos = imfs.state(1),
|
||||||
|
serverlist = imfs.state(),
|
||||||
|
detail = imfs.state(),
|
||||||
|
detail_view = imfs.state(),
|
||||||
|
joining = imfs.state(),
|
||||||
|
join_error = imfs.state(),
|
||||||
|
address = imfs.state(),
|
||||||
|
port = imfs.state(),
|
||||||
|
username = imfs.state(core.settings:get("name")),
|
||||||
|
password = imfs.state(""),
|
||||||
|
search = ""
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_content_menu()
|
||||||
|
imfs.show(content_menu, {
|
||||||
|
search = "",
|
||||||
|
show_type = imfs.state("all"),
|
||||||
|
content_shown = imfs.state(),
|
||||||
|
page = imfs.state(1),
|
||||||
|
current_package = imfs.state(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_online_content_menu(content)
|
||||||
|
imfs.show(online_content_menu, {
|
||||||
|
search = "",
|
||||||
|
show_type = imfs.state("all"),
|
||||||
|
content_shown = imfs.state(),
|
||||||
|
page = imfs.state(1),
|
||||||
|
current_package = imfs.state(),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_settings_menu()
|
||||||
|
imfs.show(settings_menu, {
|
||||||
|
settings_shown = imfs.state(),
|
||||||
|
categories_shown = imfs.state(),
|
||||||
|
category = imfs.state("Theme"),
|
||||||
|
search = ""
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function show_about_menu()
|
||||||
|
imfs.show(about_menu, {
|
||||||
|
search = "",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
show_meta_menu()
|
||||||
|
|
||||||
152
package.lua
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
|
||||||
|
package = {}
|
||||||
|
|
||||||
|
local function filename(url)
|
||||||
|
local path = url:split("/")
|
||||||
|
return path[#path]
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.install_from_git(url)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.install_from_contentdb(url)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.can_update()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.update()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.info()
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.is_installed(kind, name)
|
||||||
|
local path = core.get_user_path()
|
||||||
|
if kind == "mod" or kind == "modpack" then
|
||||||
|
path = path.."/mods/"..name
|
||||||
|
elseif kind == "game" then
|
||||||
|
path = path.."/games/"..name
|
||||||
|
elseif kind == "txp" then
|
||||||
|
path = path.."/textures/"..name
|
||||||
|
end
|
||||||
|
return core.is_dir(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
local loading_screenshots = {}
|
||||||
|
local screenshot_path = core.get_cache_path().."/cdb"
|
||||||
|
function package.screenshot(pkg, url, level)
|
||||||
|
if not url then
|
||||||
|
url = pkg.thumbnail
|
||||||
|
end
|
||||||
|
|
||||||
|
if not level then
|
||||||
|
level = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
if not url then
|
||||||
|
return default_textures.."no_screenshot.png"
|
||||||
|
end
|
||||||
|
|
||||||
|
url = url:gsub("/thumbnails/[0-9]+/", "/thumbnails/"..level.."/")
|
||||||
|
|
||||||
|
if loading_screenshots[url] then
|
||||||
|
return loading_screenshots[url]
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = screenshot_path.."/"
|
||||||
|
..string.format(
|
||||||
|
"%s-%s-%s-l%d-%s",
|
||||||
|
pkg.type,
|
||||||
|
pkg.author,
|
||||||
|
pkg.name,
|
||||||
|
level,
|
||||||
|
filename(url)
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_exists(path) then
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
|
||||||
|
local state = imfs.state(default_textures.."loading_screenshot.png")
|
||||||
|
loading_screenshots[url] = state
|
||||||
|
|
||||||
|
core.handle_async(function(args)
|
||||||
|
return core.download_file(args.url, args.path)
|
||||||
|
end, {path = path, url = url},
|
||||||
|
function(success)
|
||||||
|
state(path)
|
||||||
|
loading_screenshots[url] = nil
|
||||||
|
end)
|
||||||
|
|
||||||
|
return state
|
||||||
|
end
|
||||||
|
|
||||||
|
local function alphabetic_sort(a, b)
|
||||||
|
return (a.title ~= "" and a.title or a.name) < (b.title ~= "" and b.title or b.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.games(dir, sort)
|
||||||
|
if not dir then
|
||||||
|
dir = core.get_gamepath()
|
||||||
|
end
|
||||||
|
local out = {}
|
||||||
|
for _, x in ipairs(core.get_dir_list(dir, true)) do
|
||||||
|
local info = core.get_content_info(dir.."/"..x)
|
||||||
|
if info.type == "game" then
|
||||||
|
out[#out +1] = info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if sort ~= false then
|
||||||
|
table.sort(out, alphabetic_sort)
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.mods(dir, sort)
|
||||||
|
if not dir then
|
||||||
|
dir = core.get_modpath()
|
||||||
|
end
|
||||||
|
local out = {}
|
||||||
|
for _, x in ipairs(core.get_dir_list(dir, true)) do
|
||||||
|
local info = core.get_content_info(dir.."/"..x)
|
||||||
|
if info.type == "mod" or info.type == "modpack" then
|
||||||
|
out[#out +1] = info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if sort ~= false then
|
||||||
|
table.sort(out, alphabetic_sort)
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.texturepacks(dir, sort)
|
||||||
|
if not dir then
|
||||||
|
dir = core.get_texturepath()
|
||||||
|
end
|
||||||
|
local out = {}
|
||||||
|
for _, x in ipairs(core.get_dir_list(dir, true)) do
|
||||||
|
local info = core.get_content_info(dir.."/"..x)
|
||||||
|
if info.type == "txp" then
|
||||||
|
out[#out +1] = info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if sort ~= false then
|
||||||
|
table.sort(out, alphabetic_sort)
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
function package.all()
|
||||||
|
local out = {}
|
||||||
|
table.insert_all(out, package.mods(nil, false))
|
||||||
|
table.insert_all(out, package.games(nil, false))
|
||||||
|
table.insert_all(out, package.texturepacks(nil, false))
|
||||||
|
table.sort(out, alphabetic_sort)
|
||||||
|
return out
|
||||||
|
end
|
||||||
122
syntax.lua
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
local keywords = {
|
||||||
|
"and", "break", "do", "else", "elseif", "end", --[["false",]] "for", "function",
|
||||||
|
"goto", "if", "in", --[["local", "nil",]] "not", "or", "repeat", "return",
|
||||||
|
"then", --[["true",]] "until", "while"
|
||||||
|
}
|
||||||
|
local keyword_set = {}
|
||||||
|
for _, kw in ipairs(keywords) do
|
||||||
|
keyword_set[kw] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local keyword_value_set = {
|
||||||
|
["true"] = true,
|
||||||
|
["false"] = true,
|
||||||
|
["nil"] = true,
|
||||||
|
["local"] = true,
|
||||||
|
["math.huge"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
local function colorize(str, color)
|
||||||
|
return "<style color="..(theme.styles["syntax_"..color] or "#fff")..">"..str.."</style>"
|
||||||
|
end
|
||||||
|
|
||||||
|
function syntax_highlight(code)
|
||||||
|
local out = ""
|
||||||
|
local pos = 1
|
||||||
|
while pos <= #code do
|
||||||
|
local matched = false
|
||||||
|
|
||||||
|
-- Whitespace
|
||||||
|
local ws_start, ws_end = code:find("^%s+", pos)
|
||||||
|
if ws_start then
|
||||||
|
out = out..colorize(code:sub(ws_start, ws_end), "whitespace")
|
||||||
|
pos = ws_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Single-line comment
|
||||||
|
if not matched then
|
||||||
|
local com_start, com_end = code:find("^%-%-[^\n]*", pos)
|
||||||
|
if com_start then
|
||||||
|
out = out..colorize(code:sub(com_start, com_end), "comment")
|
||||||
|
pos = com_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- String (double-quoted, simple escape handling)
|
||||||
|
if not matched then
|
||||||
|
local str_start, str_end = code:find('^"[^"]*"', pos)
|
||||||
|
if str_start then
|
||||||
|
out = out..colorize(code:sub(str_start, str_end), "string")
|
||||||
|
pos = str_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- String (single-quoted, similar)
|
||||||
|
if not matched then
|
||||||
|
local str_start, str_end = code:find("^'[^']*", pos)
|
||||||
|
if str_start then
|
||||||
|
out = out..colorize(code:sub(str_start, str_end), "string")
|
||||||
|
pos = str_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Documentation placeholders. These aren't in Lua, but that means they
|
||||||
|
-- can be highlighted without worrying about confusing alternative meanings.
|
||||||
|
if not matched then
|
||||||
|
local str_start, str_end = code:find('^\\?<[^>]*>', pos)
|
||||||
|
if str_start then
|
||||||
|
out = out..colorize(code:sub(str_start, str_end), "placeholder")
|
||||||
|
pos = str_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Number (basic integer/float)
|
||||||
|
if not matched then
|
||||||
|
local num_start, num_end = code:find("^%d+%.?%d*[eE]?[+-]?%d*", pos)
|
||||||
|
if num_start then
|
||||||
|
out = out..colorize(code:sub(num_start, num_end), "number")
|
||||||
|
pos = num_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Identifier/Keyword
|
||||||
|
if not matched then
|
||||||
|
local id_start, id_end, id_text = code:find("^([%a_][%w_]*)", pos)
|
||||||
|
if id_start then
|
||||||
|
if keyword_set[id_text] then
|
||||||
|
out = out..colorize(id_text, "keyword")
|
||||||
|
elseif keyword_value_set[id_text] then
|
||||||
|
out = out..colorize(id_text, "keyword_value")
|
||||||
|
else
|
||||||
|
out = out..colorize(id_text, "identifier")
|
||||||
|
end
|
||||||
|
pos = id_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Operators/Punctuation (catch-all for non-alphanumeric)
|
||||||
|
if not matched then
|
||||||
|
local op_start, op_end = code:find("^[^%s%a%d_\"'{}(),%][]", pos)
|
||||||
|
if op_start then
|
||||||
|
local op = code:sub(op_start, op_end)
|
||||||
|
out = out..colorize(op, op == "-" and code:sub(op_end +1, op_end +2):find "^%d" and "number" or "operator")
|
||||||
|
pos = op_end + 1
|
||||||
|
matched = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If nothing matched, advance to avoid infinite loop (e.g., invalid char)
|
||||||
|
if not matched then
|
||||||
|
out = out..colorize(code:sub(pos, pos), "generic")
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
BIN
textures/button_bg_shape.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
textures/button_border_shape.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
textures/cancel.png
Normal file
|
After Width: | Height: | Size: 178 B |
BIN
textures/checkbox_bg_shape.png
Normal file
|
After Width: | Height: | Size: 118 B |
BIN
textures/checkbox_border_shape.png
Normal file
|
After Width: | Height: | Size: 128 B |
BIN
textures/checkbox_check_shape.png
Normal file
|
After Width: | Height: | Size: 129 B |
BIN
textures/circle.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
textures/circle_active.png
Normal file
|
After Width: | Height: | Size: 171 B |
BIN
textures/container_bg_shape.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
textures/container_border_shape.png
Normal file
|
After Width: | Height: | Size: 261 B |
BIN
textures/field_bg_shape.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
textures/field_border_shape.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
textures/games.png
Normal file
|
After Width: | Height: | Size: 168 B |
BIN
textures/loader.png
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
textures/menu_content.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
textures/menu_content_hovered.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
textures/menu_servers.png
Normal file
|
After Width: | Height: | Size: 313 B |
BIN
textures/menu_servers_creative.png
Normal file
|
After Width: | Height: | Size: 157 B |
BIN
textures/menu_servers_favorite.png
Normal file
|
After Width: | Height: | Size: 216 B |
BIN
textures/menu_servers_hovered.png
Normal file
|
After Width: | Height: | Size: 417 B |
BIN
textures/menu_servers_icon_ping_0.png
Normal file
|
After Width: | Height: | Size: 117 B |
BIN
textures/menu_servers_icon_ping_1.png
Normal file
|
After Width: | Height: | Size: 136 B |
BIN
textures/menu_servers_icon_ping_2.png
Normal file
|
After Width: | Height: | Size: 156 B |
BIN
textures/menu_servers_icon_ping_3.png
Normal file
|
After Width: | Height: | Size: 158 B |
BIN
textures/menu_servers_icon_ping_4.png
Normal file
|
After Width: | Height: | Size: 145 B |
BIN
textures/menu_servers_mods.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
textures/menu_servers_peaceful.png
Normal file
|
After Width: | Height: | Size: 188 B |
BIN
textures/menu_servers_players.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
textures/menu_servers_unfavorite.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
textures/refresh.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
textures/scrollbar_bg_shape.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
textures/scrollbar_border_shape.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
textures/scrollbar_thumb_shape.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
textures/search.png
Normal file
|
After Width: | Height: | Size: 170 B |
BIN
textures/tab_bg_shape.png
Normal file
|
After Width: | Height: | Size: 228 B |
BIN
textures/tab_border_shape.png
Normal file
|
After Width: | Height: | Size: 245 B |
BIN
textures_rgt/button_bg_shape.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
textures_rgt/button_border_shape.png
Normal file
|
After Width: | Height: | Size: 320 B |
BIN
textures_rgt/container_bg_shape.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
textures_rgt/container_border_shape.png
Normal file
|
After Width: | Height: | Size: 477 B |
BIN
textures_rgt/field_bg_shape.png
Normal file
|
After Width: | Height: | Size: 224 B |
BIN
textures_rgt/field_border_shape.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
textures_rgt/scrollbar_thumb.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
textures_rgt/scrollbar_track.png
Normal file
|
After Width: | Height: | Size: 285 B |
284
theme.lua
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
|
||||||
|
theme = {
|
||||||
|
styles = {
|
||||||
|
bg_color = "#151618",
|
||||||
|
text_color = "#aaa",
|
||||||
|
text_color_muted = "#777",
|
||||||
|
heading_color = "#ddb969",
|
||||||
|
link_color = "#68b",
|
||||||
|
link_color_hovered = "#9be",
|
||||||
|
code_color = "#da8",
|
||||||
|
|
||||||
|
syntax_keyword = "#d8d",
|
||||||
|
syntax_keyword_value = "#79e",
|
||||||
|
syntax_identifier = "#adf",
|
||||||
|
syntax_operator = "#f66",
|
||||||
|
syntax_string = "#da8",
|
||||||
|
syntax_number = "#cdb",
|
||||||
|
syntax_comment = "#777",
|
||||||
|
syntax_placeholder = "#ccc",
|
||||||
|
|
||||||
|
container = {
|
||||||
|
border_color = "#292d2fff",
|
||||||
|
border_shape = menupath.."textures/container_border_shape.png",
|
||||||
|
bg_color = "#403e39ff",
|
||||||
|
bg_color_alt = "#373530ff",
|
||||||
|
bg_shape = menupath.."textures/container_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
field = {
|
||||||
|
border_color = "#263038ff",
|
||||||
|
border_shape = menupath.."textures/field_border_shape.png",
|
||||||
|
bg_color = "#393f40",
|
||||||
|
bg_shape = menupath.."textures/field_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
button = {
|
||||||
|
text_color = "#ccc",
|
||||||
|
border_color = "#292d2fff",
|
||||||
|
border_color_hovered = "#252829ff",
|
||||||
|
border_color_active = "#252829ff",
|
||||||
|
border_shape = menupath.."textures/button_border_shape.png",
|
||||||
|
bg_color = "#403e39ff",
|
||||||
|
bg_color_hovered = "#373633ff",
|
||||||
|
bg_color_active = "#373633ff",
|
||||||
|
bg_shape = menupath.."textures/button_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
tab = {
|
||||||
|
text_color = "#ccc",
|
||||||
|
border_color = "#292d2fff",
|
||||||
|
border_color_hovered = "#252829ff",
|
||||||
|
border_color_active = "#252829ff",
|
||||||
|
border_shape = menupath.."textures/tab_border_shape.png",
|
||||||
|
bg_color = "#403e39ff",
|
||||||
|
bg_color_hovered = "#373633ff",
|
||||||
|
bg_color_active = "#373633ff",
|
||||||
|
bg_shape = menupath.."textures/tab_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
checkbox = {
|
||||||
|
border_color = "#252829ff",
|
||||||
|
border_shape = menupath.."textures/checkbox_border_shape.png",
|
||||||
|
bg_color = "#3d464dff",
|
||||||
|
bg_shape = menupath.."textures/checkbox_bg_shape.png",
|
||||||
|
check_color = "#252829ff",
|
||||||
|
check_shape = menupath.."textures/checkbox_check_shape.png",
|
||||||
|
},
|
||||||
|
scrollbar = {
|
||||||
|
border_color = "#292d2fff",
|
||||||
|
border_shape = menupath.."textures/scrollbar_border_shape.png",
|
||||||
|
bg_color = "#1c2023ff",
|
||||||
|
bg_shape = menupath.."textures/scrollbar_bg_shape.png",
|
||||||
|
thumb_color = "#fff",
|
||||||
|
thumb_shape = menupath.."textures/scrollbar_thumb_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
|
||||||
|
icons = {
|
||||||
|
menu_content = {
|
||||||
|
{shape = menupath.."textures/menu_content.png", color = "#fff"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ RGT theme.
|
||||||
|
theme = {
|
||||||
|
styles = {
|
||||||
|
bg_color = "#151618",
|
||||||
|
text_color = "#aaa",
|
||||||
|
text_color_muted = "#777",
|
||||||
|
heading_color = "#ddb969",
|
||||||
|
link_color = "#68b",
|
||||||
|
link_color_hovered = "#9be",
|
||||||
|
code_color = "#da8",
|
||||||
|
|
||||||
|
syntax_keyword = "#d8d",
|
||||||
|
syntax_keyword_value = "#79e",
|
||||||
|
syntax_identifier = "#adf",
|
||||||
|
syntax_operator = "#f66",
|
||||||
|
syntax_string = "#da8",
|
||||||
|
syntax_number = "#cdb",
|
||||||
|
syntax_comment = "#777",
|
||||||
|
syntax_placeholder = "#ccc",
|
||||||
|
|
||||||
|
container = {
|
||||||
|
border_color = "#684f1dff",--"#292d2fff",
|
||||||
|
border_shape = menupath.."textures/container_border_shape.png",
|
||||||
|
bg_color = "#1c2023ff", --"#403e39ff",
|
||||||
|
bg_color_alt = "#22272aff",--"#373530ff",
|
||||||
|
bg_shape = menupath.."textures/container_bg_shape.png",
|
||||||
|
border_width = "16",
|
||||||
|
},
|
||||||
|
field = {
|
||||||
|
border_color = "#4d4d4dff",
|
||||||
|
border_shape = menupath.."textures/field_border_shape.png",
|
||||||
|
bg_color = "#5c5c5c",
|
||||||
|
bg_shape = menupath.."textures/field_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
button = {
|
||||||
|
text_color = "#ccc",
|
||||||
|
border_color = "#4d4d4dff",
|
||||||
|
border_color_hovered = "#3c3c3cff",
|
||||||
|
border_color_active = "#2b2b2bff",
|
||||||
|
border_shape = menupath.."textures/button_border_shape.png",
|
||||||
|
bg_color = "#5c5c5c",
|
||||||
|
bg_color_hovered = "#4b4b4bff",
|
||||||
|
bg_color_active = "#3a3a3aff",
|
||||||
|
bg_shape = menupath.."textures/button_bg_shape.png",
|
||||||
|
border_width = "8",
|
||||||
|
},
|
||||||
|
icons = {
|
||||||
|
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
function theme.get_background_image(bg, state)
|
||||||
|
local bg_color
|
||||||
|
local border_color
|
||||||
|
|
||||||
|
if state == "hovered" then
|
||||||
|
bg_color = theme.styles[bg].bg_color_hovered
|
||||||
|
border_color = theme.styles[bg].border_color_hovered
|
||||||
|
elseif state == "active" then
|
||||||
|
bg_color = theme.styles[bg].bg_color_active
|
||||||
|
border_color = theme.styles[bg].border_color_active
|
||||||
|
else
|
||||||
|
bg_color = theme.styles[bg].bg_color
|
||||||
|
border_color = theme.styles[bg].border_color
|
||||||
|
end
|
||||||
|
|
||||||
|
return "("..theme.styles[bg].bg_shape.."^[multiply:"..bg_color..")^("..theme.styles[bg].border_shape.."^[multiply:"..border_color..")", theme.styles[bg].border_width
|
||||||
|
end
|
||||||
|
|
||||||
|
function theme.get_image(element, prop)
|
||||||
|
return "("..theme.styles[element][prop.."_shape"].."^[multiply:"..theme.styles[element][prop.."_color"]..")"
|
||||||
|
end
|
||||||
|
|
||||||
|
local assets = os.getenv("HOME").."/eclipse-workspace/mods/mtmenu/"
|
||||||
|
function theme.icon(name)
|
||||||
|
local icon = theme.styles.icons[name]
|
||||||
|
if icon then
|
||||||
|
if type(icon) == "table" then
|
||||||
|
local out = {}
|
||||||
|
for i = 1, #icon do
|
||||||
|
out[#out + 1] = "("..icon[i].shape.."^[multiply:"..icon[i].color..")"
|
||||||
|
end
|
||||||
|
return table.concat(out, "^")
|
||||||
|
else
|
||||||
|
return icon
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = os.getenv("HOME").."/eclipse-workspace/mtmenu/textures/"..name..".png"
|
||||||
|
return file_exists(path) and path or assets..name..".png"
|
||||||
|
end
|
||||||
|
|
||||||
|
function theme.field(x, y, w, h, label, value)
|
||||||
|
if value then
|
||||||
|
h = h + 0.3
|
||||||
|
end
|
||||||
|
imfs.group(x, y, w, h)
|
||||||
|
if value then
|
||||||
|
imfs.label(0.2, 0.125, label)
|
||||||
|
end
|
||||||
|
imfs.image(0, value and 0.3 or 0, "100%", value and "100% - 0.3" or "100%", theme.get_background_image("field"), theme.styles.field.border_width)
|
||||||
|
local field = imfs.field(0.1, value and 0.3 or 0, "100% - 0.2", value and "100% - 0.3" or "100%", value or label)
|
||||||
|
imfs.group_end()
|
||||||
|
return field
|
||||||
|
end
|
||||||
|
|
||||||
|
function theme.number_field(x, y, w, h, value, step)
|
||||||
|
step = step or 1
|
||||||
|
imfs.group(x, y, w, h)
|
||||||
|
imfs.button(0, "50% - 0.25", 0.5, 0.5, "-")
|
||||||
|
:onclick(function()
|
||||||
|
value(tonumber(value()) - step)
|
||||||
|
end)
|
||||||
|
imfs.image(0.6, 0, "100% - 1.2", "100%", theme.get_background_image("field"), theme.styles.field.border_width)
|
||||||
|
local field = imfs.field(0.7, 0, "100% - 1.4", "100%", value())
|
||||||
|
imfs.button("100% - 0.5", "50% - 0.25", 0.5, 0.5, "+")
|
||||||
|
:onclick(function()
|
||||||
|
value(tonumber(value()) + step)
|
||||||
|
end)
|
||||||
|
imfs.group_end()
|
||||||
|
return field
|
||||||
|
end
|
||||||
|
|
||||||
|
function theme.checkbox(x, y, label, value, tooltip)
|
||||||
|
imfs.group(x, y, 5, 0.75)
|
||||||
|
local box
|
||||||
|
box = imfs.button(0, 0, 0.75, "100%")
|
||||||
|
:image(theme.get_background_image("checkbox")..(value() and "^"..theme.get_image("checkbox", "check") or ""))
|
||||||
|
:onclick(function()
|
||||||
|
value(not value())
|
||||||
|
if box._onchange then
|
||||||
|
box._onchange(value._val)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
:tooltip(tooltip, not not tooltip)
|
||||||
|
box.onchange = function(e, fn)
|
||||||
|
e._onchange = fn
|
||||||
|
end
|
||||||
|
imfs.label(0.85, 0.375, label)
|
||||||
|
imfs.group_end()
|
||||||
|
return box
|
||||||
|
end
|
||||||
|
|
||||||
|
local open_dropdown = imfs.state()
|
||||||
|
function theme.dropdown(x, y, w, h, options, value)
|
||||||
|
local id = imfs._new_id()
|
||||||
|
local el
|
||||||
|
local open = open_dropdown() == id
|
||||||
|
local border_width = pixel_to_formspec(theme.styles.container.border_width)
|
||||||
|
local box_height = math.min(#options, 5) * 0.5
|
||||||
|
imfs.group(x, y, w, open and h + box_height or h)
|
||||||
|
imfs.group(0, 0, "100%", h)
|
||||||
|
el = imfs.button(0, 0, "100%", "100%", value())
|
||||||
|
:onclick(function()
|
||||||
|
if open_dropdown() == id then
|
||||||
|
open_dropdown(false)
|
||||||
|
else
|
||||||
|
open_dropdown(id)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.label("100% - 0.6", "50%", open and "^" or "v")
|
||||||
|
imfs.group_end()
|
||||||
|
|
||||||
|
if open then
|
||||||
|
imfs.image(0, h, "100%", box_height + border_width * 2, theme.get_background_image("container"))
|
||||||
|
imfs.scroll_container(border_width, h + border_width, "100% - "..(border_width * 2), box_height)
|
||||||
|
for i = 0, #options - 1 do
|
||||||
|
local option = options[i + 1]
|
||||||
|
imfs.button(0, i * 0.5, "100%", 0.5, option)
|
||||||
|
:style({
|
||||||
|
border = false,
|
||||||
|
bgimg = assets.."white.png",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
bgcolor = i % 2 == 1 and theme.styles.container.bg_color or theme.styles.container.bg_color_alt,
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
border = false,
|
||||||
|
bgimg = assets.."white.png",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
border = false,
|
||||||
|
bgimg = assets.."white.png",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:onclick(function()
|
||||||
|
value(option)
|
||||||
|
open_dropdown(false)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
end
|
||||||
|
imfs.group_end()
|
||||||
|
return el
|
||||||
|
end
|
||||||
|
|
||||||
87
views/about.lua
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
|
||||||
|
local function get_credits()
|
||||||
|
local f = assert(io.open(core.get_mainmenu_path() .. "/credits.json"))
|
||||||
|
local json = core.parse_json(f:read("*all"))
|
||||||
|
f:close()
|
||||||
|
return json
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_renderer_info()
|
||||||
|
local out = {}
|
||||||
|
|
||||||
|
local renderer = core.get_active_renderer()
|
||||||
|
if renderer:sub(1, 7) == "OpenGL " then
|
||||||
|
renderer = renderer:sub(8)
|
||||||
|
end
|
||||||
|
local m = renderer:match("^[%d.]+")
|
||||||
|
if not m then
|
||||||
|
m = renderer:match("^ES [%d.]+")
|
||||||
|
end
|
||||||
|
out[#out + 1] = m or renderer
|
||||||
|
out[#out + 1] = core.get_active_driver():lower()
|
||||||
|
out[#out + 1] = core.get_active_irrlicht_device():upper()
|
||||||
|
|
||||||
|
return table.concat(out, " / ")
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
if not state.credits then
|
||||||
|
local credits = get_credits()
|
||||||
|
state.credits = table.concat({
|
||||||
|
"<global halign=center color=", theme.styles.text_color, ">",
|
||||||
|
"<tag name=muted color=", theme.styles.text_color_muted, ">",
|
||||||
|
"<tag name=link color=", theme.styles.link_color, ">",
|
||||||
|
"<tag name=heading color=", theme.styles.heading_color, " size=24>",
|
||||||
|
"<heading>", fgettext_ne("Core Developers"), "</heading>\n",
|
||||||
|
core.hypertext_escape(table.concat(credits.core_developers, "\n")),
|
||||||
|
"\n\n<heading>", fgettext_ne("Core Team"), "</heading>\n",
|
||||||
|
core.hypertext_escape(table.concat(credits.core_team, "\n")),
|
||||||
|
"\n\n<heading>", fgettext_ne("Active Contributors"), "</heading>\n",
|
||||||
|
core.hypertext_escape(table.concat(credits.contributors, "\n")),
|
||||||
|
"\n\n<heading>", fgettext_ne("Previous Core Developers"), "</heading>\n",
|
||||||
|
core.hypertext_escape(table.concat(credits.previous_core_developers, "\n")),
|
||||||
|
"\n\n<heading>", fgettext_ne("Previous Contributors"), "</heading>\n",
|
||||||
|
core.hypertext_escape(table.concat(credits.previous_contributors, "\n")),
|
||||||
|
"\n\n",
|
||||||
|
}):gsub("%[.-%]", "<muted>%1</muted>"):gsub("\\<.-\\>", "<link>%1</link>")
|
||||||
|
end
|
||||||
|
|
||||||
|
local header_height = size.x * (72/672)
|
||||||
|
imfs.image(0, 0.2, size.x, header_height, default_textures.."menu_header.png")
|
||||||
|
|
||||||
|
imfs.hypertext(0, header_height + 0.3, "100%", 0.5, "<global halign=center valign=middle color="..theme.styles.text_color.."><b>Version "..version.string.."</b>")
|
||||||
|
|
||||||
|
imfs.hypertext(0, header_height + 0.9, "100%", "100% - "..(header_height + 0.9), state.credits)
|
||||||
|
|
||||||
|
imfs.button("100% - 2.2", 0.2, 2, 0.75, "Back")
|
||||||
|
:onclick(function()
|
||||||
|
show_meta_menu()
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0.2, 0.2, 3, 0.75, "API reference")
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0.2, 1.05, 3, 0.75, "Website")
|
||||||
|
:onclick(function()
|
||||||
|
core.open_url("https://luanti.org")
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0.2, 1.9, 3, 0.75, "Open data directory")
|
||||||
|
:onclick(function()
|
||||||
|
core.open_dir(core.get_user_path())
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.box(0, "100% - 0.45", 6, 0.45, theme.styles.bg_color)
|
||||||
|
imfs.label(0.2, "100% - 0.225", fgettext_ne("Active renderer:").." "..get_renderer_info())
|
||||||
|
|
||||||
|
local offset = math.abs(size.x - size.y)
|
||||||
|
imfs.image(size.x > size.y and offset / 2 or 0, size.x < size.y and offset / 2 or 0, math.min(size.x, size.y), math.min(size.x, size.y), default_textures.."logo.png^[opacity:32")
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
236
views/content.lua
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
|
||||||
|
local function search_content(state, search)
|
||||||
|
local out = {}
|
||||||
|
|
||||||
|
for i = 1, #state.content do
|
||||||
|
local pkg = state.content[i]
|
||||||
|
if pkg.name:find(search, 1, true) then
|
||||||
|
out[#out + 1] = pkg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
if not state.content then
|
||||||
|
state.content = package.all()
|
||||||
|
state.content_shown._val = state.content
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_package = state.current_package()
|
||||||
|
|
||||||
|
imfs.box(0, 0, "100%", current_package and 1 or 2, theme.styles.container.bg_color)
|
||||||
|
imfs.row(0.2, 0.125, "100% - 0.4", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
theme.field(0, 0, "1x", "100%", state.search)
|
||||||
|
:onchange(function(value)
|
||||||
|
state.search = value
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
state.page(1)
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("search"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Search content")
|
||||||
|
:onclick(function()
|
||||||
|
state.page(1)
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("cancel"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Clear search")
|
||||||
|
:onclick(function()
|
||||||
|
state.search = ""
|
||||||
|
state.page(1)
|
||||||
|
state.content_shown(state.content)
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 3.5, 0.75, "Check for updates")
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 2, 0.75, "Back")
|
||||||
|
:onclick(function()
|
||||||
|
if current_package then
|
||||||
|
state.current_package(false)
|
||||||
|
else
|
||||||
|
show_meta_menu()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.box(0, 0.95, "100%", 0.1, theme.styles.container.border_color)
|
||||||
|
|
||||||
|
if current_package then
|
||||||
|
imfs.hypertext(0.25, 1.05, "100% - 0.5", "100% - 1.05", "\
|
||||||
|
<global color="..theme.styles.text_color..">\
|
||||||
|
<tag name=action color="..theme.styles.link_color.." hovercolor="..theme.styles.link_color_hovered..">\
|
||||||
|
"..markdown_to_hypertext(read_file(current_package.path.."/README.md") or ""))
|
||||||
|
else
|
||||||
|
imfs.row(0.2, 1.125, "100% - 0.4", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
|
||||||
|
imfs.group(0, 0, "1x", 0.75)
|
||||||
|
imfs.image(0, 0, "100%", "100%", theme.get_background_image("button"), "8,8")
|
||||||
|
imfs.row(0.15, 0.05, "100% - 0.3", "100% - 0.13")
|
||||||
|
local show_type = state.show_type()
|
||||||
|
imfs.button(0, 0, "1x", "100%", "All")
|
||||||
|
:style(show_type == "all" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "all" then
|
||||||
|
state.show_type("all")
|
||||||
|
state.page(1)
|
||||||
|
state.content = package.all()
|
||||||
|
if state.search ~= "" then
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
else
|
||||||
|
state.content_shown(state.content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Games")
|
||||||
|
:style(show_type == "games" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "games" then
|
||||||
|
state.show_type("games")
|
||||||
|
state.page(1)
|
||||||
|
state.content = package.games()
|
||||||
|
if state.search ~= "" then
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
else
|
||||||
|
state.content_shown(state.content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Mods")
|
||||||
|
:style(show_type == "mods" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "mods" then
|
||||||
|
state.show_type("mods")
|
||||||
|
state.page(1)
|
||||||
|
state.content = package.mods()
|
||||||
|
if state.search ~= "" then
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
else
|
||||||
|
state.content_shown(state.content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Texture Packs")
|
||||||
|
:style(show_type == "texturepacks" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "texturepacks" then
|
||||||
|
state.show_type("texturepacks")
|
||||||
|
state.page(1)
|
||||||
|
state.content = package.texturepacks()
|
||||||
|
if state.search ~= "" then
|
||||||
|
state.content_shown(search_content(state, state.search))
|
||||||
|
else
|
||||||
|
state.content_shown(state.content)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.group_end()
|
||||||
|
|
||||||
|
imfs.button(0, 0, 3.5, 0.75, "Browse online content...")
|
||||||
|
:onclick(function()
|
||||||
|
show_online_content_menu()
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 2.5, 0.75, "New package...")
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
|
||||||
|
imfs.box(0, 1.95, "100%", 0.1, theme.styles.container.border_color)
|
||||||
|
|
||||||
|
local content = state.content_shown()
|
||||||
|
if #content == 0 then
|
||||||
|
imfs.hypertext(0, 2.05, "100%", 2, [[
|
||||||
|
<global halign=center valign=middle color=#444>
|
||||||
|
<style size=36><b>No results.</b></style>
|
||||||
|
]])
|
||||||
|
else
|
||||||
|
local num_per_page = math.floor(size.x / 3) * math.floor((size.y - 2) / 3)
|
||||||
|
local pages = math.ceil(#content / num_per_page)
|
||||||
|
imfs.scroll_container(0, 2.05, "100%", "100% - 2.55", "horizontal", 0, "")
|
||||||
|
:scrollbar(function()
|
||||||
|
imfs.scrollbar(-800, -800, 0, 0, "horizontal", state.page)
|
||||||
|
:options({
|
||||||
|
min = 1,
|
||||||
|
max = pages,
|
||||||
|
smallstep = 1
|
||||||
|
})
|
||||||
|
:onchange(function(action, value)
|
||||||
|
if action == "CHG" then
|
||||||
|
state.page(value)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
local x = 0
|
||||||
|
local y = 0
|
||||||
|
local start = (state.page() - 1) * num_per_page
|
||||||
|
for i = start + 1, start + num_per_page do
|
||||||
|
local pkg = content[i]
|
||||||
|
if not pkg then break end
|
||||||
|
if not pkg.icon then
|
||||||
|
pkg.icon = pkg.type == "game" and pkg.path.."/menu/icon.png" or pkg.path.."/icon.png"
|
||||||
|
if not file_exists(pkg.icon) then
|
||||||
|
pkg.icon = theme.icon("menu_content")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.button(x + 0.125, y, 2.5, 2.5)
|
||||||
|
:image(pkg.icon)
|
||||||
|
:tooltip(pkg.title ~= "" and pkg.title or pkg.name)
|
||||||
|
:onclick(function()
|
||||||
|
state.current_package(pkg)
|
||||||
|
end)
|
||||||
|
x = x + 3
|
||||||
|
if x + 2.5 > size.x then
|
||||||
|
x = 0
|
||||||
|
y = y + 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
imfs.row(0, "100% - 0.375", "100%", 0.25)
|
||||||
|
:align("center")
|
||||||
|
for i = 1, pages do
|
||||||
|
imfs.button(0, 0, 0.25, 0.25)
|
||||||
|
:image(theme.icon(i == state.page() and "circle_active" or "circle"))
|
||||||
|
:onclick(function()
|
||||||
|
state.page(i)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
imfs.row_end()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
8
views/docs.lua
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
0
views/game.lua
Normal file
129
views/meta_menu.lua
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor("#151618", true)
|
||||||
|
|
||||||
|
imfs.image(0, size.y * 0.08, size.x, size.x * (72/672), default_textures.."menu_header.png")
|
||||||
|
|
||||||
|
local games = state.games
|
||||||
|
|
||||||
|
if #games < 1 then
|
||||||
|
imfs.hypertext(0, 0, size.x, size.y, [[
|
||||||
|
<global valign=middle halign=center>
|
||||||
|
<tag name=action color=#222 hovercolor=#444>
|
||||||
|
<style size=72 color=#222><b>No games installed.</b></style>
|
||||||
|
<style size=36 color=#222>
|
||||||
|
<action name=content>Click here to install a game.</action>
|
||||||
|
</style>
|
||||||
|
]])
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.scroll_container(0, 0, size.x, size.y, "horizontal", 0, "")
|
||||||
|
:scrollbar(function()
|
||||||
|
imfs.scrollbar(-800, -800, 0, 0, "horizontal", state.scroll_pos)
|
||||||
|
:options({
|
||||||
|
min = 1,
|
||||||
|
max = #games,
|
||||||
|
smallstep = 1,
|
||||||
|
})
|
||||||
|
:onchange(function(action, value)
|
||||||
|
if action == "CHG" then
|
||||||
|
state.scroll_pos(value)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local center = size.x / 2
|
||||||
|
|
||||||
|
for i, x in ipairs(games) do
|
||||||
|
imfs.button(center + (i - #games / 2) * 0.25, size.y - 0.5, 0.25, 0.25)
|
||||||
|
:image(theme.icon(i == state.scroll_pos() and "circle_active" or "circle"))
|
||||||
|
:onclick(function()
|
||||||
|
state.scroll_pos(i)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local dist = i - state.scroll_pos()
|
||||||
|
local scale = 4 / (math.abs(dist) + 1)
|
||||||
|
if scale > 0.1 then
|
||||||
|
--This looks neat, but is useless for UI purposes: center +10^(1 /math.abs(dist)) *math.sign(dist)
|
||||||
|
local lc = center + math.abs(dist * 10) ^ 0.55 * math.sign(dist)
|
||||||
|
if x.menuicon_path == "" then
|
||||||
|
x.menuicon_path = theme.icon("games")
|
||||||
|
end
|
||||||
|
--TODO: Do these need some kind of background?
|
||||||
|
imfs.button(lc - scale /2, size.y / 2 - scale / 2 + (4 - scale) / 2 + 2, scale, scale)
|
||||||
|
:image(x.menuicon_path)
|
||||||
|
:tooltip(x.title, "#444", "#aaa")
|
||||||
|
if dist == 0 then
|
||||||
|
imfs.button(lc - scale / 2, size.y / 2 + 4, 4, 0.5, x.title)
|
||||||
|
:styles(styles_no_background)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
imfs.button("5%", "30%", 4, 4)
|
||||||
|
:style({
|
||||||
|
border = false,
|
||||||
|
bgimg = theme.icon("menu_servers"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
bgimg = theme.icon("menu_servers_hovered"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgimg = theme.icon("menu_servers_hovered"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:onclick(show_servers_menu)
|
||||||
|
imfs.button("5%", size.y * 0.3 + 4, 4, 0.5, "Servers")
|
||||||
|
:styles(styles_no_background)
|
||||||
|
|
||||||
|
imfs.button(size.x * 0.95 - 4, "30%", 4, 4)
|
||||||
|
:style({
|
||||||
|
border = false,
|
||||||
|
bgimg = theme.icon("menu_content"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
bgimg = theme.icon("menu_content_hovered"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgimg = theme.icon("menu_content_hovered"),
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:onclick(show_content_menu)
|
||||||
|
imfs.button(size.x * 0.95 - 4, size.y * 0.3 + 4, 4, 0.5, "Content")
|
||||||
|
:styles(styles_no_background)
|
||||||
|
|
||||||
|
imfs.button(1, 0, 2, 0.5, "About")
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("tab")
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
bgimg = theme.get_background_image("tab", "hovered")
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgimg = theme.get_background_image("tab", "active"),
|
||||||
|
})
|
||||||
|
:onclick(show_about_menu)
|
||||||
|
|
||||||
|
imfs.button(size.x - 3, 0, 2, 0.5, "Settings")
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("tab"),
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
bgimg = theme.get_background_image("tab", "hovered"),
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgimg = theme.get_background_image("tab", "active"),
|
||||||
|
})
|
||||||
|
:onclick(show_settings_menu)
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
231
views/online_content.lua
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
|
||||||
|
local function fetch_content()
|
||||||
|
local url = core.settings:get("contentdb_url")
|
||||||
|
.."/api/packages/?type=mod&type=game&type=txp&protocol_version="
|
||||||
|
..core.get_max_supp_proto().."&engine_version="
|
||||||
|
..core.urlencode(core.get_version().string)
|
||||||
|
for _, item in pairs(core.settings:get("contentdb_flag_blacklist"):split(",")) do
|
||||||
|
item = item:trim()
|
||||||
|
if item ~= "" then
|
||||||
|
url = url .. "&hide=" .. core.urlencode(item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local http = core.get_http_api()
|
||||||
|
local response = http.fetch_sync({
|
||||||
|
url = url,
|
||||||
|
extra_headers = {
|
||||||
|
core.get_http_accept_languages()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return core.parse_json(response.data)
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
imfs.box(0, 0, "100%", 1, theme.styles.container.bg_color)
|
||||||
|
imfs.row(0.2, 0.125, "100% - 0.4", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
|
||||||
|
imfs.group(0, 0, "1.25x", 0.75)
|
||||||
|
imfs.image(0, 0, "100%", "100%", theme.get_background_image("button"))
|
||||||
|
imfs.row(0.15, 0.05, "100% - 0.3", "100% - 0.13")
|
||||||
|
local show_type = state.show_type()
|
||||||
|
imfs.button(0, 0, "1x", "100%", "All")
|
||||||
|
:style(show_type == "all" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "all" then
|
||||||
|
state.show_type("all")
|
||||||
|
state.page(1)
|
||||||
|
-- state.content = package.all()
|
||||||
|
-- if state.search ~= "" then
|
||||||
|
-- state.content_shown(search_content(state, state.search))
|
||||||
|
-- else
|
||||||
|
-- state.content_shown(state.content)
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Games")
|
||||||
|
:style(show_type == "games" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "games" then
|
||||||
|
state.show_type("games")
|
||||||
|
state.page(1)
|
||||||
|
-- state.content = package.games()
|
||||||
|
-- if state.search ~= "" then
|
||||||
|
-- state.content_shown(search_content(state, state.search))
|
||||||
|
-- else
|
||||||
|
-- state.content_shown(state.content)
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Mods")
|
||||||
|
:style(show_type == "mods" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "mods" then
|
||||||
|
state.show_type("mods")
|
||||||
|
state.page(1)
|
||||||
|
-- state.content = package.mods()
|
||||||
|
-- if state.search ~= "" then
|
||||||
|
-- state.content_shown(search_content(state, state.search))
|
||||||
|
-- else
|
||||||
|
-- state.content_shown(state.content)
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.box(0, 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Texture Packs")
|
||||||
|
:style(show_type == "texturepacks" and style_borderless_alt or style_borderless)
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
:onclick(function()
|
||||||
|
if show_type ~= "texturepacks" then
|
||||||
|
state.show_type("texturepacks")
|
||||||
|
state.page(1)
|
||||||
|
-- state.content = package.texturepacks()
|
||||||
|
-- if state.search ~= "" then
|
||||||
|
-- state.content_shown(search_content(state, state.search))
|
||||||
|
-- else
|
||||||
|
-- state.content_shown(state.content)
|
||||||
|
-- end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.group_end()
|
||||||
|
|
||||||
|
theme.field(0, 0, "1x", "100%")
|
||||||
|
:onchange(function(value)
|
||||||
|
state.search = value
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("search"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Search content")
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("cancel"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Clear search")
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 2, "100%", "Back")
|
||||||
|
:onclick(function()
|
||||||
|
show_content_menu()
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.box(0, 0.95, "100%", 0.1, theme.styles.container.border_color)
|
||||||
|
|
||||||
|
if not state.content_shown() then
|
||||||
|
imfs.row(0, "50% - 0.25", "100%", 0.5)
|
||||||
|
:align("center")
|
||||||
|
|
||||||
|
imfs.image(0, 0, 1, 0.5, theme.icon("loader"))
|
||||||
|
:animated(4, 200)
|
||||||
|
imfs.row_end()
|
||||||
|
if not state.loading then
|
||||||
|
core.handle_async(fetch_content, nil, function(content)
|
||||||
|
state.content = content
|
||||||
|
state.num_content = #content
|
||||||
|
state.content_shown(state.content)
|
||||||
|
state.loading = false
|
||||||
|
end)
|
||||||
|
state.loading = true
|
||||||
|
end
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
|
|
||||||
|
local content = state.content_shown()
|
||||||
|
local num_per_page = math.floor((size.x + 0.5) / 5.5) * math.floor((size.y - 1.05) / 3.5)
|
||||||
|
local excess_x = (size.x + 0.5) % 5.5
|
||||||
|
local excess_y = (size.y - 1.05) % 3.5
|
||||||
|
imfs.scroll_container(0, 1.05, "100%", "100% - 1.55", "horizontal", 0, "")
|
||||||
|
:scrollbar(function()
|
||||||
|
imfs.scrollbar(0, size.y - 0.5, size.x, 0.5, "horizontal", state.page())
|
||||||
|
:options({
|
||||||
|
min = 1,
|
||||||
|
max = math.ceil(state.num_content / num_per_page),
|
||||||
|
smallstep = 1
|
||||||
|
})
|
||||||
|
:onchange(function(action, value)
|
||||||
|
if action == "CHG" then
|
||||||
|
state.page(value)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local x = 0
|
||||||
|
local y = 0
|
||||||
|
local start = (state.page() - 1) * num_per_page
|
||||||
|
local i = start
|
||||||
|
local idx = start
|
||||||
|
while idx < start + num_per_page do
|
||||||
|
i = i + 1
|
||||||
|
local pkg = content[i]
|
||||||
|
if not pkg then break end
|
||||||
|
|
||||||
|
if not package.is_installed(pkg.type, pkg.name) then
|
||||||
|
idx = idx + 1
|
||||||
|
|
||||||
|
imfs.group(x + excess_x / 2, y + excess_y / 2, 5, 3)
|
||||||
|
imfs.image(0, 0, "100%", "100%", package.screenshot(pkg))
|
||||||
|
imfs.box(0, 0, "100%", "100%", "#000a")
|
||||||
|
imfs.hypertext(0.1, 0.1, "100% - 0.2", "100% - 0.2", string.format(
|
||||||
|
[[<global color=#fff><style size=24><b>%s</b></style>
|
||||||
|
<b> by %s</b>
|
||||||
|
%s]],
|
||||||
|
core.hypertext_escape(pkg.title or pkg.name),
|
||||||
|
pkg.author,
|
||||||
|
pkg.short_description
|
||||||
|
))
|
||||||
|
:style(".scrollbar", {
|
||||||
|
size = "0"
|
||||||
|
})
|
||||||
|
imfs.button(0, 0, "100%", "100%")
|
||||||
|
:styles(styles_no_background)
|
||||||
|
:style("hovered", {
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgcolor = "#0004"
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgcolor = "#0004"
|
||||||
|
})
|
||||||
|
:onclick(function()
|
||||||
|
|
||||||
|
end)
|
||||||
|
imfs.group_end()
|
||||||
|
x = x + 5.5
|
||||||
|
if x + 5 > size.x then
|
||||||
|
x = 0
|
||||||
|
y = y + 3.5
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
562
views/servers.lua
Normal file
|
|
@ -0,0 +1,562 @@
|
||||||
|
|
||||||
|
local function add_favorite_server(address, port)
|
||||||
|
state.favorite_servers[#state.favorite_servers +1] = {address = address, port = port}
|
||||||
|
local f = io.open(minetest.get_user_path().."/client/serverlist/favoriteservers.json", "w")
|
||||||
|
f:write(minetest.write_json(state.favorite_servers))
|
||||||
|
f:flush()
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove_favorite_server(address, port)
|
||||||
|
local idx
|
||||||
|
for i, x in pairs(state.favorite_servers) do
|
||||||
|
if x.address == address and x.port == port then
|
||||||
|
idx = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if idx then
|
||||||
|
table.remove(state.favorite_servers, idx)
|
||||||
|
local f = io.open(minetest.get_user_path().."/client/serverlist/favoriteservers.json", "w")
|
||||||
|
f:write(minetest.write_json(state.favorite_servers))
|
||||||
|
f:flush()
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function search_server_list(state, input)
|
||||||
|
if input:trim() == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local search_str = ""
|
||||||
|
local words = {}
|
||||||
|
local mods = {}
|
||||||
|
local game
|
||||||
|
local players = {}
|
||||||
|
|
||||||
|
input = input:lower()
|
||||||
|
|
||||||
|
for x in input:gmatch("%S+") do
|
||||||
|
if x:sub(1, 4) == "mod:" then
|
||||||
|
mods[#mods + 1] = x:sub(5)
|
||||||
|
elseif x:sub(1, 7) == "player:" then
|
||||||
|
players[#players + 1] = x:sub(8)
|
||||||
|
elseif x:sub(1, 5) == "game:" then
|
||||||
|
game = x:sub(6)
|
||||||
|
else
|
||||||
|
words[#words + 1] = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
for _, x in ipairs(state.servers) do
|
||||||
|
local passed = true
|
||||||
|
|
||||||
|
for _, a in ipairs(words) do
|
||||||
|
if not (x.description and x.description:lower():find(a, 1, true) or x.name and x.name:lower():find(a, 1, true)) then
|
||||||
|
passed = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- PUC Lua doesn't have `continue`, hence the indentation.
|
||||||
|
if passed then
|
||||||
|
if game and x.gameid and game ~= x.gameid:lower():trim() then
|
||||||
|
passed = false
|
||||||
|
end
|
||||||
|
|
||||||
|
if passed then
|
||||||
|
if x.mods and #mods > 0 then
|
||||||
|
local found = 0
|
||||||
|
for _, a in ipairs(x.mods) do
|
||||||
|
for _, b in ipairs(mods) do
|
||||||
|
if a == b then
|
||||||
|
found = found + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
passed = found == #mods
|
||||||
|
else
|
||||||
|
passed = not not x.mods
|
||||||
|
end
|
||||||
|
|
||||||
|
if passed then
|
||||||
|
if x.clients_list and #players > 0 then
|
||||||
|
local found = 0
|
||||||
|
for _, a in ipairs(x.clients_list) do
|
||||||
|
for _, b in ipairs(players) do
|
||||||
|
if a:lower():trim() == b then
|
||||||
|
found = found + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
passed = found == #players
|
||||||
|
else
|
||||||
|
passed = not not x.clients_list
|
||||||
|
end
|
||||||
|
|
||||||
|
if passed then
|
||||||
|
out[#out +1] = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refresh_server_list(state)
|
||||||
|
core.handle_async(
|
||||||
|
function(param)
|
||||||
|
local http = core.get_http_api()
|
||||||
|
local url = string.format("%s/list?proto_version_min=%d&proto_version_max=%d",
|
||||||
|
"https://servers.luanti.org" or core.settings:get("serverlist_url"),
|
||||||
|
core.get_min_supp_proto(),
|
||||||
|
core.get_max_supp_proto()
|
||||||
|
)
|
||||||
|
|
||||||
|
local response = http.fetch_sync({url = url})
|
||||||
|
if not response.succeeded then
|
||||||
|
return {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local retval = core.parse_json(response.data)
|
||||||
|
return retval and retval.list or {}
|
||||||
|
end,
|
||||||
|
nil,
|
||||||
|
function(result)
|
||||||
|
local list = table.copy(state.favorite_servers)
|
||||||
|
table.insert_all(list, result)
|
||||||
|
state.servers = list
|
||||||
|
if state.search ~= "" then
|
||||||
|
state.servers_filtered = search_server_list(state, state.search)
|
||||||
|
list = state.servers_filtered
|
||||||
|
end
|
||||||
|
state.serverlist(list)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function join_server(state)
|
||||||
|
core.settings:set("name", state.username())
|
||||||
|
local address
|
||||||
|
local port
|
||||||
|
if state.joining() == true then
|
||||||
|
if not tonumber(state.port()) then
|
||||||
|
state.join_error("Invalid port.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
address = state.address():lower()
|
||||||
|
port = state.port():lower()
|
||||||
|
else
|
||||||
|
address = state.joining().address:lower()
|
||||||
|
port = state.joining().port
|
||||||
|
end
|
||||||
|
local is_favorite = false
|
||||||
|
for _, x in ipairs(state.favorite_servers) do
|
||||||
|
if x.address == address and x.port == port then
|
||||||
|
is_favorite = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not is_favorite then
|
||||||
|
add_favorite_server(address, port)
|
||||||
|
end
|
||||||
|
core.settings:set("address", address)
|
||||||
|
core.settings:set("remote_port", port)
|
||||||
|
gamedata = {
|
||||||
|
playername = state.username(),
|
||||||
|
password = state.password(),
|
||||||
|
address = address,
|
||||||
|
port = port,
|
||||||
|
selected_world = 0,
|
||||||
|
singleplayer = false
|
||||||
|
}
|
||||||
|
|
||||||
|
core.start()
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
local loading
|
||||||
|
if not state.serverlist() then
|
||||||
|
loading = true
|
||||||
|
local favorite_servers = core.parse_json(read_file(core.get_user_path().."/client/serverlist/favoriteservers.json") or "{}")
|
||||||
|
for _, x in ipairs(favorite_servers) do
|
||||||
|
x.favorite = true
|
||||||
|
end
|
||||||
|
state.favorite_servers = favorite_servers
|
||||||
|
state.serverlist._val = favorite_servers
|
||||||
|
refresh_server_list(state)
|
||||||
|
end
|
||||||
|
|
||||||
|
local enable_tooltips = not (state.joining() or state.detail_view())
|
||||||
|
|
||||||
|
local list = state.serverlist()
|
||||||
|
|
||||||
|
imfs.row("10%", size.y * 0.1 - 0.85, "80%", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
|
||||||
|
theme.field(0, 0, "1x", "100%", state.search)
|
||||||
|
:onchange(function(value)
|
||||||
|
state.search = value
|
||||||
|
end)
|
||||||
|
:onenter(function(value)
|
||||||
|
state.search = value
|
||||||
|
state.servers_filtered = search_server_list(state, state.search)
|
||||||
|
state.serverlist(state.servers_filtered)
|
||||||
|
state.scroll_pos(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("search"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Search server list")
|
||||||
|
:onclick(function()
|
||||||
|
state.servers_filtered = search_server_list(state, state.search)
|
||||||
|
state.serverlist(state.servers_filtered)
|
||||||
|
state.scroll_pos(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("cancel"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Clear search")
|
||||||
|
:onclick(function()
|
||||||
|
state.search = ""
|
||||||
|
state.servers_filtered = nil
|
||||||
|
state.serverlist(state.servers)
|
||||||
|
state.scroll_pos(1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("refresh"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Refresh server list")
|
||||||
|
:onclick(function()
|
||||||
|
refresh_server_list(state)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 3, 0.75, "Direct Connection...")
|
||||||
|
:onclick(function()
|
||||||
|
state.joining(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.row_end()
|
||||||
|
|
||||||
|
local border_width = pixel_to_formspec(theme.styles.container.border_width)
|
||||||
|
|
||||||
|
imfs.image("10%", "10%", "80%", "80%", theme.get_background_image("container"))
|
||||||
|
imfs.group("10% + "..border_width, "10% + "..border_width, "80% - "..(border_width * 2), "80% - "..(border_width * 2))
|
||||||
|
|
||||||
|
imfs.scroll_container(0, 0, state.detail() and "65% - 0.05" or "100%", "100%", "vertical", 0, "")
|
||||||
|
:scrollbar(function()
|
||||||
|
imfs.scrollbar(state.detail() and "65% - 0.55" or "100% - 0.5", 0, 0.5, "100%", "vertical", state.scroll_pos)
|
||||||
|
:options({
|
||||||
|
min = 1,
|
||||||
|
max = #list,
|
||||||
|
smallstep = 1,
|
||||||
|
thumbsize = 0,
|
||||||
|
})
|
||||||
|
:onchange(function(action, value)
|
||||||
|
if action == "CHG" then
|
||||||
|
state.scroll_pos(value)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.scope("servers")
|
||||||
|
local height = size.y * 0.8
|
||||||
|
local i = 0
|
||||||
|
local idx = state.scroll_pos()
|
||||||
|
while true do
|
||||||
|
-- We must use a while loop here because we skip incompatible servers,
|
||||||
|
-- and we have no way of knowing how many there will be until we iterate.
|
||||||
|
if i > math.ceil(height / 0.5) then break end
|
||||||
|
|
||||||
|
local x = list[idx]
|
||||||
|
-- End early if we've run out of list.
|
||||||
|
if not x then break end
|
||||||
|
|
||||||
|
-- Skip incompatible servers. You can't join them,
|
||||||
|
-- so showing them is rather pointless.
|
||||||
|
if (x.proto_max or version.proto_min) >= version.proto_min then
|
||||||
|
local ping_lvl = 0
|
||||||
|
local lag = (x.lag or 0) * 1000 + (x.ping or 0) * 250
|
||||||
|
if lag <= 125 then
|
||||||
|
ping_lvl = 4
|
||||||
|
elseif lag <= 175 then
|
||||||
|
ping_lvl = 3
|
||||||
|
elseif lag <= 250 then
|
||||||
|
ping_lvl = 2
|
||||||
|
elseif lag <= 400 then
|
||||||
|
ping_lvl = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = x.name and x.name:trim() or ""
|
||||||
|
if name == "" then
|
||||||
|
name = core.colorize("#888", x.address..":"..x.port)
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.button(0, i * 0.5, "100%", 0.5)
|
||||||
|
:style({
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
bgcolor = i % 2 == 1 and theme.styles.container.bg_color or theme.styles.container.bg_color_alt,
|
||||||
|
})
|
||||||
|
:style("hovered", {
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
})
|
||||||
|
:onclick(function()
|
||||||
|
state.detail(x)
|
||||||
|
end)
|
||||||
|
imfs.row(0.1, i * 0.5, "100% - 0.65", 0.5)
|
||||||
|
if x.favorite then
|
||||||
|
imfs.image(0, 0, 0.5, 0.5, theme.icon("menu_servers_favorite"))
|
||||||
|
:tooltip("Favorite server", enable_tooltips)
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = x.name and x.name:trim() or ""
|
||||||
|
if name == "" then
|
||||||
|
name = core.colorize("#888", x.address..":"..x.port)
|
||||||
|
end
|
||||||
|
-- We use an arealabel to ensure that the name will be clipped
|
||||||
|
-- if it is inordinately long, rather than overflowing onto
|
||||||
|
-- other thigs.
|
||||||
|
imfs.arealabel(0, 0.125, "1x", 0.375, name)
|
||||||
|
|
||||||
|
if x.pvp == false then
|
||||||
|
imfs.image(0, 0, 0.5, 0.5, theme.icon("menu_servers_peaceful"))
|
||||||
|
:tooltip("Peaceful", enable_tooltips)
|
||||||
|
end
|
||||||
|
|
||||||
|
if x.creative then
|
||||||
|
imfs.image(0, 0, 0.5, 0.5, theme.icon("menu_servers_creative"))
|
||||||
|
:tooltip("Creative", enable_tooltips)
|
||||||
|
end
|
||||||
|
|
||||||
|
if x.clients then
|
||||||
|
local clients = x.clients..(
|
||||||
|
x.clients_max and "/"..x.clients_max or ""
|
||||||
|
)
|
||||||
|
local color = "#aaa"
|
||||||
|
if x.clients > 0 and x.clients_max then
|
||||||
|
local percent = x.clients /x.clients_max
|
||||||
|
if percent < 0.75 then
|
||||||
|
color = "#638b67"
|
||||||
|
elseif percent < 1 then
|
||||||
|
color = "#a69174"
|
||||||
|
else
|
||||||
|
color = "#9d5b5b"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
imfs.hypertext(0, 0, 1, 0.5, "<global valign=middle halign=center color="..color..">"..core.hypertext_escape(clients))
|
||||||
|
end
|
||||||
|
|
||||||
|
if x.ping or x.lag then
|
||||||
|
imfs.image(0, 0, 0.5, 0.5, theme.icon("menu_servers_icon_ping_"..ping_lvl))
|
||||||
|
:tooltip("Ping: "..math.floor(lag), enable_tooltips)
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.row_end()
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
idx = idx + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.scope_end()
|
||||||
|
|
||||||
|
if loading then
|
||||||
|
imfs.hypertext(0, i * 0.5, "100%", 0.5, "<global valign=middle halign=center color=#777>Loading...")
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
local detail = state.detail()
|
||||||
|
if detail then
|
||||||
|
imfs.box("65% - 0.05", 0, 0.1, "100%", theme.styles.container.border_color)
|
||||||
|
imfs.column("65% + 0.1", border_width, "35% - "..(border_width + 0.1), "100% - "..(border_width * 2))
|
||||||
|
:gap(0.1)
|
||||||
|
imfs.hypertext(0, 0, "100%", 0.75, "<global valign=middle halign=center color=#aaa><b>"..core.hypertext_escape(detail.name or detail.address..":"..detail.port).."</b>")
|
||||||
|
if detail.mods or detail.clients_list or detail.favorites then
|
||||||
|
imfs.row(0.1, 0, "100% - 0.2", 0.5)
|
||||||
|
if detail.mods then
|
||||||
|
imfs.button(0, 0, 0.5, 0.5)
|
||||||
|
:image(theme.icon("menu_servers_mods"))
|
||||||
|
:tooltip("Show mod list")
|
||||||
|
:onclick(function()
|
||||||
|
state.detail_view("mod_list")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if detail.clients_list then
|
||||||
|
imfs.button(0, 0, 0.5, 0.5)
|
||||||
|
:image(theme.icon("menu_servers_players"))
|
||||||
|
:tooltip("Show player list")
|
||||||
|
:onclick(function()
|
||||||
|
state.detail_view("player_list")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
if detail.favorite then
|
||||||
|
imfs.button(0, 0, 0.5, 0.5)
|
||||||
|
:image(theme.icon("menu_servers_unfavorite"))
|
||||||
|
:tooltip("Remove from favorites")
|
||||||
|
:onclick(function()
|
||||||
|
detail.favorite = nil
|
||||||
|
remove_favorite_server(detail.address, detail.port)
|
||||||
|
-- Bump the serverlist to notify that we've changed it.
|
||||||
|
state.serverlist(state.serverlist)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
imfs.row_end()
|
||||||
|
end
|
||||||
|
imfs.hypertext(0.1, 0, "100% - 0.2", "1x", "<global color=#aaa>"..core.hypertext_escape(detail.description or ""))
|
||||||
|
imfs.button(0, 0, "100%", 0.75, "Join server")
|
||||||
|
:onclick(function()
|
||||||
|
state.joining(detail)
|
||||||
|
end)
|
||||||
|
imfs.column_end()
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.group_end()
|
||||||
|
|
||||||
|
imfs.button("100% - 1", 0.25, 0.75, 0.75, "<")
|
||||||
|
:onclick(function()
|
||||||
|
show_meta_menu()
|
||||||
|
end)
|
||||||
|
|
||||||
|
local joining = state.joining()
|
||||||
|
if joining then
|
||||||
|
imfs.box(0, 0, "100%", "100%", "#0008")
|
||||||
|
imfs.button(0, 0, "100%", "100%")
|
||||||
|
:styles(styles_no_background)
|
||||||
|
:onclick(function()
|
||||||
|
state.joining(false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.group("20%", "20%", "60%", "60%")
|
||||||
|
imfs.image(0, 0, "100%", "100%", theme.get_background_image("container"))
|
||||||
|
imfs.scroll_container(border_width + 0.1, border_width + 0.1, "100% - "..(border_width * 2 + 0.2), "100% - "..(border_width * 2 + 0.2))
|
||||||
|
imfs.column(0, 0, "100%", 100)
|
||||||
|
:gap(0.1)
|
||||||
|
|
||||||
|
if state.join_error() then
|
||||||
|
imfs.hypertext(0, 0, "100%", 0.75, "<global valign=middle color=#9d5b5b>"..core.hypertext_escape(state.server_connection_error.msg))
|
||||||
|
end
|
||||||
|
|
||||||
|
if joining == true then
|
||||||
|
imfs.hypertext(0, 0, "100%", 0.75, "<global valign=middle halign=center color=#aaa>Connecting to server...")
|
||||||
|
imfs.group(0, 0, "100%", 1.05)
|
||||||
|
imfs.image(0, 0.3, "100%", "100% - 0.3", theme.get_background_image("field"))
|
||||||
|
imfs.label(0.2, 0.125, "Address")
|
||||||
|
imfs.field(0.1, 0.3, "70% - 0.2", "100% - 0.3")
|
||||||
|
:onchange(function(value)
|
||||||
|
state.address(value)
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
join_server(state)
|
||||||
|
end)
|
||||||
|
imfs.box("70% - 0.05", 0.3, 0.1, "100% - 0.3", theme.styles.field.border_color)
|
||||||
|
imfs.label("70% + 0.2", 0.125, "Port")
|
||||||
|
imfs.field("70% + 0.1", 0.3, "30% - 0.2", "100% - 0.3")
|
||||||
|
:onchange(function(value)
|
||||||
|
state.port(value)
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
join_server(state)
|
||||||
|
end)
|
||||||
|
imfs.group_end()
|
||||||
|
else
|
||||||
|
imfs.hypertext(0, 0, "100%", 0.75, "<global valign=middle halign=center color=#aaa>Connecting to <b>"..core.hypertext_escape(joining.name or joining.address..":"..joining.port).."</b>...")
|
||||||
|
end
|
||||||
|
|
||||||
|
theme.field(0, 0, "100%", 0.75, "Username", state.username)
|
||||||
|
:onchange(function(value)
|
||||||
|
state.username(value)
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
join_server(state)
|
||||||
|
end)
|
||||||
|
|
||||||
|
theme.field(0, 0, "100%", 0.75, "Password", "")
|
||||||
|
:password()
|
||||||
|
:onchange(function(value)
|
||||||
|
state.password(value)
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
join_server(state)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.row(0, 0, "100%", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Cancel")
|
||||||
|
:onclick(function()
|
||||||
|
state.joining(false)
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, "1x", "100%", "Join")
|
||||||
|
:onclick(function()
|
||||||
|
join_server(state)
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.column_end()
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
imfs.group_end()
|
||||||
|
elseif state.detail_view() then
|
||||||
|
imfs.box(0, 0, "100%", "100%", "#0008")
|
||||||
|
imfs.button(0, 0, "100%", "100%")
|
||||||
|
:styles(styles_no_background)
|
||||||
|
:onclick(function()
|
||||||
|
state.detail_view(false)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.image("20%", "20%", "60%", "60%", theme.get_background_image("container"))
|
||||||
|
imfs.group("20% + "..border_width, "20% + "..border_width, "60% - "..(border_width * 2), "60% - "..(border_width * 2))
|
||||||
|
imfs.scroll_container(0, 0, "100%", "100% - 0.8")
|
||||||
|
:scrollbar("100% - 0.5", 0, 0.5, "100% - 0.8", "vertical")
|
||||||
|
local list = state.detail_view() == "mod_list" and detail.mods or detail.clients_list
|
||||||
|
for i = 0, #list - 1 do
|
||||||
|
imfs.button(0, i * 0.5, "100%", 0.5)
|
||||||
|
:style({
|
||||||
|
border = false,
|
||||||
|
bgimg = "[fill:1x1:0,0:#fff",
|
||||||
|
bgimg_middle = 0,
|
||||||
|
bgcolor = i % 2 == 1 and theme.styles.container.bg_color or theme.styles.container.bg_color_alt,
|
||||||
|
})
|
||||||
|
:style("hovered", style_borderless_hovered)
|
||||||
|
:style("pressed", style_borderless_hovered)
|
||||||
|
-- This isn't just the button label because we want left-alignment.
|
||||||
|
imfs.arealabel(0.1, i * 0.5 + 0.125, "100%", 0.375, list[i + 1])
|
||||||
|
end
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
imfs.button(0, "100% - 0.75", "100%", 0.75, "Close")
|
||||||
|
:onclick(function()
|
||||||
|
state.detail_view(false)
|
||||||
|
end)
|
||||||
|
imfs.group_end()
|
||||||
|
end
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
271
views/settings.lua
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
|
||||||
|
dofile(core.get_builtin_path().."common/settings/settingtypes.lua")
|
||||||
|
|
||||||
|
-- Some categories, like Accessibility and Theme, do not appear
|
||||||
|
-- in the builtin settingtypes.txt, so they are added to the state table here.
|
||||||
|
local function inject_settings(state)
|
||||||
|
-- Theme
|
||||||
|
table.insert(state.categories, 1, "Theme")
|
||||||
|
state.settings.Theme = {
|
||||||
|
{
|
||||||
|
comment = "The background color of containers.",
|
||||||
|
context = "client",
|
||||||
|
default = "#fff",
|
||||||
|
name = "theme.container_bg_color",
|
||||||
|
readable_name = "Container background color",
|
||||||
|
requires = {},
|
||||||
|
type = "string",
|
||||||
|
value = imfs.state("#fff")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Accessibility
|
||||||
|
table.insert(state.categories, 2, "Accessibility")
|
||||||
|
state.settings.Accessibility = {
|
||||||
|
{
|
||||||
|
comment = "Smooths rotation of camera, also called look or mouse smoothing. 0 to disable.",
|
||||||
|
context = "client",
|
||||||
|
default = "0.0",
|
||||||
|
max = 0.99,
|
||||||
|
min = 0,
|
||||||
|
name = "camera_smoothing",
|
||||||
|
readable_name = "Camera smoothing",
|
||||||
|
requires = {},
|
||||||
|
type = "float",
|
||||||
|
value = imfs.state("0.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function search_settings(state, search)
|
||||||
|
if state.search == "" then
|
||||||
|
return state.settings, state.categories
|
||||||
|
end
|
||||||
|
|
||||||
|
local out = {}
|
||||||
|
for category, settings in pairs(state.settings) do
|
||||||
|
for i = 1, #settings do
|
||||||
|
if settings[i].name:find(search, 1, true) then
|
||||||
|
if not out[category] then
|
||||||
|
out[category] = {settings[i]}
|
||||||
|
else
|
||||||
|
table.insert(out[category], settings[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local categories = {}
|
||||||
|
for i = 1, #state.categories do
|
||||||
|
local category = state.categories[i]
|
||||||
|
if category.name then
|
||||||
|
for j = 1, #state.categories do
|
||||||
|
if out[category[j]] then
|
||||||
|
if not categories[category.name] then
|
||||||
|
table.insert(categories, {name = category.name})
|
||||||
|
end
|
||||||
|
table.insert(categories[#categories], category[j])
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if out[category] then
|
||||||
|
table.insert(categories, category)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return out, categories
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(state)
|
||||||
|
imfs.begin(size.x, size.y)
|
||||||
|
:padding(0, 0)
|
||||||
|
:bgcolor(theme.styles.bg_color, true)
|
||||||
|
|
||||||
|
if not state.settings then
|
||||||
|
state.settings = {}
|
||||||
|
state.categories = {}
|
||||||
|
local category
|
||||||
|
local settings = settingtypes.parse_config_file(false, false)
|
||||||
|
for _, x in ipairs(settings) do
|
||||||
|
if x.type == "category" then
|
||||||
|
if x.level == 0 then
|
||||||
|
state.categories[#state.categories + 1] = {name = x.name}
|
||||||
|
elseif x.level == 1 then
|
||||||
|
local group = state.categories[#state.categories]
|
||||||
|
group[#group + 1] = x.name
|
||||||
|
state.settings[x.name] = {}
|
||||||
|
category = x.name
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local s = state.settings[category]
|
||||||
|
x.value = imfs.state()
|
||||||
|
if x.type == "string" or x.type == "float" or x.type == "int" or x.type == "enum" then
|
||||||
|
x.value._val = minetest.settings:get(x.name) or x.default
|
||||||
|
elseif x.type == "bool" then
|
||||||
|
x.value._val = minetest.settings:get_bool(x.name, x.default)
|
||||||
|
end
|
||||||
|
s[#s +1] = x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
inject_settings(state)
|
||||||
|
|
||||||
|
state.settings_shown._val = state.settings
|
||||||
|
state.categories_shown._val = state.categories
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.box(0, 0, "100%", 1, theme.styles.container.bg_color)
|
||||||
|
|
||||||
|
imfs.row(0.2, 0.125, "100% - 0.4", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
theme.field(0, 0, "1x", "100%", state.search)
|
||||||
|
:onchange(function(value)
|
||||||
|
state.search = value
|
||||||
|
end)
|
||||||
|
:onenter(function()
|
||||||
|
local settings, categories = search_settings(state, state.search)
|
||||||
|
if categories[1] then
|
||||||
|
local first = categories[1].name and categories[1][1] or categories[1]
|
||||||
|
state.category(first)
|
||||||
|
end
|
||||||
|
state.categories_shown(categories)
|
||||||
|
state.settings_shown(settings)
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("search"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Search settings")
|
||||||
|
:onclick(function()
|
||||||
|
local settings, categories = search_settings(state, state.search)
|
||||||
|
if categories[1] then
|
||||||
|
local first = categories[1].name and categories[1][1] or categories[1]
|
||||||
|
state.category(first)
|
||||||
|
end
|
||||||
|
state.categories_shown(categories)
|
||||||
|
state.settings_shown(settings)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("cancel"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Clear search")
|
||||||
|
:onclick(function()
|
||||||
|
state.search = ""
|
||||||
|
state.settings_shown(state.settings)
|
||||||
|
state.categories_shown(state.categories)
|
||||||
|
end)
|
||||||
|
imfs.button(0, 0, 2, "100%", "Back")
|
||||||
|
:onclick(function()
|
||||||
|
show_meta_menu()
|
||||||
|
end)
|
||||||
|
imfs.row_end()
|
||||||
|
|
||||||
|
imfs.box(0, 1, "20%", "100% - 1", theme.styles.container.bg_color)
|
||||||
|
imfs.box(0, 0.95, "100%", 0.1, theme.styles.container.border_color)
|
||||||
|
|
||||||
|
imfs.scroll_container(0.2, 1.05, "20% - 0.45", "100% - 1.05")
|
||||||
|
local categories = state.categories_shown()
|
||||||
|
if categories then
|
||||||
|
local y = 0.1
|
||||||
|
for i = 1, #categories do
|
||||||
|
local group = categories[i]
|
||||||
|
if not group then break end
|
||||||
|
-- Top-level categories.
|
||||||
|
if type(group) == "string" then
|
||||||
|
imfs.button(0, y, "100%", 0.5, group)
|
||||||
|
:onclick(function()
|
||||||
|
if state.category() ~= group then
|
||||||
|
state.category(group)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
y = y + 0.55
|
||||||
|
-- Grouped categories.
|
||||||
|
else
|
||||||
|
imfs.arealabel(0, y + 0.125, "100%", 0.375, group.name)
|
||||||
|
y = y + 0.425
|
||||||
|
local h = 0
|
||||||
|
for j = 1, #group do
|
||||||
|
local category = group[j]
|
||||||
|
imfs.box(0.12, y + 0.245, 0.25, 0.01, theme.styles.text_color)
|
||||||
|
imfs.button(0.25, y, "100% - 0.25", 0.5, category)
|
||||||
|
:onclick(function()
|
||||||
|
if state.category() ~= category then
|
||||||
|
state.category(category)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
h = h + 0.55
|
||||||
|
y = y + 0.55
|
||||||
|
end
|
||||||
|
imfs.box(0.12, y - h, 0.01, h - 0.3, theme.styles.text_color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
imfs.box("20% - 0.05", 1, 0.1, "100% - 1", theme.styles.container.border_color)
|
||||||
|
|
||||||
|
imfs.scroll_container("20% + 0.25", 1.05, "80% - 0.45", "100% - 1.05")
|
||||||
|
imfs.column(0, 0, "100%", 1000)
|
||||||
|
:gap(0.1)
|
||||||
|
local settings = state.settings_shown()[state.category()]
|
||||||
|
if settings then
|
||||||
|
for i = 1, #settings do
|
||||||
|
local setting = settings[i]
|
||||||
|
if not setting then break end
|
||||||
|
if setting.type == "bool" then
|
||||||
|
local active = minetest.settings:get_bool(setting.name, setting.default)
|
||||||
|
theme.checkbox(0, 0, setting.readable_name, setting.value, setting.comment)
|
||||||
|
elseif setting.type == "float" or setting.type == "int" then
|
||||||
|
imfs.arealabel(0, 0, "100%", 0.325, setting.readable_name)
|
||||||
|
imfs.row(0, 0, "100%", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
theme.number_field(0, 0, "1x", 0.75, setting.value)
|
||||||
|
:tooltip(setting.comment)
|
||||||
|
imfs.button(0, 0, 2, 0.75, "Set")
|
||||||
|
imfs.row_end()
|
||||||
|
elseif setting.type == "string" then
|
||||||
|
imfs.arealabel(0, 0, "100%", 0.325, setting.readable_name)
|
||||||
|
imfs.row(0, 0, "100%", 0.75)
|
||||||
|
:gap(0.1)
|
||||||
|
theme.field(0, 0, "1x", 0.75, setting.value)
|
||||||
|
:tooltip(setting.comment)
|
||||||
|
if setting.value._val ~= setting.default then
|
||||||
|
imfs.button(0, 0, 0.75, 0.75)
|
||||||
|
:image(theme.icon("refresh"))
|
||||||
|
:style({
|
||||||
|
bgimg = theme.get_background_image("button"),
|
||||||
|
bgimg_middle = theme.styles.button.border_width,
|
||||||
|
})
|
||||||
|
:tooltip("Reset to default")
|
||||||
|
end
|
||||||
|
imfs.button(0, 0, 2, 0.75, "Set")
|
||||||
|
imfs.row_end()
|
||||||
|
elseif setting.type == "enum" then
|
||||||
|
imfs.scope(setting.name)
|
||||||
|
imfs.arealabel(0, 0, "100%", 0.325, setting.readable_name)
|
||||||
|
imfs.row(0, 0, "100%", "fit")
|
||||||
|
:gap(0.1)
|
||||||
|
theme.dropdown(0, 0, "1x", 0.75, setting.values, setting.value)
|
||||||
|
:tooltip(setting.comment)
|
||||||
|
imfs.button(0, 0, 2, 0.75, "Set")
|
||||||
|
imfs.row_end()
|
||||||
|
imfs.scope_end()
|
||||||
|
else
|
||||||
|
print(dump(setting))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
imfs.column_end()
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||