Page 1 of 2

[Mod] Smart Formspecs - OO builder + binding [1.1][smartfs]

Posted: Mon Oct 28, 2013 15:33
by rubenwardy
Image

This mod provides a 2nd generation way of creating forms - this means that the modder does not need to worry about complex formspec strings*
  • Expandable: you can register your own elements to use on the form.
  • Easy event handling: use binding to handle events.
  • New elements: includes a toggle button
  • Inventories: add pages to smart inventories, without worrying about which mod is installed.
No longer do you have to use fiddly formspec strings and receive field functions, instead you use code like this:
Spoiler

Code: Select all

myform = smartfs.create("smartfs:form",function(state)
    state:size(10,7)
    state:label(2,0,"lbl","SmartFS example formspec!")
    state:field(7,1,3,1,"txt","Textbox")
    state:image(0,0,2,2,"img","default_stone.png")
    state:toggle(0,2,3,1,"tg",{"plenty..","of..","custom..","elements"})
    state:checkbox(2,1,"c","Easy code",true)

    state:button(0,3,2,1,"btn","Click Me"):click(function(self,state)
        print("Button clicked!")
        self.setText("Hello")
        state:close()
    end)
    return true
end)
myform:show("playername")
License: CC0

To install this library, install it as a mod and depend on it,
or if you do not like doing that then place the smartfs.lua file in your mod and then include it (dofile).
There is an example.lua file in the download that shows you how to use this library.

Download / GitHub Page

* it is possible to insert custom formspec strings

Posted: Mon Oct 28, 2013 19:04
by fairiestoy
Hey Ruben,
first of all, nice idea to add kind of a wrapper for that formspec strings. But one tip for handling this:
Better use it as standalone mod. This way, several modders will not have the same file and can access your
wrapper if they put it into their dependency file. Would make life easier of modders and keeps the data size
smaller due to only one folder with this files.


Greetings

Posted: Mon Oct 28, 2013 19:07
by rubenwardy
But nobody likes depending on mods, so they will just not use it.

I have added that.

Posted: Mon Oct 28, 2013 19:13
by fairiestoy
Tell me if im wrong, but there are a few libraries that are used as dependency because they are helpful like plantlife. Also the amount of mods with dependencies and without are imo very averaged. But it was just a suggestion, maybe keep it optional :P

Posted: Tue Oct 29, 2013 00:05
by LionsDen
Or maybe shoot to get it included as part of the minetest download. Maybe part of the default files/mod or something.

Posted: Tue Oct 29, 2013 09:57
by PilzAdam
The main problem with formspec strings is not the format, but rather the positioning. It was designed for inventory lists, so creating whole GUIs with it is a PITA.

Posted: Tue Oct 29, 2013 11:08
by jojoa1997
I agree. I have made my own inventory before and the hardest is the positioning. I remember reloading the game at least 50 times. Now if there was a way to see formspecs changed real time then that would be helpful.

Posted: Tue Oct 29, 2013 14:51
by rubenwardy
I could make it so you can give positions in pixels from the top left corner, but that could be hacky, and people WILL get confused.

I started work on a Formspec GUI Editor, but I got distracted. There is only a main class in that project so far, no real code at all.

You could make a mod the shows formspecs in game, but it would not have an editor.

Posted: Tue Oct 29, 2013 15:15
by rubenwardy
Unfortunately state:close() does not currently work as there is no way to do this in the api.

Posted: Tue Nov 05, 2013 13:20
by u34
rubenwardy wrote:Unfortunately state:close() does not currently work as there is no way to do this in the api.
how can i close a GUI by button anyway?
how can i activate a function by enabling a button in a formspec?

Posted: Tue Nov 05, 2013 13:25
by rubenwardy
This element has not been added yet.

https://github.com/minetest/minetest/bl ... i.txt#L990

Code: Select all

btn:onClick(function(self,state)
    print("Button clicked!")
    self.setText("Hello")
    state:close()
end)

Posted: Tue Nov 05, 2013 13:30
by rubenwardy
Added the element

Code: Select all

local btn = state:button(0,3,2,1,"btn","Click Me")
btn:setClose(true)
btn:onClick(function(self,state)
    --run on close
end)

Posted: Tue Nov 05, 2013 13:41
by u34
rubenwardy wrote:This element has not been added yet.

https://github.com/minetest/minetest/bl ... i.txt#L990

Code: Select all

btn:onClick(function(self,state)
        print("Button clicked!")
        self.setText("Hello")
        state:close()
    end)
you can add an exit button by default to close the formspec.
does this help?

Posted: Tue Nov 05, 2013 14:58
by rubenwardy
Please read all my responses.
rubenwardy wrote: Added the element

Code: Select all

local btn = state:button(0,3,2,1,"btn","Click Me")
btn:setClose(true)
btn:onClick(function(self,state)
    --run on close
end)
EDIT: Do you mean the proceed button?

Posted: Mon Feb 17, 2014 11:52
by rubenwardy
Bump :P

Posted: Thu Feb 20, 2014 20:37
by rubenwardy

Update!

I have added a wrapper for advanced inventories.

This means that you can add your formspec easily to smart inventories, such as inventory_plus and unified_inventory, without caring which one is installed. You don't have to modify your code for each one.

Adding to the inventory is as simple as:

Code: Select all

smartfs.add_to_inventory(myform, "icon.png", "MyFormLabel")
It works, but is quite buggy at this stage.

Any suggestions?

Have you got any suggestions, such as new elements?

I may be adding a page element (ie: start, back, 1, 2, 3, next, end)
to display stuff.

Posted: Thu Feb 20, 2014 22:42
by domtron vox
Great Mod! I am going to try and use it for my mod.

I do have a question though. It seems like the height value has no effect on the button. Is this a problem with smartfs or is it an engine limitation(I was trying to make a square button)?

EDIT:
I'm using the smartfs.lua as a library. I got the following error. Is using "throw" a mistake or did I do something to cause the error? I replaced all "throw"s with "print" and it worked.

Code: Select all

18:31:27: ERROR[main]: ServerError: ...on/games/minetest/bin/../mods/mod_manuel/smartfs.lua:19: attempt to call global 'throw' (a nil value)
18:31:27: ERROR[main]: stack traceback:
18:31:27: ERROR[main]:     ...on/games/minetest/bin/../mods/mod_manuel/smartfs.lua:19: in function 'create'

Posted: Fri Feb 21, 2014 17:00
by rubenwardy
Lua is stupid.

It uses error instead of throw. Fixed.

Also, if you look at the error:

Form <name> already exists!

You have tried making the same form twice.

eg: you made two forms called foo:bar


As for button heights, it should work.

Posted: Fri Feb 21, 2014 19:27
by domtron vox
rubenwardy wrote:Fixed.
Always a nice thing to hear.
rubenwardy wrote:You have tried making the same form twice.
Yep, I understood since your error is quite clear. I was just referring to the throw vs. error thing.
rubenwardy wrote:As for button heights, it should work.
Then I have no clue what I'm doing wrong. :/


I'm interested in eventually using tables for my mod. I think tables were added after last stable release. If so: are you planning on implementing tables or are you aiming for compatibility with the current minetest stable?

Anyway, don't be rushed on my account. At this stage labels are working quite nicely and your library is exactly what I need. Thanks for making it.

Posted: Thu Feb 27, 2014 21:44
by domtron vox
Hi, I did some documentation for you. Use it, lose it, or shred it. It is yours to do with as you please. I'll write up a section on creating new elements once I get around to trying it out. ;)

I'm not 100% sure how accurate it is.

If you don't use it I would like to point out that some of your function descriptions of the label and field elements refer to button instead of label/field.
Label
...
  • element:setText( text ) - set the caption of the button
Edit: For some reason the file wouldn't upload so I put it in a spoiler:
Spoiler
#Using Smart Formspec
Smartfs provides 2nd generation Minetest forms to replace clunky formspec strings. Each smartfs form is a container filled with GUI elements. A number of default elements are included with smartfs, but modders can also define their own custom elements. This document describes the basic usage of the smartfs API.

##Installation
Smartfs can be used as a library or a mod.

To use smartfs as a library, copy the smartfs.lua file to your mod folder and add
dofile(minetest.get\_modpath(minetest.get\_current\_modname()).."/smartfs.lua")
to the top of any files that use it.

To use smartfs as a mod, add it to your game's mods folder or to the user mods folder and enable it.

## Creating Forms
A form is a rectangular area of the screen upon which all elements are placed. Use the smartfs.create() function to create a new form. This function takes two arguments and returns a form object.

The first argument is a unique string that identifies the form. The second argument is a function that should take a single argument called state which is used to set form properties like size and background color. State also has constructors for all form elements and can be used with state:element_name. Below is a quick example.

form_name = smartfs.create("My Form",
function(state)
--sets the form's size
-- (width, hieght)
state:size(5,5)

--creates a label and places it on the form
--(x-pos, y-pos, name, text)
state:label(3,3,"ly label", "A label!")
end)
## Showing Forms
Forms can be shown to the player by using the show(target) function. The target argument is the name of the player that will see the form.

form_name:show("singleplayer")


## Inventory Support
Smartfs supports adding a button to Inventory+ or Unified Inventory which will open one of your own custom forms. Use the smartfs.add\_to\_inventory(form, icon, title) function where form is the smartfs form linked to by the button, icon is the button image(only for unified inventory), and title is the button text(only for inventory+.

smartfs.add_to_inventory(form_name, "my form icon.png", "Open My Form")

##Creating New Elements


#Full API
##Smartfs

* create( name,function ) - creates a new form with name and adds elements to it by running function.
* add\_to\_inventory( name,icon,title ) - adds a button with image icon if unified inventory or text title if inventory+ that links to the form belonging to the given name.

##Form

* state:size( width,height ) - sets the forms width and height.

##Button

###Creation

* state:button( x,y,w,h,name,text ) - create a new button at x,y with name and caption (text)

###Manipulation

* element:setPosition( x,y ) - change the position
* element:getPosition() - get the current position
* element:setSize( w,h ) - set the size
* element:getSize() - get the size
* element:setText( text ) - set the caption of the button
* element:getText() - get the caption of the button
* element:setImage( filename ) - sets the background of the button
* element:getImage() - get the background filename of the button

###Event Handling

* element:onClick( func(self,state) ) - specify a function to run when the button is clicked

##Toggle Button

###Creation

* state:toggle( x,y,w,h,name,list ) - create a new toggle button at x,y with name and possible list of values

###Manipulation

* element:setPosition( x,y ) - change the position
* element:getPosition() - get the current position
* element:setSize( w,h ) - set the size
* element:getSize() - get the size
* element:getText() - get the text of the toggle option
* element:setId( filename ) - sets the selected id
* element:getId() - get the selected id

###Event Handling

* element:onToggle( func(self,state) ) - specify a function to run when the value if toggled

##Label

###Creation

* state:label( x,y,name,text ) - create a new label at x,y with name and caption (text)

###Manipulation

* element:setPosition( x,y ) - change the position
* element:getPosition() - get the current position
* element:setText( text ) - set the caption of the label
* element:getText() - get the caption of the label

##Field and Text Area
###Creation

* state:field( x,y,w,h,name,label ) - create a new field at x,y with label
* state:pwdfield( x,y,w,h,name,label ) - create a password field
* state:textarea( x,y,w,h,name,label ) - create a new textarea

###Manipulation

* element:setPosition( x,y ) - change the position
* element:getPosition() - get the current position
* element:setSize( w,h ) - set the size
* element:getSize() - get the size
* element:setText( text ) - set the caption of the button
* element:getText() - get the caption of the field
* element:setImage( filename ) - sets the background of the field
* element:getImage() - get the background filename of the field

###Event Handling

* element:onClick( func(self,state) ) - specify a function to run when the field is clicked

Posted: Fri Feb 28, 2014 11:39
by webdesigner97
Would this allow to run function on button click like this one:

Code: Select all

button:onlick(callAFunction(withButtonRelatedParam))
I hope you understand what I mean: I have a formspec with let's say 10 buttons. Whenever one of these buttons gets clicked, it should run a function with a parameter based on its e.g. label text.

Posted: Fri Feb 28, 2014 11:40
by rubenwardy
Look good!

I will add this later. Good work!

Posted: Fri Feb 28, 2014 19:56
by domtron vox
webdesigner97 wrote:Would this allow to run function on button click like this one:

Code: Select all

button:onlick(callAFunction(withButtonRelatedParam))
if button is a variable that points to an element. For example if button is set using either

Code: Select all

local button = state:button(0,0, 1,4, "btn","a button")
or

Code: Select all

local button = state:get("btn")--if the button you want to get is called "btn"
in a form creation callback function(the function you pass to smartfs.create) then

Code: Select all

button:onlick(callAFunction(withButtonRelatedParam))
would work.

Here is a working example. Each time either button is pressed it adds a label to the form underneath it:

EDIT:I tweaked the code since rubenwardy said this would crash if multiple players joined a game. I tested it with a local host server and two clients.

Code: Select all

dofile(minetest.get_modpath(minetest.get_current_modname()).."/smartfs.lua")

minetest.register_on_joinplayer(function(player)

    local form = smartfs.create("test@"..player:get_player_name(),
                   function(state)

                       state:size(8,8)

                       --set variable using creation assignment
                       local button1 = state:button(0,0, 2,1, "btn1","A button")
                  
                       --set variable using state:get
                       state:button(2,0, 3,1, "btn2","Another button")
                       local button2 = state:get("btn2")

                       --incremented so each new label is in the right spot
                       local ypos1 = 2
                       local ypos2 = 2
                       
                       --button one on clicked function
                       state:get("btn1"):onClick(function(self, state)
                           state:label(0,ypos1, "lable1"..ypos1, "A label!")
                           ypos1 = ypos1 + 1
                       end)

                       --button two on clicked function
                       button2:onClick(function(self, state)
                           state:label(2,ypos2, "lable2"..ypos2, "A label!")
                           ypos2 = ypos2 + 1
                       end)                       


                   end)

    form:show(player:get_player_name())

end)
So far I haven't found a way to edit the elements of a form after creation all editing needs to be defined inside the creation callback.

Posted: Fri Feb 28, 2014 20:15
by domtron vox
And since I took the time to figure that out here are the docs on it. I put them after showing forms:
Spoiler
## Modifying Elements
Elements have functions of the form element:function(args) where you need to have access to the element object.

You can get the element object by assigning a variable to its creation function like so:

local button1 = state:button(0,0, 1,4, "btn1", "A button")
--button1 is now a table representing the button

You can also get the element by using state:get(name). The example below will retrieve a button with the name "btn1":

button1 = state:get("btn1")
--or
state:get("btn1"):onClick(your\_onclick\_function

Both of these methods need to be used inside the form creation callback function, the function you pass to smartfs.create.

Now that you have located your element you can modify it.

button1:setPos(4,0)
Again it is in .md format and yours to do with as you please.

Posted: Fri Feb 28, 2014 22:51
by rubenwardy
You could edit stuff outside of the callbacks, by getting the stored states.

Code: Select all

state = smartfs.opened[name]
-- gets the open state or null.

inv = smartfs.inv[name]
-- gets the open inventory smartfs, if set.
You could then do

Code: Select all

Btn = state:get("name")
Btn:setText("new")

State:_show_()
I will add a function for this, in the future, so you don't call a private function.