Add new buttons to main inventory, add inventory sorting methods + API

This commit is contained in:
Jean-Patrick Guerrero 2021-10-31 22:11:41 +01:00
parent 8d7ca9df18
commit 9276598e3e
13 changed files with 414 additions and 163 deletions

34
API.md
View File

@ -235,6 +235,40 @@ A map of search filters, indexed by name.
---
### Sorting methods
Sorting methods are used to filter the player's main inventory.
#### `i3.add_sorting_method(def)`
Adds a player inventory sorting method.
- `def` is the method definition.
Example:
```Lua
i3.add_sorting_method {
name = "test",
description = "Cool sorting method",
func = function(player, data)
local inv = player:get_inventory()
local list = inv:get_list("main")
table.sort(list)
-- An array of items must be returned
return list
end,
}
```
#### `i3.sorting_methods`
A table containing all sorting methods.
---
### Item list compression
`i3` can reduce the item list size by compressing a group of items.

View File

@ -15,10 +15,10 @@ This mod requires **Minetest 5.4+**
- Quick Crafting
- Backpacks
- 3D Player Model Preview
- Inventory Sorting (alphabetical + item stack compression)
- Inventory Sorting (with optional compression)
- Item Bookmarks
- Waypoints
- Item List Compression (**`moreblocks`** supported)
- Item List Compression (**`moreblocks`** is supported)
**¹** *This mode is a Terraria-like system that shows recipes you can craft from items you ever had in your inventory.
To enable it: `i3_progressive_mode = true` in `minetest.conf`.*

View File

@ -2,6 +2,7 @@ local make_fs = i3.files.gui()
local gmatch, match, split = i3.get("gmatch", "match", "split")
local S, err, fmt, reg_items = i3.get("S", "err", "fmt", "reg_items")
local name_sort, count_sort, sort_inventory = i3.get("name_sort", "count_sort", "sort_inventory")
local sort, concat, copy, insert, remove = i3.get("sort", "concat", "copy", "insert", "remove")
local true_str, true_table, is_str, is_func, is_table, clean_name =
i3.get("true_str", "true_table", "is_str", "is_func", "is_table", "clean_name")
@ -166,6 +167,10 @@ function i3.set_fs(player, _fs)
local data = i3.data[name]
if not data then return end
if data.auto_sorting then
sort_inventory(player, data)
end
local fs = fmt("%s%s", make_fs(player, data), _fs or "")
player:set_inventory_formspec(fs)
end
@ -289,3 +294,65 @@ function i3.compress(item, def)
i3.compressed[it] = true
end
end
function i3.add_sorting_method(def)
if not true_table(def) then
return err "i3.add_sorting_method: definition missing"
elseif not true_str(def.name) then
return err "i3.add_sorting_method: name missing"
elseif not is_func(def.func) then
return err "i3.add_sorting_method: function missing"
end
insert(i3.sorting_methods, def)
end
local function pre_sorting(player)
local inv = player:get_inventory()
local list = inv:get_list("main")
local size = inv:get_size("main")
local new_inv, stack_meta = {}, {}
for i = 1, size do
local stack = list[i]
local name = stack:get_name()
local count = stack:get_count()
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear then
stack_meta[#stack_meta + 1] = stack
else
new_inv[#new_inv + 1] = ItemStack(fmt("%s %u", name, count))
end
end
end
for i = 1, #stack_meta do
new_inv[#new_inv + 1] = stack_meta[i]
end
return new_inv
end
i3.add_sorting_method {
name = "alphabetical",
description = S"Sort items by name (A-Z)",
func = function(player, data)
local new_inv = pre_sorting(player)
name_sort(new_inv, data.reverse_sorting)
return new_inv
end
}
i3.add_sorting_method {
name = "numerical",
description = S"Sort items by number of items per stack",
func = function(player, data)
local new_inv = pre_sorting(player)
count_sort(new_inv, data.reverse_sorting)
return new_inv
end,
}

View File

@ -1,6 +1,6 @@
local translate = core.get_translated_string
local insert, remove, floor, vec_add, vec_mul =
table.insert, table.remove, math.floor, vector.add, vector.mul
local insert, remove, sort, vec_add, vec_mul =
table.insert, table.remove, table.sort, vector.add, vector.mul
local fmt, find, gmatch, match, sub, split, lower =
string.format, string.find, string.gmatch, string.match, string.sub, string.split, string.lower
local reg_items, reg_nodes, reg_craftitems, reg_tools =
@ -297,7 +297,7 @@ end
local function round(num, decimal)
local mul = 10 ^ decimal
return floor(num * mul + 0.5) / mul
return math.floor(num * mul + 0.5) / mul
end
local function is_fav(favs, query_item)
@ -351,9 +351,107 @@ local function spawn_item(player, stack)
core.add_item(look_at, stack)
end
local function name_sort(inv, reverse)
return sort(inv, function(a, b)
a, b = a:get_name(), b:get_name()
if reverse then
return a > b
end
return a < b
end)
end
local function count_sort(inv, reverse)
return sort(inv, function(a, b)
a, b = a:get_count(), b:get_count()
if reverse then
return a > b
end
return a < b
end)
end
local function get_sorting_idx(name)
local idx = 1
for i, def in ipairs(i3.sorting_methods) do
if name == def.name then
idx = i
end
end
return idx
end
local function compress_items(player)
local inv = player:get_inventory()
local list = inv:get_list("main")
local size = inv:get_size("main")
local new_inv, _new_inv, special = {}, {}, {}
for i = 1, size do
local stack = list[i]
local name = stack:get_name()
local count = stack:get_count()
local stackmax = stack:get_stack_max()
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear or count >= stackmax then
special[#special + 1] = stack
else
new_inv[name] = new_inv[name] or 0
new_inv[name] = new_inv[name] + count
end
end
end
for name, count in pairs(new_inv) do
local stackmax = ItemStack(name):get_stack_max()
local iter = math.ceil(count / stackmax)
local leftover = count
for _ = 1, iter do
_new_inv[#_new_inv + 1] = fmt("%s %u", name, math.min(stackmax, leftover))
leftover = leftover - stackmax
end
end
for i = 1, #special do
_new_inv[#_new_inv + 1] = special[i]
end
inv:set_list("main", _new_inv)
end
local function sort_inventory(player, data)
if data.inv_compress then
compress_items(player)
end
local sorts = {}
for _, def in ipairs(i3.sorting_methods) do
sorts[def.name] = def.func
end
local new_inv = sorts[data.sort](player, data)
if new_inv then
local inv = player:get_inventory()
inv:set_list("main", new_inv)
end
end
-------------------------------------------------------------------------------
local registry = {
local _ = {
-- Groups
is_group = is_group,
extract_groups = extract_groups,
@ -366,6 +464,10 @@ local registry = {
-- Sorting
search = search,
name_sort = name_sort,
count_sort = count_sort,
sort_inventory = sort_inventory,
get_sorting_idx = get_sorting_idx,
sort_by_category = sort_by_category,
apply_recipe_filters = apply_recipe_filters,
@ -446,7 +548,7 @@ function i3.get(...)
local t = {}
for i, var in ipairs{...} do
t[i] = registry[var]
t[i] = _[var]
end
return unpack(t)

View File

@ -8,13 +8,14 @@ local clr, ESC, check_privs = i3.get("clr", "ESC", "check_privs")
local min, max, floor, ceil, round = i3.get("min", "max", "floor", "ceil", "round")
local sprintf, find, match, sub, upper = i3.get("fmt", "find", "match", "sub", "upper")
local reg_items, reg_tools, reg_entities = i3.get("reg_items", "reg_tools", "reg_entities")
local maxn, sort, concat, copy, insert, remove = i3.get("maxn", "sort", "concat", "copy", "insert", "remove")
local maxn, sort, concat, copy, insert, remove =
i3.get("maxn", "sort", "concat", "copy", "insert", "remove")
local true_str, is_fav, is_num = i3.get("true_str", "is_fav", "is_num")
local is_group, extract_groups, item_has_groups =
i3.get("is_group", "extract_groups", "item_has_groups")
local groups_to_items, compression_active, compressible =
i3.get("groups_to_items", "compression_active", "compressible")
local get_sorting_idx, is_group, extract_groups, item_has_groups =
i3.get("get_sorting_idx", "is_group", "extract_groups", "item_has_groups")
local function fmt(elem, ...)
if not fs_elements[elem] then
@ -404,6 +405,84 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa
end
end
local function show_popup(fs, data)
if data.confirm_trash then
fs("style_type[box;colors=#999,#999,#808080,#808080]")
for _ = 1, 3 do
fs("box", 2.97, 10.75, 4.3, 0.5, "")
end
fs("label", 3.12, 11, "Confirm trash?")
fs("image_button", 5.17, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes")
fs("image_button", 6.27, 10.75, 1, 0.5, "", "confirm_trash_no", "No")
elseif data.show_settings then
fs("style_type[box;colors=#999,#999,#808080,#808080]")
for _ = 1, 3 do
fs("box", 2.1, 9.25, 6, 2, "")
end
for _ = 1, 3 do
fs("box", 2.1, 9.25, 6, 0.5, "#707070")
end
fs("image_button", 7.75, 9.35, 0.25, 0.25, PNG.cancel_hover .. "^\\[brighten", "close_settings", "")
local show_home = data.show_setting == "home"
local show_sorting = data.show_setting == "sorting"
local show_misc = data.show_setting == "misc"
fs(fmt("style[setting_home;textcolor=%s;sound=i3_click]", show_home and "#ff0" or "#fff"))
fs(fmt("style[setting_sorting;textcolor=%s;sound=i3_click]", show_sorting and "#ff0" or "#fff"))
fs(fmt("style[setting_misc;textcolor=%s;sound=i3_click]", show_misc and "#ff0" or "#fff"))
fs("button", 2.2, 9.25, 1.8, 0.55, "setting_home", "Home")
fs("button", 4, 9.25, 1.8, 0.55, "setting_sorting", "Sorting")
fs("button", 5.8, 9.25, 1.8, 0.55, "setting_misc", "Misc.")
if show_home then
local home_pos = data.home or ""
home_pos = home_pos:gsub(",", ", "):gsub("%(", ""):gsub("%)", "")
local home_str = fmt("Home position: %s", home_pos)
home_str = data.home and home_str or ES"No home set"
fs("button", 2.1, 9.7, 6, 0.8, "", home_str)
fs("image_button", 4.2, 10.4, 1.8, 0.7, "", "set_home", "Set home")
elseif show_sorting then
fs("button", 2.1, 9.7, 6, 0.8, "", ES"Select the inventory sorting method:")
fs(fmt("style[prev_sort;fgimg=%s;fgimg_hovered=%s]", PNG.prev, PNG.prev_hover))
fs(fmt("style[next_sort;fgimg=%s;fgimg_hovered=%s]", PNG.next, PNG.next_hover))
fs("image_button", 2.2, 10.6, 0.35, 0.35, "", "prev_sort", "")
fs("image_button", 7.65, 10.6, 0.35, 0.35, "", "next_sort", "")
fs("style[sort_method;font=bold;font_size=20]")
fs("button", 2.55, 10.35, 5.1, 0.8, "sort_method", data.sort:gsub("^%l", upper))
local idx = get_sorting_idx(data.sort)
local desc = i3.sorting_methods[idx].description
if desc then
fs(fmt("tooltip[%s;%s]", "sort_method", desc))
end
elseif show_misc then
fs("checkbox", 2.4, 10.05,
"inv_compress", ES"Inventory compression", tostring(data.inv_compress))
fs("checkbox", 2.4, 10.5,
"auto_sorting", ES"Automatic sorting", tostring(data.auto_sorting))
fs("checkbox", 2.4, 10.95,
"reverse_sorting", ES"Reverse sorting", tostring(data.reverse_sorting))
end
end
end
local function get_inventory_fs(player, data, fs)
fs("listcolors[#bababa50;#bababa99]")
@ -482,9 +561,9 @@ local function get_inventory_fs(player, data, fs)
local btn = {
{"trash", ES"Clear inventory"},
{"sort_az", ES"Sort items (A-Z)"},
{"sort_za", ES"Sort items (Z-A)"},
{"compress", ES"Compress items"},
{"sort", ES"Sort items"},
{"settings", ES"Settings"},
{"home", ES"Go home"},
}
for i, v in ipairs(btn) do
@ -493,21 +572,11 @@ local function get_inventory_fs(player, data, fs)
fs(fmt("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]",
btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)]))
fs("image_button", i + 3.447 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "")
fs("image_button", i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "")
fs(fmt("tooltip[%s;%s]", btn_name, tooltip))
end
if data.confirm_trash then
fs("style_type[box;colors=#999,#999,#808080,#808080]")
for _ = 1, 3 do
fs("box", 2.97, 10.75, 4.3, 0.5, "")
end
fs("label", 3.12, 11, "Confirm trash?")
fs("image_button", 5.17, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes")
fs("image_button", 6.27, 10.75, 1, 0.5, "", "confirm_trash_no", "No")
end
show_popup(fs, data)
end
local function get_tooltip(item, info, pos)

View File

@ -7,11 +7,11 @@ local fmt, find, match, sub, lower = i3.get("fmt", "find", "match", "sub", "lowe
local vec_new, vec_mul, vec_eq, vec_round = i3.get("vec_new", "vec_mul", "vec_eq", "vec_round")
local sort, copy, insert, remove, indexof = i3.get("sort", "copy", "insert", "remove", "indexof")
local msg, is_str, is_fav = i3.get("msg", "is_str", "is_fav")
local msg, is_fav = i3.get("msg", "is_fav")
local is_group, extract_groups, groups_to_items =
i3.get("is_group", "extract_groups", "groups_to_items")
local search, sort_by_category, apply_recipe_filters =
i3.get("search", "sort_by_category", "apply_recipe_filters")
local search, get_sorting_idx, sort_inventory, sort_by_category, apply_recipe_filters =
i3.get("search", "get_sorting_idx", "sort_inventory", "sort_by_category", "apply_recipe_filters")
local show_item, spawn_item, clean_name, compressible, check_privs =
i3.get("show_item", "spawn_item", "clean_name", "compressible", "check_privs")
@ -40,6 +40,8 @@ local function reset_data(data)
data.export_usg = nil
data.alt_items = nil
data.confirm_trash = nil
data.show_settings = nil
data.show_setting = "home"
data.items = data.items_raw
if data.current_itab > 1 then
@ -66,104 +68,6 @@ local function get_recipes(player, item)
not no_usages and usages or nil
end
local function __sort(inv, reverse)
sort(inv, function(a, b)
if not is_str(a) then
a = a:get_name()
end
if not is_str(b) then
b = b:get_name()
end
if reverse then
return a > b
end
return a < b
end)
end
local function sort_itemlist(player, az)
local inv = player:get_inventory()
local list = inv:get_list("main")
local size = inv:get_size("main")
local new_inv, stack_meta = {}, {}
for i = 1, size do
local stack = list[i]
local name = stack:get_name()
local count = stack:get_count()
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear then
stack_meta[#stack_meta + 1] = stack
else
new_inv[#new_inv + 1] = fmt("%s %u", name, count)
end
end
end
for i = 1, #stack_meta do
new_inv[#new_inv + 1] = stack_meta[i]
end
if az then
__sort(new_inv)
else
__sort(new_inv, true)
end
inv:set_list("main", new_inv)
end
local function compress_items(player)
local inv = player:get_inventory()
local list = inv:get_list("main")
local size = inv:get_size("main")
local new_inv, _new_inv, special = {}, {}, {}
for i = 1, size do
local stack = list[i]
local name = stack:get_name()
local count = stack:get_count()
local stackmax = stack:get_stack_max()
local empty = stack:is_empty()
local meta = stack:get_meta():to_table()
local wear = stack:get_wear() > 0
if not empty then
if next(meta.fields) or wear or count >= stackmax then
special[#special + 1] = stack
else
new_inv[name] = new_inv[name] or 0
new_inv[name] = new_inv[name] + count
end
end
end
for name, count in pairs(new_inv) do
local stackmax = ItemStack(name):get_stack_max()
local iter = ceil(count / stackmax)
local leftover = count
for _ = 1, iter do
_new_inv[#_new_inv + 1] = fmt("%s %u", name, min(stackmax, leftover))
leftover = leftover - stackmax
end
end
for i = 1, #special do
_new_inv[#_new_inv + 1] = special[i]
end
__sort(_new_inv)
inv:set_list("main", _new_inv)
end
local function get_stack(player, stack)
local inv = player:get_inventory()
@ -174,6 +78,13 @@ local function get_stack(player, stack)
end
end
local function safe_teleport(player, pos)
pos.y = pos.y + 0.5
local vel = player:get_velocity()
player:add_velocity(vec_mul(vel, -1))
player:set_pos(pos)
end
i3.new_tab {
name = "inventory",
description = S"Inventory",
@ -181,6 +92,7 @@ i3.new_tab {
fields = function(player, data, fields)
local name = player:get_player_name()
local inv = player:get_inventory()
local sb_inv = fields.scrbar_inv
if fields.skins then
@ -194,6 +106,9 @@ i3.new_tab {
data.subcat = indexof(i3.SUBCAT, sub(field, 5))
break
elseif sub(field, 1, 8) == "setting_" then
data.show_setting = match(field, "_(%w+)$")
elseif find(field, "waypoint_%d+") then
local id, action = match(field, "_(%d+)_(%w+)$")
id = tonumber(id)
@ -206,12 +121,7 @@ i3.new_tab {
elseif action == "teleport" then
local pos = vec_new(waypoint.pos)
pos.y = pos.y + 0.5
local vel = player:get_velocity()
player:add_velocity(vec_mul(vel, -1))
player:set_pos(pos)
safe_teleport(player, pos)
msg(name, fmt("Teleported to %s", clr("#ff0", waypoint.name)))
elseif action == "refresh" then
@ -242,23 +152,79 @@ i3.new_tab {
end
end
if fields.trash then
if fields.quit then
data.confirm_trash = nil
data.show_settings = nil
elseif fields.trash then
data.show_settings = nil
data.confirm_trash = true
elseif fields.settings then
data.confirm_trash = nil
data.show_settings = true
elseif fields.confirm_trash_yes or fields.confirm_trash_no then
if fields.confirm_trash_yes then
local inv = player:get_inventory()
inv:set_list("main", {})
inv:set_list("craft", {})
end
data.confirm_trash = nil
elseif fields.compress then
compress_items(player)
elseif fields.close_settings then
data.show_settings = nil
elseif fields.sort_az or fields.sort_za then
sort_itemlist(player, fields.sort_az)
elseif fields.sort then
sort_inventory(player, data)
elseif fields.prev_sort or fields.next_sort then
local idx = get_sorting_idx(data.sort)
local tot = #i3.sorting_methods
idx = idx - (fields.prev_sort and 1 or -1)
if idx > tot then
idx = 1
elseif idx == 0 then
idx = tot
end
data.sort = i3.sorting_methods[idx].name
elseif fields.inv_compress then
data.inv_compress = false
if fields.inv_compress == "true" then
data.inv_compress = true
end
elseif fields.auto_sorting then
data.auto_sorting = false
if fields.auto_sorting == "true" then
data.auto_sorting = true
end
elseif fields.reverse_sorting then
data.reverse_sorting = false
if fields.reverse_sorting == "true" then
data.reverse_sorting = true
end
elseif fields.home then
if not data.home then
return msg(name, "No home set")
elseif not check_privs(name, {home = true}) then
return msg(name, "'home' privilege missing")
end
safe_teleport(player, core.string_to_pos(data.home))
msg(name, S"Welcome back home!")
elseif fields.set_home then
data.home = core.pos_to_string(player:get_pos(), 1)
elseif sb_inv and sub(sb_inv, 1, 3) == "CHG" then
data.scrbar_inv = tonumber(match(sb_inv, "%d+"))

View File

@ -11,8 +11,8 @@ local PNG = {
next = "i3_next.png",
arrow = "i3_arrow.png",
trash = "i3_trash.png",
sort_az = "i3_sort_az.png",
sort_za = "i3_sort_za.png",
sort = "i3_sort.png",
settings = "i3_settings.png",
compress = "i3_compress.png",
fire = "i3_fire.png",
fire_anim = "i3_fire_anim.png",
@ -36,14 +36,15 @@ local PNG = {
visible = "i3_visible.png^\\[brighten",
nonvisible = "i3_non_visible.png",
exit = "i3_exit.png",
home = "i3_home.png",
cancel_hover = "i3_cancel.png^\\[brighten",
search_hover = "i3_search.png^\\[brighten",
export_hover = "i3_export.png^\\[brighten",
trash_hover = "i3_trash.png^\\[brighten^\\[colorize:#f00:100",
compress_hover = "i3_compress.png^\\[brighten",
sort_az_hover = "i3_sort_az.png^\\[brighten",
sort_za_hover = "i3_sort_za.png^\\[brighten",
sort_hover = "i3_sort.png^\\[brighten",
settings_hover = "i3_settings.png^\\[brighten",
prev_hover = "i3_next_hover.png^\\[transformFX",
next_hover = "i3_next_hover.png",
tab_hover = "i3_tab_hover.png",
@ -58,13 +59,14 @@ local PNG = {
add_hover = "i3_add.png^\\[brighten",
refresh_hover = "i3_refresh.png^\\[brighten",
exit_hover = "i3_exit.png^\\[brighten",
home_hover = "i3_home.png^\\[brighten",
}
local styles = string.format([[
style_type[field;border=false;bgcolor=transparent]
style_type[label,field;font_size=16]
style_type[button;border=false;content_offset=0]
style_type[image_button,item_image_button;border=false;sound=i3_click]
style_type[image_button,item_image_button,checkbox;border=false;sound=i3_click]
style_type[item_image_button;bgimg_hovered=%s]
style[pagenum,no_item,no_rcp;font=bold;font_size=18]
@ -82,7 +84,7 @@ local styles = string.format([[
style[craft_rcp,craft_usg;noclip=true;font_size=16;sound=i3_craft;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
style[confirm_trash_yes,confirm_trash_no;noclip=true;font_size=16;
style[confirm_trash_yes,confirm_trash_no,set_home;noclip=true;font_size=16;
bgimg=i3_btn9.png;bgimg_hovered=i3_btn9_hovered.png;
bgimg_pressed=i3_btn9_pressed.png;bgimg_middle=4,6]
]],
@ -104,6 +106,7 @@ local fs_elements = {
image = "image[%f,%f;%f,%f;%s]",
tooltip = "tooltip[%f,%f;%f,%f;%s]",
button = "button[%f,%f;%f,%f;%s;%s]",
checkbox = "checkbox[%f,%f;%s;%s;%s]",
item_image = "item_image[%f,%f;%f,%f;%s]",
hypertext = "hypertext[%f,%f;%f,%f;%s;%s]",
bg9 = "background9[%f,%f;%f,%f;%s;false;%u]",

View File

@ -30,6 +30,7 @@ i3 = {
},
META_SAVES = {
home = true,
bag_item = true,
bag_size = true,
waypoints = true,
@ -39,13 +40,16 @@ i3 = {
-- Caches
init_items = {},
recipes_cache = {},
usages_cache = {},
fuel_cache = {},
usages_cache = {},
recipes_cache = {},
tabs = {},
craft_types = {},
recipe_filters = {},
search_filters = {},
craft_types = {},
tabs = {},
sorting_methods = {},
files = {
api = lf("/etc/api.lua"),
@ -145,11 +149,17 @@ local function init_data(player, info)
data.items = i3.init_items
data.items_raw = i3.init_items
data.favs = {}
data.sort = "alphabetical"
data.show_setting = "home"
data.auto_sorting = false
data.reverse_sorting = false
data.inv_compress = true
data.export_counts = {}
data.current_tab = 1
data.current_itab = 1
data.subcat = 1
data.scrbar_inv = 0
data.compress = true
data.lang_code = get_lang_code(info)
data.fs_version = info.formspec_version

BIN
textures/i3_home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
textures/i3_settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
textures/i3_sort.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB