From 3de2eacda534c53f8634bc8a94e24bcab3ea18fb Mon Sep 17 00:00:00 2001 From: bell07 Date: Sat, 12 Aug 2017 01:01:35 +0200 Subject: [PATCH] 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 --- docs/API.md | 6 + docs/example-container.lua | 173 ++++++++++++++++++++++++++++ init.lua | 2 +- smartfs.lua | 226 ++++++++++++++++++++++++++----------- 4 files changed, 342 insertions(+), 65 deletions(-) create mode 100644 docs/example-container.lua diff --git a/docs/API.md b/docs/API.md index 5151e49..678d407 100644 --- a/docs/API.md +++ b/docs/API.md @@ -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 diff --git a/docs/example-container.lua b/docs/example-container.lua new file mode 100644 index 0000000..a57c089 --- /dev/null +++ b/docs/example-container.lua @@ -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) diff --git a/init.lua b/init.lua index 543915b..a4c516e 100644 --- a/init.lua +++ b/init.lua @@ -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) diff --git a/smartfs.lua b/smartfs.lua index 38fafee..4f7ff43 100644 --- a/smartfs.lua +++ b/smartfs.lua @@ -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