Add views and containers

* Views and Containers support

* completed the checkbox-fix

* Add getContainerState() to the documentation

* changed implementation for fields processing
Removed again _sfs_process_value_ and _sfs_process_action_
use element:submit() and element:setValue() instead utilizating new state:_get_element_recursive_()
Additional bugfix: container location was missed

* runtime optimization. Do _get_element_recursive_ once for setValue and submit
master
bell07 2017-08-12 01:01:35 +02:00 committed by rubenwardy
parent 9acbd42aeb
commit 3de2eacda5
4 changed files with 342 additions and 65 deletions

View File

@ -37,6 +37,8 @@
* state:background( x,y,w,h,name,image ) - create an image box in background
* state:inventory( x,y,w,h,name ) - create an inventory listing (use 'main' as name for the main player inventory)
* state:checkbox( x,y,name,label,selected ) - create a check box.
* state:container(x,y,name) - Add a container with elements shift relative to x,y
* state:view(x,y,name) - Add a virtual container (view). element coordinates are ablsolute to the parent view
* state:element( element_type, data ) - Semi-private, create an element with type and data.
###Variables
@ -62,6 +64,7 @@
* element:getBackground() - get the current background
* element:setVisible(bool) - set the visibility status (set hidden=>false, unhide=>true or nil)
* element:getVisible() - get the visibility status
* element:setValue(string) - set value for the element, called internally from on_receive_fields
###Button
* element:setText( text ) - set the caption of the button
@ -127,3 +130,6 @@
* element:onBuild( func(self) ) - run every time form is shown. You can set code from here
* element:setCode( code ) - set the formspec code
* element:getCode( code ) - get the formspec code
###Container/View
* element:getContainerState() - returns the container's sub-state to work with or add container elements

173
docs/example-container.lua Normal file
View File

@ -0,0 +1,173 @@
-----------------------------------------------------------------
--- Exemple nested container
-----------------------------------------------------------------
local header_form_function = function(state)
local label = state:label(0,0,"lbl","SmartFS formspec using nested containers")
label:setSize(10,0.5)
state:toggle(0,0.5,3,1,"img_tog",{"img on","img off"}):onToggle(function(self,func)
if state:get("img"):getVisible() then
state:get("img"):setVisible(false)
else
state:get("img"):setVisible(true)
end
end)
local tog1 = state:toggle(3,0.5,3,1,"container1_tog",{"container1 on","container1 off"})
tog1:onToggle(function(self,func)
if state:get("container1"):getVisible() then
state:get("container1"):setVisible(false)
else
state:get("container1"):setVisible()
end
end)
state:toggle(6,0.5,3,1,"container2_tog",{"container2 on","container2 off"}):onToggle(function(self,func)
local container2 = state:get("container1"):getContainerState():get("container2")
if container2:getVisible() then
container2:setVisible(false)
else
container2:setVisible()
end
end)
end
local main_form_function = function(state)
-- load the header inplace to the current state
state:size(10,10)
header_form_function(state)
local image = state:image(0,2,1,1,"img","default_ladder_steel.png")
state:toggle(0,3,3,1,"tg",{"plenty..","of..","custom..","elements"})
state:onInput(function(state, fields, player)
print("hook 1")
end)
local container1_element = state:view(0, 4, "container1")
container1_element:setBackground("default_stone.png")
local sub_state1 = container1_element:getContainerState()
sub_state1:size(10,6)
-- all x/y values needs to be "real"
sub_state1:image(0,4,1,1,"img","default_ladder_wood.png")
sub_state1:toggle(0,5,3,1,"tg",{"plenty..","of..","custom..","elements"})
sub_state1:onInput(function(state, fields, player)
print("sub_state1 hook 2")
end)
local container2_element = sub_state1:container(0, 7, "container2") --nested to sub_state1
container2_element:setBackground("default_brick.png")
local sub_state2 = container2_element:getContainerState()
sub_state2:size(10,3)
-- all the x/y values in relation to container
sub_state2:image(0,0,1,1,"img", "default_stone.png")
local tog2 = sub_state2:toggle(0,1,3,1,"tg",{"second..","toggle..","with..","same..","name.."})
tog2:setBackground("default_gold_block.png")
sub_state2:onInput(function(state, fields, player)
print("sub_state2 hook 1")
end)
end
local form_container = smartfs.create("smartfs:container_form",main_form_function)
minetest.register_chatcommand("sfs_v", {
params = "",
description = "SmartFS test formspec with nested containers",
func = function(name, param)
form_container:show(name)
end,
})
-----------------------------------------------------------------
--- Exemple tabbed container
-----------------------------------------------------------------
local tab1_def = function(state)
state:label(0,0,"lbl","Tab1 Content")
state:toggle(0,1,2,2,"tog",{"plenty..","of..","custom..","elements"})
end
local tab2_def = function(state)
state:label(0,0,"lbl","Tab2 Content")
state:field(0.5,2,4,1,"txt","Textbox")
local listbox = state:listbox(0,3,9,5,"list")
listbox:addItem("First entry")
listbox:addItem("Second entry")
end
local tab3_def = function(state)
state:label(0,0,"lbl","Tab3 Content")
local area = state:textarea(0.5,1,9,5,"ta","Text:")
area:setText("Hallo")
end
local tab_form_function = function(state)
state:size(10,10)
local tab_element = state:container(0, 0, "tab_container")
local tab_state = tab_element:getContainerState()
tab_state:size(10,10)
local tab_controller = {
_tabs = {},
set_active = function(self, tabname)
for name, def in pairs(self._tabs) do
if name == tabname then
def.button:setBackground("default_gold_block.png")
def.container:setVisible()
else
def.button:setBackground(nil)
def.container:setVisible(false)
end
end
end,
tab_add = function(self, name, def)
self._tabs[name] = def
end,
}
local tab1 = {}
tab1.button = tab_state:button(0,0,2,1,"tab1_btn","Tab 1")
tab1.button:onClick(function(self)
tab_controller:set_active("tab1")
end)
tab1.container = tab_state:container(0,1,"tab1_container")
tab1.containerstate = tab1.container:getContainerState()
tab1_def(tab1.containerstate)
tab1.containerstate:size(10,9)
tab_controller:tab_add("tab1", tab1)
local tab2 = {}
tab2.button = tab_state:button(2,0,2,1,"tab2_btn","Tab 2")
tab2.button:onClick(function(self)
tab_controller:set_active("tab2")
end)
tab2.container = tab_state:container(0,1,"tab2_container")
tab2.containerstate = tab2.container:getContainerState()
tab2_def(tab2.containerstate)
tab2.containerstate:size(10,9)
tab_controller:tab_add("tab2", tab2)
local tab3 = {}
tab3.button = tab_state:button(4,0,2,1,"tab3_btn","Tab 3")
tab3.button:onClick(function(self)
tab_controller:set_active("tab3")
end)
tab3.container = tab_state:container(0,1,"tab3_container")
tab3.containerstate = tab3.container:getContainerState()
tab3_def(tab3.containerstate)
tab3.containerstate:size(10,9)
tab_controller:tab_add("tab3", tab3)
tab_controller:set_active("tab1") --default tab
end
local form_tab = smartfs.create("smartfs:container_tab", tab_form_function)
minetest.register_chatcommand("sfs_t", {
params = "",
description = "SmartFS test formspec with tabbed containers",
func = function(name, param)
form_tab:show(name)
end,
})
--smartfs.set_player_inventory(form_tab)

View File

@ -1,6 +1,6 @@
smartfs = dofile(minetest.get_modpath("smartfs").."/smartfs.lua")
-- dofile(minetest.get_modpath("smartfs").."/docs/example.lua")
-- dofile(minetest.get_modpath("smartfs").."/docs/example-container.lua")
assert(minetest.is_yes(true) == true)
assert(minetest.is_yes(false) == false)

View File

@ -264,7 +264,16 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
-- create object to handle formspec location
local function _make_location_(form, newplayer, params, is_inv, nodepos)
local self = {}
if nodepos then
if form.root and form.root.location then --the parent "form" is a state
self.type = "container"
self.containerElement = form
self.parentState = form.root
if self.parentState.location.type == "container" then
self.rootState = self.parentState.location.rootState
else
self.rootState = self.parentState
end
elseif nodepos then
self.type = "nodemeta"
self.pos = nodepos
elseif newplayer then
@ -295,9 +304,24 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
close = function(self)
self.closed = true
end,
getSize = function(self)
return self._size
end,
size = function(self,w,h)
self._size = {w=w,h=h}
end,
setSize = function(self,w,h)
self._size = {w=w,h=h}
end,
getNamespace = function(self)
local ref = self
local namespace = ""
while ref.location.type == "container" do
namespace = ref.location.containerElement.name.."#"..namespace
ref = ref.location.parentState -- step near to the root
end
return namespace
end,
_buildFormspec_ = function(self,size)
local res = ""
if self._size and size then
@ -326,31 +350,64 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
meta:set_string("smartfs_name", self.def.name)
end
end,
-- Receive fields and actions from formspec
_sfs_on_receive_fields_ = function(self, player, fields)
assert(self)
assert(player)
for key,val in pairs(fields) do
if self._ele[key] then
self._ele[key].data.value = val
end
_get_element_recursive_ = function(self, field)
local topfield
for z in field:gmatch("[^#]+") do
topfield = z
break
end
for key,val in pairs(self._ele) do
if val.submit then
val:submit(fields, player)
local element = self._ele[topfield]
if element and field == topfield then
return element
elseif element then
if element._getSubElement_ then
local rel_field = string.sub(field, string.len(topfield)+2)
return element:_getSubElement_(rel_field)
else
return element
end
else
return nil
end
-- call onInput hook if enabled
end,
-- process onInput hook for the state
_sfs_process_oninput_ = function(self, fields, player)
if self._onInput then
self:_onInput(fields, player)
end
-- recursive all onInput hooks on visible containers
for elename, eledef in pairs(self._ele) do
if eledef.getContainerState and eledef:getVisible() then
eledef:getContainerState():_sfs_process_oninput_(fields, player)
end
end
end,
-- Receive fields and actions from formspec
_sfs_on_receive_fields_ = function(self, player, fields)
local fields_todo = {}
for field, value in pairs(fields) do
local element = self:_get_element_recursive_(field)
if element then
fields_todo[field] = { element = element, value = value }
end
end
for field, todo in pairs(fields_todo) do
todo.element:setValue(todo.value)
end
self:_sfs_process_oninput_(fields, player)
for field, todo in pairs(fields_todo) do
if todo.element.submit then
todo.element:submit(todo.value, player)
end
end
if not fields.quit and not self.closed then
self:_show_()
else
-- to be closed
self.players:disconnect(player)
if self.location.type == "player" then
smartfs.opened[player] = nil
@ -444,6 +501,9 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
getVisible = function(self)
return self.data.visible
end,
getAbsName = function(self)
return self.root:getNamespace()..self.name
end,
setBackground = function(self, image)
self.data.background = image
end,
@ -465,6 +525,9 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
return ""
end
end,
setValue = function(self, value)
self.data.value = value
end,
}
ele.data.visible = true --visible by default
@ -601,6 +664,20 @@ function smartfs._makeState_(form, newplayer, params, is_inv, nodepos)
name = name
})
end,
container = function(self, x, y, name, relative)
return self:element("container", {
pos = {x=x, y=y},
name = name,
relative = false
})
end,
view = function(self, x, y, name, relative)
return self:element("container", {
pos = {x=x, y=y},
name = name,
relative = true
})
end,
}
end
@ -645,15 +722,15 @@ smartfs.element("button", {
elseif self.data.item then
specstring = specstring..self.data.item..";"
end
specstring = specstring..self.name..";"..
specstring = specstring..self:getAbsName()..";"..
minetest.formspec_escape(self.data.value).."]"
if self.data.tooltip then
specstring = specstring.."tooltip["..self.name..";"..self.data.tooltip.."]"
specstring = specstring.."tooltip["..self:getAbsName()..";"..self.data.tooltip.."]"
end
return specstring
end,
submit = function(self, fields, player)
if fields[self.name] and self._click then
submit = function(self, field, player)
if self._click then
self:_click(self.root, player)
end
end,
@ -664,7 +741,7 @@ smartfs.element("button", {
self._click = func
end,
setText = function(self,text)
self.data.value = text
self:setValue(text)
end,
getText = function(self)
return self.data.value
@ -710,20 +787,18 @@ smartfs.element("toggle", {
";"..
self.data.size.w..","..self.data.size.h..
";"..
self.name..
self:getAbsName()..
";"..
minetest.formspec_escape(self.data.list[self.data.id])..
"]"
end,
submit = function(self, fields, player)
if fields[self.name] then
self.data.id = self.data.id + 1
if self.data.id > #self.data.list then
self.data.id = 1
end
if self._tog then
self:_tog(self.root, player)
end
submit = function(self, field, player)
self.data.id = self.data.id + 1
if self.data.id > #self.data.list then
self.data.id = 1
end
if self._tog then
self:_tog(self.root, player)
end
end,
onToggle = function(self,func)
@ -753,7 +828,7 @@ smartfs.element("label", {
"]"
end,
setText = function(self,text)
self.data.value = text
self:setValue(text)
end,
getText = function(self)
return self.data.value
@ -775,7 +850,7 @@ smartfs.element("field", {
";"..
self.data.size.w..","..self.data.size.h..
";"..
self.name..
self:getAbsName()..
";"..
minetest.formspec_escape(self.data.label)..
";"..
@ -787,7 +862,7 @@ smartfs.element("field", {
";"..
self.data.size.w..","..self.data.size.h..
";"..
self.name..
self:getAbsName()..
";"..
minetest.formspec_escape(self.data.label)..
"]"
@ -797,7 +872,7 @@ smartfs.element("field", {
";"..
self.data.size.w..","..self.data.size.h..
";"..
self.name..
self:getAbsName()..
";"..
minetest.formspec_escape(self.data.label)..
";"..
@ -806,7 +881,7 @@ smartfs.element("field", {
end
end,
setText = function(self,text)
self.data.value = text
self:setValue(text)
end,
getText = function(self)
return self.data.value
@ -842,7 +917,7 @@ smartfs.element("image", {
if self.data.imgtype == "background" then
self.data.background = text
else
self.data.value = text
self:setValue(text)
end
end,
getImage = function(self)
@ -862,26 +937,18 @@ smartfs.element("checkbox", {
self.data.label = self.data.label or ""
end,
build = function(self)
if self.data.value then
self.data.value = "true"
else
self.data.value = "false"
end
return "checkbox["..
self.data.pos.x..","..self.data.pos.y..
";"..
self.name..
self:getAbsName()..
";"..
minetest.formspec_escape(self.data.label)..
";" .. boolToStr(self.data.value) .."]"
end,
submit = function(self, fields, player)
if fields[self.name] then
-- self.data.value already set by value transfer
-- call the toggle function if defined
if self._tog then
self:_tog(self.root, player)
end
submit = function(self, field, player)
-- call the toggle function if defined
if self._tog then
self:_tog(self.root, player)
end
end,
setValue = function(self, value)
@ -913,7 +980,7 @@ smartfs.element("list", {
";"..
self.data.size.w..","..self.data.size.h..
";"..
self.data.name..
self:getAbsName()..
";"..
table.concat(self.data.items, ",")..
";"..
@ -923,16 +990,14 @@ smartfs.element("list", {
return listformspec
end,
submit = function(self, fields, player)
if fields[self.name] then
local _type = string.sub(fields[self.data.name], 1, 3)
local index = tonumber(string.sub(fields[self.data.name], 5))
self.data.selected = index
if _type == "CHG" and self._click then
self:_click(self.root, index, player)
elseif _type == "DCL" and self._doubleClick then
self:_doubleClick(self.root, index, player)
end
submit = function(self, field, player)
local _type = string.sub(field,1,3)
local index = tonumber(string.sub(field,5))
self.data.selected = index
if _type == "CHG" and self._click then
self:_click(self.root, index, player)
elseif _type == "DCL" and self._doubleClick then
self:_doubleClick(self.root, index, player)
end
end,
onClick = function(self, func)
@ -997,7 +1062,7 @@ smartfs.element("inventory", {
return "list["..
(self.data.location or "current_player") ..
";"..
self.name..
self.name.. --no namespacing
";"..
self.data.pos.x..","..self.data.pos.y..
";"..
@ -1046,9 +1111,9 @@ smartfs.element("code", {
return self.data.code
end,
submit = function(self, fields, player)
submit = function(self, field, player)
if self._sub then
self:_sub(self.root, fields, player)
self:_sub(self.root, field, player)
end
end,
onSubmit = function(self,func)
@ -1065,4 +1130,37 @@ smartfs.element("code", {
end
})
smartfs.element("container", {
onCreate = function(self)
assert(self.data.pos and self.data.pos.x and self.data.pos.y, "container needs valid pos")
assert(self.name, "container needs name")
self._state = smartfs._makeState_(self, nil, self.root.param)
end,
-- redefinitions. The size is not handled by data.size but by container-state:size
setSize = function(self,w,h)
self:getContainerState():setSize(w,h)
end,
getSize = function(self)
return self:getContainerState():getSize()
end,
-- element interface methods
build = function(self)
if self.data.relative ~= true then
return "container["..self.data.pos.x..","..self.data.pos.y.."]"..
self:getContainerState():_buildFormspec_(false)..
"container_end[]"
else
return self:getContainerState():_buildFormspec_(false)
end
end,
getContainerState = function(self)
return self._state
end,
_getSubElement_ = function(self, field)
return self:getContainerState():_get_element_recursive_(field)
end,
})
return smartfs