smartfs.lua 39 KB

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