smartfs.lua 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536
  1. ---------------------------
  2. -- SmartFS: Smart Formspecs
  3. -- License: CC0 or WTFPL
  4. -- by Rubenwardy
  5. ---------------------------
  6. local smartfs = {
  7. _fdef = {},
  8. _edef = {},
  9. _ldef = {},
  10. opened = {},
  11. inv = {}
  12. }
  13. local function boolToStr(v)
  14. return v and "true" or "false"
  15. end
  16. -- the smartfs() function
  17. function smartfs.__call(self, name)
  18. return smartfs.get(name)
  19. end
  20. function smartfs.get(name)
  21. return smartfs._fdef[name]
  22. end
  23. ------------------------------------------------------
  24. -- Smartfs Interface - Creates a new form and adds elements to it by running the function. Use before Minetest loads. (like minetest.register_node)
  25. ------------------------------------------------------
  26. -- Register forms and elements
  27. function smartfs.create(name, onload)
  28. assert(not smartfs._fdef[name],
  29. "SmartFS - (Error) Form "..name.." already exists!")
  30. assert(not smartfs.loaded or smartfs._loaded_override,
  31. "SmartFS - (Error) Forms should be declared while the game loads.")
  32. smartfs._fdef[name] = {
  33. form_setup_callback = onload,
  34. name = name,
  35. show = smartfs._show_,
  36. attach_to_node = smartfs._attach_to_node_
  37. }
  38. return smartfs._fdef[name]
  39. end
  40. ------------------------------------------------------
  41. -- Smartfs Interface - Creates a new element type
  42. ------------------------------------------------------
  43. function smartfs.element(name, data)
  44. assert(not smartfs._edef[name],
  45. "SmartFS - (Error) Element type "..name.." already exists!")
  46. assert(data.onCreate, "element requires onCreate method")
  47. smartfs._edef[name] = data
  48. return smartfs._edef[name]
  49. end
  50. ------------------------------------------------------
  51. -- Smartfs Interface - Creates a dynamic form. Returns state
  52. ------------------------------------------------------
  53. function smartfs.dynamic(name,player)
  54. if not smartfs._dynamic_warned then
  55. smartfs._dynamic_warned = true
  56. minetest.log("warning", "SmartFS - (Warning) On the fly forms are being used. May cause bad things to happen")
  57. end
  58. local statelocation = smartfs._ldef.player._make_state_location_(player)
  59. local state = smartfs._makeState_({name=name}, nil, statelocation, player)
  60. smartfs.opened[player] = state
  61. return state
  62. end
  63. ------------------------------------------------------
  64. -- Smartfs Interface - Returns the name of an installed and supported inventory mod that will be used above, or nil
  65. ------------------------------------------------------
  66. function smartfs.inventory_mod()
  67. if minetest.global_exists("unified_inventory") then
  68. return "unified_inventory"
  69. elseif minetest.global_exists("inventory_plus") then
  70. return "inventory_plus"
  71. elseif minetest.global_exists("sfinv") then
  72. return "sfinv"
  73. else
  74. return nil
  75. end
  76. end
  77. ------------------------------------------------------
  78. -- Smartfs Interface - Adds a form to an installed advanced inventory. Returns true on success.
  79. ------------------------------------------------------
  80. function smartfs.add_to_inventory(form, icon, title, show_inv)
  81. local ldef
  82. local invmod = smartfs.inventory_mod()
  83. if invmod then
  84. ldef = smartfs._ldef[invmod]
  85. else
  86. return false
  87. end
  88. return ldef.add_to_inventory(form, icon, title, (show_inv == nil) and true or show_inv)
  89. end
  90. ------------------------------------------------------
  91. -- Smartfs Interface - Set the form as players inventory
  92. ------------------------------------------------------
  93. function smartfs.set_player_inventory(form)
  94. smartfs._ldef.inventory.set_inventory(form)
  95. end
  96. ------------------------------------------------------
  97. -- Smartfs Interface - Allows you to use smartfs.create after the game loads. Not recommended!
  98. ------------------------------------------------------
  99. function smartfs.override_load_checks()
  100. smartfs._loaded_override = true
  101. end
  102. ------------------------------------------------------
  103. -- Smartfs formspec locations
  104. ------------------------------------------------------
  105. -- Unified inventory plugin
  106. smartfs._ldef.unified_inventory = {
  107. add_to_inventory = function(form, icon, title, show_inv)
  108. unified_inventory.register_button(form.name, {
  109. type = "image",
  110. image = icon,
  111. })
  112. unified_inventory.register_page(form.name, {
  113. get_formspec = function(player, formspec)
  114. local name = player:get_player_name()
  115. local state
  116. if smartfs.inv[name] and smartfs.inv[name].def.name == form.name then
  117. state = smartfs.inv[name]
  118. else
  119. local statelocation = smartfs._ldef.unified_inventory._make_state_location_(name)
  120. state = smartfs._makeState_(form, nil, statelocation, name)
  121. if form.form_setup_callback(state) ~= false then
  122. smartfs.inv[name] = state
  123. else
  124. smartfs.inv[name] = nil
  125. return ""
  126. end
  127. end
  128. return {formspec = state:_buildFormspec_(false), draw_inventory = show_inv}
  129. end
  130. })
  131. end,
  132. _make_state_location_ = function(player)
  133. return {
  134. type = "inventory",
  135. player = player,
  136. _show_ = function(state)
  137. unified_inventory.set_inventory_formspec(minetest.get_player_by_name(state.location.player), state.def.name)
  138. end
  139. }
  140. end
  141. }
  142. -- Inventory plus plugin
  143. smartfs._ldef.inventory_plus = {
  144. add_to_inventory = function(form, icon, title)
  145. minetest.register_on_joinplayer(function(player)
  146. inventory_plus.register_button(player, form.name, title)
  147. end)
  148. minetest.register_on_player_receive_fields(function(player, formname, fields)
  149. if formname == "" and fields[form.name] then
  150. local name = player:get_player_name()
  151. local statelocation = smartfs._ldef.inventory_plus._make_state_location_(name)
  152. local state = smartfs._makeState_(form, nil, statelocation, name)
  153. if form.form_setup_callback(state) ~= false then
  154. smartfs.inv[name] = state
  155. state:show()
  156. end
  157. end
  158. end)
  159. end,
  160. _make_state_location_ = function(player)
  161. return {
  162. type = "inventory",
  163. player = player,
  164. _show_ = function(state)
  165. inventory_plus.set_inventory_formspec(minetest.get_player_by_name(state.location.player), state:_buildFormspec_(true))
  166. end
  167. }
  168. end
  169. }
  170. -- Sfinv plugin
  171. smartfs._ldef.sfinv = {
  172. add_to_inventory = function(form, icon, title, show_inv)
  173. sfinv.register_page(form.name, {
  174. title = title,
  175. get = function(self, player, context)
  176. local name = player:get_player_name()
  177. local state
  178. if smartfs.inv[name] then
  179. state = smartfs.inv[name]
  180. else
  181. local statelocation = smartfs._ldef.sfinv._make_state_location_(name)
  182. state = smartfs._makeState_(form, nil, statelocation, name)
  183. smartfs.inv[name] = state
  184. if form.form_setup_callback(state) ~= false then
  185. smartfs.inv[name] = state
  186. else
  187. return ""
  188. end
  189. end
  190. local fs = state:_buildFormspec_(false)
  191. return sfinv.make_formspec(player, context, fs, show_inv)
  192. end,
  193. on_player_receive_fields = function(self, player, _, fields)
  194. local name = player:get_player_name()
  195. if smartfs.inv[name] then
  196. smartfs.inv[name]:_sfs_on_receive_fields_(name, fields)
  197. end
  198. end,
  199. on_leave = function(self, player)
  200. local name = player:get_player_name()
  201. if smartfs.inv[name] then
  202. smartfs.inv[name].players:disconnect(name)
  203. smartfs.inv[name] = nil
  204. end
  205. end,
  206. })
  207. end,
  208. _make_state_location_ = function(player)
  209. return {
  210. type = "inventory",
  211. inventory_handles_fields = true,
  212. player = player,
  213. _show_ = function(state)
  214. sfinv.set_player_inventory_formspec(minetest.get_player_by_name(state.location.player))
  215. end,
  216. }
  217. end
  218. }
  219. -- Show to player
  220. smartfs._ldef.player = {
  221. _make_state_location_ = function(player)
  222. return {
  223. type = "player",
  224. player = player,
  225. _show_ = function(state)
  226. if not state._show_queued then
  227. state._show_queued = true
  228. minetest.after(0, function(state)
  229. if state then
  230. state._show_queued = nil
  231. if (not state.closed) and (not state.obsolete) then
  232. minetest.show_formspec(state.location.player, state.def.name, state:_buildFormspec_(true))
  233. end
  234. end
  235. end, state) -- state given as reference. Maybe additional updates are done in the meantime or the form is obsolete
  236. end
  237. end
  238. }
  239. end
  240. }
  241. -- Standalone inventory
  242. smartfs._ldef.inventory = {
  243. set_inventory = function(form)
  244. if sfinv and sfinv.enabled then
  245. sfinv.enabled = nil
  246. end
  247. minetest.register_on_joinplayer(function(player)
  248. local name = player:get_player_name()
  249. local statelocation = smartfs._ldef.inventory._make_state_location_(name)
  250. local state = smartfs._makeState_(form, nil, statelocation, name)
  251. if form.form_setup_callback(state) ~= false then
  252. smartfs.inv[name] = state
  253. state:show()
  254. end
  255. end)
  256. minetest.register_on_leaveplayer(function(player)
  257. local name = player:get_player_name()
  258. smartfs.inv[name].obsolete = true
  259. smartfs.inv[name] = nil
  260. end)
  261. end,
  262. _make_state_location_ = function(name)
  263. return {
  264. type = "inventory",
  265. player = name,
  266. _show_ = function(state)
  267. if not state._show_queued then
  268. state._show_queued = true
  269. minetest.after(0, function(state)
  270. if state then
  271. state._show_queued = nil
  272. local player = minetest.get_player_by_name(state.location.player)
  273. --print("smartfs formspec:", state:_buildFormspec_(true))
  274. player:set_inventory_formspec(state:_buildFormspec_(true))
  275. end
  276. end, state)
  277. end
  278. end
  279. }
  280. end
  281. }
  282. -- Node metadata
  283. smartfs._ldef.nodemeta = {
  284. _make_state_location_ = function(nodepos)
  285. return {
  286. type = "nodemeta",
  287. pos = nodepos,
  288. _show_ = function(state)
  289. if not state._show_queued then
  290. state._show_queued = true
  291. minetest.after(0, function(state)
  292. if state then
  293. state._show_queued = nil
  294. local meta = minetest.get_meta(state.location.pos)
  295. meta:set_string("formspec", state:_buildFormspec_(true))
  296. meta:set_string("smartfs_name", state.def.name)
  297. meta:mark_as_private("smartfs_name")
  298. end
  299. end, state)
  300. end
  301. end,
  302. }
  303. end
  304. }
  305. -- Sub-container (internally used)
  306. smartfs._ldef.container = {
  307. _make_state_location_ = function(element)
  308. local self = {
  309. type = "container",
  310. containerElement = element,
  311. parentState = element.root
  312. }
  313. if self.parentState.location.type == "container" then
  314. self.rootState = self.parentState.location.rootState
  315. else
  316. self.rootState = self.parentState
  317. end
  318. return self
  319. end
  320. }
  321. ------------------------------------------------------
  322. -- Minetest Interface - on_receive_fields callback can be used in minetest.register_node for nodemeta forms
  323. ------------------------------------------------------
  324. function smartfs.nodemeta_on_receive_fields(nodepos, formname, fields, sender, params)
  325. local meta = minetest.get_meta(nodepos)
  326. local nodeform = meta:get_string("smartfs_name")
  327. if not nodeform then
  328. print("SmartFS - (Warning) smartfs.nodemeta_on_receive_fields for node without smarfs data")
  329. return false
  330. end
  331. -- get the currentsmartfs state
  332. local opened_id = minetest.pos_to_string(nodepos)
  333. local state
  334. local form = smartfs.get(nodeform)
  335. if not smartfs.opened[opened_id] or -- If opened first time
  336. smartfs.opened[opened_id].def.name ~= nodeform or -- Or form is changed
  337. smartfs.opened[opened_id].obsolete then
  338. local statelocation = smartfs._ldef.nodemeta._make_state_location_(nodepos)
  339. state = smartfs._makeState_(form, params, statelocation)
  340. if smartfs.opened[opened_id] then
  341. smartfs.opened[opened_id].obsolete = true
  342. end
  343. smartfs.opened[opened_id] = state
  344. form.form_setup_callback(state)
  345. else
  346. state = smartfs.opened[opened_id]
  347. end
  348. -- Set current sender check for multiple users on node
  349. local name
  350. if sender then
  351. name = sender:get_player_name()
  352. state.players:connect(name)
  353. end
  354. -- take the input
  355. state:_sfs_on_receive_fields_(name, fields)
  356. -- Reset form if all players disconnected
  357. if sender and not state.players:get_first() and not state.obsolete then
  358. local statelocation = smartfs._ldef.nodemeta._make_state_location_(nodepos)
  359. local resetstate = smartfs._makeState_(form, params, statelocation)
  360. if form.form_setup_callback(resetstate) ~= false then
  361. resetstate:show()
  362. end
  363. smartfs.opened[opened_id] = nil
  364. end
  365. end
  366. ------------------------------------------------------
  367. -- Minetest Interface - on_player_receive_fields callback in case of inventory or player
  368. ------------------------------------------------------
  369. minetest.register_on_player_receive_fields(function(player, formname, fields)
  370. local name = player:get_player_name()
  371. if smartfs.opened[name] and smartfs.opened[name].location.type == "player" then
  372. if smartfs.opened[name].def.name == formname then
  373. local state = smartfs.opened[name]
  374. state:_sfs_on_receive_fields_(name, fields)
  375. -- disconnect player if form closed
  376. if not state.players:get_first() then
  377. smartfs.opened[name].obsolete = true
  378. smartfs.opened[name] = nil
  379. end
  380. end
  381. elseif smartfs.inv[name] and smartfs.inv[name].location.type == "inventory" and not smartfs.inv[name].location.inventory_handles_fields then
  382. local state = smartfs.inv[name]
  383. state:_sfs_on_receive_fields_(name, fields)
  384. end
  385. return false
  386. end)
  387. ------------------------------------------------------
  388. -- Minetest Interface - Notify loading of smartfs is done
  389. ------------------------------------------------------
  390. minetest.after(0, function()
  391. smartfs.loaded = true
  392. end)
  393. ------------------------------------------------------
  394. -- Form Interface [linked to form:show()] - Shows the form to a player
  395. ------------------------------------------------------
  396. function smartfs._show_(form, name, params)
  397. assert(form)
  398. assert(type(name) == "string", "smartfs: name needs to be a string")
  399. assert(minetest.get_player_by_name(name), "player does not exist")
  400. local statelocation = smartfs._ldef.player._make_state_location_(name)
  401. local state = smartfs._makeState_(form, params, statelocation, name)
  402. if form.form_setup_callback(state) ~= false then
  403. if smartfs.opened[name] then -- set maybe previous form to obsolete
  404. smartfs.opened[name].obsolete = true
  405. end
  406. smartfs.opened[name] = state
  407. state:show()
  408. end
  409. return state
  410. end
  411. ------------------------------------------------------
  412. -- Form Interface [linked to form:attach_to_node()] - Attach a formspec to a node meta
  413. ------------------------------------------------------
  414. function smartfs._attach_to_node_(form, nodepos, params)
  415. assert(form)
  416. assert(nodepos and nodepos.x)
  417. local statelocation = smartfs._ldef.nodemeta._make_state_location_(nodepos)
  418. local state = smartfs._makeState_(form, params, statelocation)
  419. if form.form_setup_callback(state) ~= false then
  420. local opened_id = minetest.pos_to_string(nodepos)
  421. if smartfs.opened[opened_id] then -- set maybe previous form to obsolete
  422. smartfs.opened[opened_id].obsolete = true
  423. end
  424. state:show()
  425. end
  426. return state
  427. end
  428. ------------------------------------------------------
  429. -- Smartfs Framework - Element class Methods
  430. ------------------------------------------------------
  431. local element_class = {}
  432. local element_class_mt = { __index = element_class }
  433. function element_class:remove()
  434. self.root._ele[self.name] = nil
  435. end
  436. function element_class:setPosition(x,y)
  437. self.data.pos = {x=x,y=y}
  438. end
  439. function element_class:getPosition()
  440. return self.data.pos
  441. end
  442. function element_class:setSize(w,h)
  443. self.data.size = {w=w,h=h}
  444. end
  445. function element_class:getSize()
  446. return self.data.size
  447. end
  448. function element_class:setVisible(visible)
  449. if visible == nil then
  450. self.data.visible = true
  451. else
  452. self.data.visible = visible
  453. end
  454. end
  455. function element_class:getVisible()
  456. return self.data.visible
  457. end
  458. function element_class:getAbsName()
  459. return self.root:getNamespace()..self.name
  460. end
  461. function element_class:setBackground(image)
  462. self.data.background = image
  463. end
  464. function element_class:getBackground()
  465. return self.data.background
  466. end
  467. function element_class:getBackgroundString()
  468. if self.data.background then
  469. local size = self:getSize()
  470. if size then
  471. return "background["..
  472. self.data.pos.x..","..self.data.pos.y..";"..
  473. size.w..","..size.h..";"..
  474. self.data.background.."]"
  475. else
  476. return ""
  477. end
  478. else
  479. return ""
  480. end
  481. end
  482. function element_class:setValue(value)
  483. self.data.value = value
  484. end
  485. function element_class:setTooltip(text)
  486. self.data.tooltip = minetest.formspec_escape(text)
  487. end
  488. function element_class:getTooltip()
  489. return self.data.tooltip
  490. end
  491. function element_class:getTooltipString()
  492. if self.data.tooltip then
  493. return "tooltip["..self:getAbsName()..";"..self:getTooltip().."]"
  494. else
  495. return ""
  496. end
  497. end
  498. ------------------------------------------------------
  499. -- Smartfs Framework - State class Methods
  500. ------------------------------------------------------
  501. local state_class = {}
  502. local state_class_mt = { __index = state_class }
  503. function state_class:get(name)
  504. return self._ele[name]
  505. end
  506. function state_class:close()
  507. self.closed = true
  508. end
  509. function state_class:getSize()
  510. return self._size
  511. end
  512. function state_class:size(w,h)
  513. self._size = {w=w,h=h}
  514. end
  515. state_class.setSize = state_class.size
  516. function state_class:getNamespace()
  517. local ref = self
  518. local namespace = ""
  519. while ref.location.type == "container" do
  520. namespace = ref.location.containerElement.name.."#"..namespace
  521. ref = ref.location.parentState -- step near to the root
  522. end
  523. return namespace
  524. end
  525. function state_class:_buildFormspec_(size)
  526. local res = ""
  527. if self._size and size then
  528. res = "size["..self._size.w..","..self._size.h.."]"
  529. end
  530. for key,val in pairs(self._ele) do
  531. if val:getVisible() then
  532. res = res .. val:getBackgroundString() .. val:build() .. val:getTooltipString()
  533. end
  534. end
  535. return res
  536. end
  537. function state_class:_get_element_recursive_(field)
  538. local topfield
  539. for z in field:gmatch("[^#]+") do
  540. topfield = z
  541. break
  542. end
  543. local element = self._ele[topfield]
  544. if element and field == topfield then
  545. return element
  546. elseif element then
  547. if element._getSubElement_ then
  548. local rel_field = string.sub(field, string.len(topfield)+2)
  549. return element:_getSubElement_(rel_field)
  550. else
  551. return element
  552. end
  553. else
  554. return nil
  555. end
  556. end
  557. -- process onInput hook for the state
  558. function state_class:_sfs_process_oninput_(fields, player)
  559. if self._onInput then
  560. self:_onInput(fields, player)
  561. end
  562. -- recursive all onInput hooks on visible containers
  563. for elename, eledef in pairs(self._ele) do
  564. if eledef.getContainerState and eledef:getVisible() then
  565. eledef:getContainerState():_sfs_process_oninput_(fields, player)
  566. end
  567. end
  568. end
  569. -- Receive fields and actions from formspec
  570. function state_class:_sfs_on_receive_fields_(player, fields)
  571. local fields_todo = {}
  572. for field, value in pairs(fields) do
  573. local element = self:_get_element_recursive_(field)
  574. if element then
  575. fields_todo[field] = { element = element, value = value }
  576. end
  577. end
  578. for field, todo in pairs(fields_todo) do
  579. todo.element:setValue(todo.value)
  580. end
  581. self:_sfs_process_oninput_(fields, player)
  582. for field, todo in pairs(fields_todo) do
  583. if todo.element.submit then
  584. todo.element:submit(todo.value, player)
  585. end
  586. end
  587. -- handle key_enter
  588. if fields.key_enter and fields.key_enter_field then
  589. local element = self:_get_element_recursive_(fields.key_enter_field)
  590. if element and element.submit_key_enter then
  591. element:submit_key_enter(fields[fields.key_enter_field], player)
  592. end
  593. end
  594. if not fields.quit and not self.closed and not self.obsolete then
  595. self:show()
  596. else
  597. self.players:disconnect(player)
  598. if not fields.quit and self.closed and not self.obsolete then
  599. --closed by application (without fields.quit). currently not supported, see: https://github.com/minetest/minetest/pull/4675
  600. minetest.show_formspec(player,"","size[5,1]label[0,0;Formspec closing not yet created!]")
  601. end
  602. end
  603. return true
  604. end
  605. function state_class:onInput(func)
  606. self._onInput = func -- (fields, player)
  607. end
  608. function state_class:load(file)
  609. local file = io.open(file, "r")
  610. if file then
  611. local table = minetest.deserialize(file:read("*all"))
  612. if type(table) == "table" then
  613. if table.size then
  614. self._size = table.size
  615. end
  616. for key,val in pairs(table.ele) do
  617. self:element(val.type,val)
  618. end
  619. return true
  620. end
  621. end
  622. return false
  623. end
  624. function state_class:save(file)
  625. local res = {ele={}}
  626. if self._size then
  627. res.size = self._size
  628. end
  629. for key,val in pairs(self._ele) do
  630. res.ele[key] = val.data
  631. end
  632. local file = io.open(file, "w")
  633. if file then
  634. file:write(minetest.serialize(res))
  635. file:close()
  636. return true
  637. end
  638. return false
  639. end
  640. function state_class:setparam(key,value)
  641. if not key then return end
  642. self.param[key] = value
  643. return true
  644. end
  645. function state_class:getparam(key,default)
  646. if not key then return end
  647. return self.param[key] or default
  648. end
  649. function state_class:element(typen,data)
  650. local type = smartfs._edef[typen]
  651. assert(type, "Element type "..typen.." does not exist!")
  652. assert(not self._ele[data.name], "Element "..data.name.." already exists")
  653. local ele = setmetatable({}, element_class_mt)
  654. ele.name = data.name
  655. ele.root = self
  656. ele.data = data
  657. ele.data.type = typen
  658. ele.data.visible = true
  659. for key, val in pairs(type) do
  660. ele[key] = val
  661. end
  662. self._ele[data.name] = ele
  663. type.onCreate(ele)
  664. return self._ele[data.name]
  665. end
  666. ------------------------------------------------------
  667. -- Smartfs Framework - State class Methods - Build time only
  668. ------------------------------------------------------
  669. function state_class:button(x, y, w, h, name, text, exitf)
  670. return self:element("button", {
  671. pos = {x=x,y=y},
  672. size = {w=w,h=h},
  673. name = name,
  674. value = text,
  675. closes = exitf or false
  676. })
  677. end
  678. function state_class:image_button(x, y, w, h, name, text, image, exitf)
  679. return self:element("button", {
  680. pos = {x=x,y=y},
  681. size = {w=w,h=h},
  682. name = name,
  683. value = text,
  684. image = image,
  685. closes = exitf or false
  686. })
  687. end
  688. function state_class:item_image_button(x, y, w, h, name, text, item, exitf)
  689. return self:element("button", {
  690. pos = {x=x,y=y},
  691. size = {w=w,h=h},
  692. name = name,
  693. value = text,
  694. item = item,
  695. closes = exitf or false
  696. })
  697. end
  698. function state_class:label(x, y, name, text)
  699. return self:element("label", {
  700. pos = {x=x,y=y},
  701. name = name,
  702. value = text,
  703. vertical = false
  704. })
  705. end
  706. function state_class:vertlabel(x, y, name, text)
  707. return self:element("label", {
  708. pos = {x=x,y=y},
  709. name = name,
  710. value = text,
  711. vertical = true
  712. })
  713. end
  714. function state_class:toggle(x, y, w, h, name, list)
  715. return self:element("toggle", {
  716. pos = {x=x, y=y},
  717. size = {w=w, h=h},
  718. name = name,
  719. id = 1,
  720. list = list
  721. })
  722. end
  723. function state_class:field(x, y, w, h, name, label)
  724. return self:element("field", {
  725. pos = {x=x, y=y},
  726. size = {w=w, h=h},
  727. name = name,
  728. value = "",
  729. label = label
  730. })
  731. end
  732. function state_class:pwdfield(x, y, w, h, name, label)
  733. local res = self:element("field", {
  734. pos = {x=x, y=y},
  735. size = {w=w, h=h},
  736. name = name,
  737. value = "",
  738. label = label
  739. })
  740. res:isPassword(true)
  741. return res
  742. end
  743. function state_class:textarea(x, y, w, h, name, label)
  744. local res = self:element("field", {
  745. pos = {x=x, y=y},
  746. size = {w=w, h=h},
  747. name = name,
  748. value = "",
  749. label = label
  750. })
  751. res:isMultiline(true)
  752. return res
  753. end
  754. function state_class:image(x, y, w, h, name, img)
  755. return self:element("image", {
  756. pos = {x=x, y=y},
  757. size = {w=w, h=h},
  758. name = name,
  759. value = img,
  760. imgtype = "image"
  761. })
  762. end
  763. function state_class:background(x, y, w, h, name, img)
  764. return self:element("image", {
  765. pos = {x=x, y=y},
  766. size = {w=w, h=h},
  767. name = name,
  768. background = img,
  769. imgtype = "background"
  770. })
  771. end
  772. function state_class:item_image(x, y, w, h, name, img)
  773. return self:element("image", {
  774. pos = {x=x, y=y},
  775. size = {w=w, h=h},
  776. name = name,
  777. value = img,
  778. imgtype = "item"
  779. })
  780. end
  781. function state_class:checkbox(x, y, name, label, selected)
  782. return self:element("checkbox", {
  783. pos = {x=x, y=y},
  784. name = name,
  785. value = selected,
  786. label = label
  787. })
  788. end
  789. function state_class:listbox(x, y, w, h, name, selected, transparent)
  790. return self:element("list", {
  791. pos = {x=x, y=y},
  792. size = {w=w, h=h},
  793. name = name,
  794. selected = selected,
  795. transparent = transparent
  796. })
  797. end
  798. function state_class:dropdown(x, y, w, h, name, selected)
  799. return self:element("dropdown", {
  800. pos = {x=x, y=y},
  801. size = {w=w, h=h},
  802. name = name,
  803. selected = selected
  804. })
  805. end
  806. function state_class:inventory(x, y, w, h, name)
  807. return self:element("inventory", {
  808. pos = {x=x, y=y},
  809. size = {w=w, h=h},
  810. name = name
  811. })
  812. end
  813. function state_class:container(x, y, name, relative)
  814. return self:element("container", {
  815. pos = {x=x, y=y},
  816. name = name,
  817. relative = false
  818. })
  819. end
  820. function state_class:view(x, y, name, relative)
  821. return self:element("container", {
  822. pos = {x=x, y=y},
  823. name = name,
  824. relative = true
  825. })
  826. end
  827. ------------------------------------------------------
  828. -- Smartfs Framework - create a form object (state)
  829. ------------------------------------------------------
  830. function smartfs._makeState_(form, params, location, newplayer)
  831. ------------------------------------------------------
  832. -- State - -- Object to manage players
  833. ------------------------------------------------------
  834. local function _make_players_(newplayer)
  835. local self = {
  836. _list = {}
  837. }
  838. function self.connect(self, player)
  839. self._list[player] = true
  840. end
  841. function self.disconnect(self, player)
  842. self._list[player] = nil
  843. end
  844. function self.get_first(self)
  845. return next(self._list)
  846. end
  847. if newplayer then
  848. self:connect(newplayer)
  849. end
  850. return self
  851. end
  852. local state = {
  853. _ele = {},
  854. def = form,
  855. players = _make_players_(newplayer),
  856. location = location,
  857. is_inv = (location.type == "inventory"), -- obsolete. Please use location.type="inventory" instead
  858. player = newplayer, -- obsolete. Please use location.player
  859. param = params or {},
  860. show = location._show_,
  861. }
  862. return setmetatable(state, state_class_mt)
  863. end
  864. -----------------------------------------------------------------
  865. ------------------------- ELEMENTS ----------------------------
  866. -----------------------------------------------------------------
  867. smartfs.element("button", {
  868. onCreate = function(self)
  869. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "button needs valid pos")
  870. assert(self.data.size and self.data.size.w and self.data.size.h, "button needs valid size")
  871. assert(self.name, "button needs name")
  872. assert(self.data.value, "button needs label")
  873. end,
  874. build = function(self)
  875. local specstring
  876. if self.data.image then
  877. if self.data.closes then
  878. specstring = "image_button_exit["
  879. else
  880. specstring = "image_button["
  881. end
  882. elseif self.data.item then
  883. if self.data.closes then
  884. specstring = "item_image_button_exit["
  885. else
  886. specstring = "item_image_button["
  887. end
  888. else
  889. if self.data.closes then
  890. specstring = "button_exit["
  891. else
  892. specstring = "button["
  893. end
  894. end
  895. specstring = specstring ..
  896. self.data.pos.x..","..self.data.pos.y..";"..
  897. self.data.size.w..","..self.data.size.h..";"
  898. if self.data.image then
  899. specstring = specstring..minetest.formspec_escape(self.data.image)..";"
  900. elseif self.data.item then
  901. specstring = specstring..self.data.item..";"
  902. end
  903. specstring = specstring..self:getAbsName()..";"..
  904. minetest.formspec_escape(self.data.value).."]"
  905. return specstring
  906. end,
  907. submit = function(self, field, player)
  908. if self._click then
  909. self:_click(self.root, player)
  910. end
  911. end,
  912. onClick = function(self,func)
  913. self._click = func
  914. end,
  915. click = function(self,func)
  916. self._click = func
  917. end,
  918. setText = function(self,text)
  919. self:setValue(text)
  920. end,
  921. getText = function(self)
  922. return self.data.value
  923. end,
  924. setImage = function(self,image)
  925. self.data.image = image
  926. self.data.item = nil
  927. end,
  928. getImage = function(self)
  929. return self.data.image
  930. end,
  931. setItem = function(self,item)
  932. self.data.item = item
  933. self.data.image = nil
  934. end,
  935. getItem = function(self)
  936. return self.data.item
  937. end,
  938. setClose = function(self,bool)
  939. self.data.closes = bool
  940. end,
  941. getClose = function(self)
  942. return self.data.closes or false
  943. end
  944. })
  945. smartfs.element("toggle", {
  946. onCreate = function(self)
  947. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "toggle needs valid pos")
  948. assert(self.data.size and self.data.size.w and self.data.size.h, "toggle needs valid size")
  949. assert(self.name, "toggle needs name")
  950. assert(self.data.list, "toggle needs data")
  951. end,
  952. build = function(self)
  953. return "button["..
  954. self.data.pos.x..","..self.data.pos.y..
  955. ";"..
  956. self.data.size.w..","..self.data.size.h..
  957. ";"..
  958. self:getAbsName()..
  959. ";"..
  960. minetest.formspec_escape(self.data.list[self.data.id])..
  961. "]"
  962. end,
  963. submit = function(self, field, player)
  964. self.data.id = self.data.id + 1
  965. if self.data.id > #self.data.list then
  966. self.data.id = 1
  967. end
  968. if self._tog then
  969. self:_tog(self.root, player)
  970. end
  971. end,
  972. onToggle = function(self,func)
  973. self._tog = func
  974. end,
  975. setId = function(self,id)
  976. self.data.id = id
  977. end,
  978. getId = function(self)
  979. return self.data.id
  980. end,
  981. getText = function(self)
  982. return self.data.list[self.data.id]
  983. end
  984. })
  985. smartfs.element("label", {
  986. onCreate = function(self)
  987. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "label needs valid pos")
  988. assert(self.data.value, "label needs text")
  989. end,
  990. build = function(self)
  991. local specstring
  992. if self.data.vertical then
  993. specstring = "vertlabel["
  994. else
  995. specstring = "label["
  996. end
  997. return specstring..
  998. self.data.pos.x..","..self.data.pos.y..
  999. ";"..
  1000. minetest.formspec_escape(self.data.value)..
  1001. "]"
  1002. end,
  1003. setText = function(self,text)
  1004. self:setValue(text)
  1005. end,
  1006. getText = function(self)
  1007. return self.data.value
  1008. end
  1009. })
  1010. smartfs.element("field", {
  1011. onCreate = function(self)
  1012. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "field needs valid pos")
  1013. assert(self.data.size and self.data.size.w and self.data.size.h, "field needs valid size")
  1014. assert(self.name, "field needs name")
  1015. self.data.value = self.data.value or ""
  1016. self.data.label = self.data.label or ""
  1017. end,
  1018. build = function(self)
  1019. if self.data.ml then
  1020. return "textarea["..
  1021. self.data.pos.x..","..self.data.pos.y..
  1022. ";"..
  1023. self.data.size.w..","..self.data.size.h..
  1024. ";"..
  1025. self:getAbsName()..
  1026. ";"..
  1027. minetest.formspec_escape(self.data.label)..
  1028. ";"..
  1029. minetest.formspec_escape(self.data.value)..
  1030. "]"
  1031. elseif self.data.pwd then
  1032. return "pwdfield["..
  1033. self.data.pos.x..","..self.data.pos.y..
  1034. ";"..
  1035. self.data.size.w..","..self.data.size.h..
  1036. ";"..
  1037. self:getAbsName()..
  1038. ";"..
  1039. minetest.formspec_escape(self.data.label)..
  1040. "]"..
  1041. self:getCloseOnEnterString()
  1042. else
  1043. return "field["..
  1044. self.data.pos.x..","..self.data.pos.y..
  1045. ";"..
  1046. self.data.size.w..","..self.data.size.h..
  1047. ";"..
  1048. self:getAbsName()..
  1049. ";"..
  1050. minetest.formspec_escape(self.data.label)..
  1051. ";"..
  1052. minetest.formspec_escape(self.data.value)..
  1053. "]"..
  1054. self:getCloseOnEnterString()
  1055. end
  1056. end,
  1057. setText = function(self,text)
  1058. self:setValue(text)
  1059. end,
  1060. getText = function(self)
  1061. return self.data.value
  1062. end,
  1063. isPassword = function(self,bool)
  1064. self.data.pwd = bool
  1065. end,
  1066. isMultiline = function(self,bool)
  1067. self.data.ml = bool
  1068. end,
  1069. getCloseOnEnterString = function(self)
  1070. if self.close_on_enter == nil then
  1071. return ""
  1072. else
  1073. return "field_close_on_enter["..self:getAbsName()..";"..tostring(self.close_on_enter).."]"
  1074. end
  1075. end,
  1076. setCloseOnEnter = function(self, value)
  1077. self.close_on_enter = value
  1078. end,
  1079. getCloseOnEnter = function(self)
  1080. return self.close_on_enter
  1081. end,
  1082. submit_key_enter = function(self, field, player)
  1083. if self._key_enter then
  1084. self:_key_enter(self.root, player)
  1085. end
  1086. end,
  1087. onKeyEnter = function(self,func)
  1088. self._key_enter = func
  1089. end,
  1090. })
  1091. smartfs.element("image", {
  1092. onCreate = function(self)
  1093. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "image needs valid pos")
  1094. assert(self.data.size and self.data.size.w and self.data.size.h, "image needs valid size")
  1095. self.data.value = self.data.value or ""
  1096. end,
  1097. build = function(self)
  1098. if self.data.imgtype == "background" then
  1099. return "" -- handled in _buildFormspec_ trough getBackgroundString()
  1100. elseif self.data.imgtype == "item" then
  1101. return "item_image["..
  1102. self.data.pos.x..","..self.data.pos.y..
  1103. ";"..
  1104. self.data.size.w..","..self.data.size.h..
  1105. ";"..
  1106. minetest.formspec_escape(self.data.value)..
  1107. "]"
  1108. else
  1109. return "image["..
  1110. self.data.pos.x..","..self.data.pos.y..
  1111. ";"..
  1112. self.data.size.w..","..self.data.size.h..
  1113. ";"..
  1114. minetest.formspec_escape(self.data.value)..
  1115. "]"
  1116. end
  1117. end,
  1118. setImage = function(self,text)
  1119. if self.data.imgtype == "background" then
  1120. self.data.background = text
  1121. else
  1122. self:setValue(text)
  1123. end
  1124. end,
  1125. getImage = function(self)
  1126. if self.data.imgtype == "background" then
  1127. return self.data.background
  1128. else
  1129. return self.data.value
  1130. end
  1131. end
  1132. })
  1133. smartfs.element("checkbox", {
  1134. onCreate = function(self)
  1135. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "checkbox needs valid pos")
  1136. assert(self.name, "checkbox needs name")
  1137. self.data.value = minetest.is_yes(self.data.value)
  1138. self.data.label = self.data.label or ""
  1139. end,
  1140. build = function(self)
  1141. return "checkbox["..
  1142. self.data.pos.x..","..self.data.pos.y..
  1143. ";"..
  1144. self:getAbsName()..
  1145. ";"..
  1146. minetest.formspec_escape(self.data.label)..
  1147. ";" .. boolToStr(self.data.value) .."]"
  1148. end,
  1149. submit = function(self, field, player)
  1150. -- call the toggle function if defined
  1151. if self._tog then
  1152. self:_tog(self.root, player)
  1153. end
  1154. end,
  1155. setValue = function(self, value)
  1156. self.data.value = minetest.is_yes(value)
  1157. end,
  1158. getValue = function(self)
  1159. return self.data.value
  1160. end,
  1161. onToggle = function(self,func)
  1162. self._tog = func
  1163. end,
  1164. })
  1165. smartfs.element("list", {
  1166. onCreate = function(self)
  1167. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "list needs valid pos")
  1168. assert(self.data.size and self.data.size.w and self.data.size.h, "list needs valid size")
  1169. assert(self.name, "list needs name")
  1170. self.data.value = minetest.is_yes(self.data.value)
  1171. self.data.items = self.data.items or {}
  1172. end,
  1173. build = function(self)
  1174. if not self.data.items then
  1175. self.data.items = {}
  1176. end
  1177. return "textlist["..
  1178. self.data.pos.x..","..self.data.pos.y..
  1179. ";"..
  1180. self.data.size.w..","..self.data.size.h..
  1181. ";"..
  1182. self:getAbsName()..
  1183. ";"..
  1184. table.concat(self.data.items, ",")..
  1185. ";"..
  1186. tostring(self.data.selected or "")..
  1187. ";"..
  1188. tostring(self.data.transparent or "false").."]"
  1189. end,
  1190. submit = function(self, field, player)
  1191. local _type = string.sub(field,1,3)
  1192. local index = tonumber(string.sub(field,5))
  1193. self.data.selected = index
  1194. if _type == "CHG" and self._click then
  1195. self:_click(self.root, index, player)
  1196. elseif _type == "DCL" and self._doubleClick then
  1197. self:_doubleClick(self.root, index, player)
  1198. end
  1199. end,
  1200. onClick = function(self, func)
  1201. self._click = func
  1202. end,
  1203. click = function(self, func)
  1204. self._click = func
  1205. end,
  1206. onDoubleClick = function(self, func)
  1207. self._doubleClick = func
  1208. end,
  1209. doubleclick = function(self, func)
  1210. self._doubleClick = func
  1211. end,
  1212. addItem = function(self, item)
  1213. table.insert(self.data.items, minetest.formspec_escape(item))
  1214. -- return the index of item. It is the last one
  1215. return #self.data.items
  1216. end,
  1217. removeItem = function(self,idx)
  1218. table.remove(self.data.items,idx)
  1219. end,
  1220. getItem = function(self, idx)
  1221. return self.data.items[idx]
  1222. end,
  1223. popItem = function(self)
  1224. local item = self.data.items[#self.data.items]
  1225. table.remove(self.data.items)
  1226. return item
  1227. end,
  1228. clearItems = function(self)
  1229. self.data.items = {}
  1230. end,
  1231. setSelected = function(self,idx)
  1232. self.data.selected = idx
  1233. end,
  1234. getSelected = function(self)
  1235. return self.data.selected
  1236. end,
  1237. getSelectedItem = function(self)
  1238. return self:getItem(self:getSelected())
  1239. end,
  1240. })
  1241. smartfs.element("dropdown", {
  1242. onCreate = function(self)
  1243. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "dropdown needs valid pos")
  1244. assert(self.data.size and self.data.size.w and self.data.size.h, "dropdown needs valid size")
  1245. assert(self.name, "dropdown needs name")
  1246. self.data.items = self.data.items or {}
  1247. self.data.selected = self.data.selected or 1
  1248. self.data.value = ""
  1249. end,
  1250. build = function(self)
  1251. return "dropdown["..
  1252. self.data.pos.x..","..self.data.pos.y..
  1253. ";"..
  1254. self.data.size.w..","..self.data.size.h..
  1255. ";"..
  1256. self:getAbsName()..
  1257. ";"..
  1258. table.concat(self.data.items, ",")..
  1259. ";"..
  1260. tostring(self:getSelected())..
  1261. "]"
  1262. end,
  1263. submit = function(self, field, player)
  1264. self:getSelected()
  1265. if self._select then
  1266. self:_select(self.root, field, player)
  1267. end
  1268. end,
  1269. onSelect = function(self, func)
  1270. self._select = func
  1271. end,
  1272. addItem = function(self, item)
  1273. table.insert(self.data.items, item)
  1274. if #self.data.items == self.data.selected then
  1275. self.data.value = item
  1276. end
  1277. -- return the index of item. It is the last one
  1278. return #self.data.items
  1279. end,
  1280. removeItem = function(self,idx)
  1281. table.remove(self.data.items,idx)
  1282. end,
  1283. getItem = function(self, idx)
  1284. return self.data.items[idx]
  1285. end,
  1286. popItem = function(self)
  1287. local item = self.data.items[#self.data.items]
  1288. table.remove(self.data.items)
  1289. return item
  1290. end,
  1291. clearItems = function(self)
  1292. self.data.items = {}
  1293. end,
  1294. setSelected = function(self,idx)
  1295. self.data.selected = idx
  1296. self.data.value = self:getItem(idx) or ""
  1297. end,
  1298. getSelected = function(self)
  1299. self.data.selected = 1
  1300. if #self.data.items > 1 then
  1301. for i = 1, #self.data.items do
  1302. if self.data.items[i] == self.data.value then
  1303. self.data.selected = i
  1304. end
  1305. end
  1306. end
  1307. return self.data.selected
  1308. end,
  1309. getSelectedItem = function(self)
  1310. return self.data.value
  1311. end,
  1312. })
  1313. smartfs.element("inventory", {
  1314. onCreate = function(self)
  1315. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "list needs valid pos")
  1316. assert(self.data.size and self.data.size.w and self.data.size.h, "list needs valid size")
  1317. assert(self.name, "list needs name")
  1318. -- Default the list name to the element name.
  1319. self.data.list = self.name
  1320. end,
  1321. build = function(self)
  1322. return "list["..
  1323. (self.data.invlocation or "current_player") ..
  1324. ";"..
  1325. self.data.list ..
  1326. ";"..
  1327. self.data.pos.x..","..self.data.pos.y..
  1328. ";"..
  1329. self.data.size.w..","..self.data.size.h..
  1330. ";"..
  1331. (self.data.index or "") ..
  1332. "]"
  1333. end,
  1334. -- available inventory locations
  1335. -- "current_player": Player to whom the menu is shown
  1336. -- "player:<name>": Any player
  1337. -- "nodemeta:<X>,<Y>,<Z>": Any node metadata
  1338. -- "detached:<name>": A detached inventory
  1339. -- "context" does not apply to smartfs, since there is no node-metadata as context available
  1340. setLocation = function(self,invlocation)
  1341. self.data.invlocation = invlocation
  1342. end,
  1343. getLocation = function(self)
  1344. return self.data.invlocation or "current_player"
  1345. end,
  1346. usePosition = function(self, pos)
  1347. self.data.invlocation = string.format("nodemeta:%d,%d,%d", pos.x, pos.y, pos.z)
  1348. end,
  1349. usePlayer = function(self, name)
  1350. self.data.invlocation = "player:" .. name
  1351. end,
  1352. useDetached = function(self, name)
  1353. self.data.invlocation = "detached:" .. name
  1354. end,
  1355. setList = function(self, list)
  1356. self.data.list = list or self.name
  1357. end,
  1358. getList = function(self)
  1359. return self.data.list
  1360. end,
  1361. setIndex = function(self,index)
  1362. self.data.index = index
  1363. end,
  1364. getIndex = function(self)
  1365. return self.data.index
  1366. end
  1367. })
  1368. smartfs.element("code", {
  1369. onCreate = function(self)
  1370. self.data.code = self.data.code or ""
  1371. end,
  1372. build = function(self)
  1373. if self._build then
  1374. self:_build()
  1375. end
  1376. return self.data.code
  1377. end,
  1378. submit = function(self, field, player)
  1379. if self._sub then
  1380. self:_sub(self.root, field, player)
  1381. end
  1382. end,
  1383. onSubmit = function(self,func)
  1384. self._sub = func
  1385. end,
  1386. onBuild = function(self,func)
  1387. self._build = func
  1388. end,
  1389. setCode = function(self,code)
  1390. self.data.code = code
  1391. end,
  1392. getCode = function(self)
  1393. return self.data.code
  1394. end
  1395. })
  1396. smartfs.element("container", {
  1397. onCreate = function(self)
  1398. assert(self.data.pos and self.data.pos.x and self.data.pos.y, "container needs valid pos")
  1399. assert(self.name, "container needs name")
  1400. local statelocation = smartfs._ldef.container._make_state_location_(self)
  1401. self._state = smartfs._makeState_(nil, self.root.param, statelocation)
  1402. end,
  1403. -- redefinitions. The size is not handled by data.size but by container-state:size
  1404. setSize = function(self,w,h)
  1405. self:getContainerState():setSize(w,h)
  1406. end,
  1407. getSize = function(self)
  1408. return self:getContainerState():getSize()
  1409. end,
  1410. -- element interface methods
  1411. build = function(self)
  1412. if self.data.relative ~= true then
  1413. return "container["..self.data.pos.x..","..self.data.pos.y.."]"..
  1414. self:getContainerState():_buildFormspec_(false)..
  1415. "container_end[]"
  1416. else
  1417. return self:getContainerState():_buildFormspec_(false)
  1418. end
  1419. end,
  1420. getContainerState = function(self)
  1421. return self._state
  1422. end,
  1423. _getSubElement_ = function(self, field)
  1424. return self:getContainerState():_get_element_recursive_(field)
  1425. end,
  1426. })
  1427. return smartfs