First commit

master
Pierre-Yves Rollo 2018-12-15 12:52:04 +01:00
commit 4b6093a706
12 changed files with 765 additions and 0 deletions

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# Nofs Modpack
No FormSpec

0
modpack.txt Normal file
View File

2
nofs_demo/depends.txt Normal file
View File

@ -0,0 +1,2 @@
nofs_lib
nofs_extra?

137
nofs_demo/init.lua Normal file
View File

@ -0,0 +1,137 @@
--[[
nofs_demo for Minetest - Demonstration mod for nofs_lib
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
nofs_demo = {}
nofs_demo.name = minetest.get_current_modname()
nofs_demo.path = minetest.get_modpath(nofs_demo.name)
--dofile(nofs.path..'/stack.lua')
local itemlist={}
minetest.after(0, function()
for name, item in pairs(minetest.registered_items) do
if item.description and item.description ~= "" and name ~= "air" then
table.insert(itemlist, name)
end
end
table.sort(itemlist)
end
)
--[[
new_form('test_form:itemlist', function(context)
if not context.page then context.page = 1 end
local formspec
local grid = { x = 8, y = 7 }
local pagesize = grid.x * grid.y
local lastpage = math.ceil(#itemlist / pagesize)
if context.page < 1 then context.page = 1 end
if context.page > lastpage then context.page = lastpage end
local count = pagesize * (context.page - 1)
formspec = "size[8,9]label[0,0;Page "..context.page.." of "..lastpage.."]"
local x, y
for y = 1, grid.y do
for x = 1, grid.x do
count = count + 1
if count < #itemlist then
formspec = formspec.."item_image_button["..(x-1)..","..y..";1,1;"..itemlist[count]..";test_form:bi_"..itemlist[count]..";]"
end
end
end
formspec = formspec.."button[6,0;1,1;b_prev;<]button[7,0;1,1;b_next;>]button_exit[7,8;1,1;button_exit;Exit]"
return formspec
end)
set_form_callback('test_form:itemlist', 'b_prev', function(formname, player, fields, context)
context.page = context.page - 1
refresh_form(formname, player)
end)
set_form_callback('test_form:itemlist', 'b_next', function(formname, player, fields, context)
context.page = context.page + 1
-- refresh_form(formname, player)
refresh_form('test_form:test', player)
end)
new_form('test_form:test', function(context)
return render_form(test_form)
--return "size[3,3]button[0,0;3,1;item;Item chooser]button[0,1;3,1;test;Test]button_exit[0,2;3,1;button_exit;Exit]"
end)
set_form_callback('test_form:test', 'item', function(formname, player, fields, context)
stack_form ("test_form:itemlist", player:get_player_name())
end)
set_form_callback('test_form:test', 'test', function(formname, player, fields, context)
minetest.show_formspec(player_name(player), "", "")
end)
--]]
-- TODO :: PROBLEME : Les tableaux associatifs ne sont pas parcourrus dans l'ordre
local main_form = {
id = 'test_form',
type = 'vbox',
{ type = 'hbox',
{ type = 'label', width = 2, height = 1, label = 'A simple text', },
{ type = 'button', width = 2, height = 1, label = 'Test',
on_action = function() print ('Button test clicked') end,
},
{ type = 'button', width = 2, height = 1, label = 'Test 2',
on_action = function() print ('Button test 2 clicked') end,
},
},
{ type = 'hbox',
{ id = 'field 1', type = 'field', height = 1, width = 4,
label = 'Field', default = 'toto',},
{ type = 'button', height = 1, width = 1, label = 'Exit', exit = 'true',
on_action = nofs.close_form, },
},
{ type = "checkbox", id = "chk1", label = "Select this", },
}
local function is_player_connected(name)
if minetest.player_exists(name)
then
for _,player in ipairs(minetest.get_connected_players()) do
if name == player:get_player_name() then
return true
end
end
end
return false
end
minetest.register_chatcommand("nofs", { params = "", description = "NOFS demo",
func = function(name, param)
if is_player_connected(name)
then
nofs.show_form(name, main_form)
end
end,
}
)

86
nofs_lib/API.md Normal file
View File

@ -0,0 +1,86 @@
# Items by type
## Common to all
- **id**: Unique (in the form) name of the widget. Default: key of the list element.
- **type**: Type of widget
## Container
- **layout**: Layout of the container, ''vbox'' vertical box (default), ''hbox'', horizontal box
## Button
- **height**, **width** : Size of the button
- **label**: Label displayed on the button
- **image**: Image displayed on the button
- **item**: Item displayed on the button
A button can't have both **image** and **item** attributes. In such case **item** attribute is ignored.
- **exit**: If true, the button will exit on click. Doesn't work if button has an **item** attribute. **close_form** can be used for such buttons.
## Field
- **height**, **width** : Size of the field
- **default**: Default value for this field if empty
- **hidden**: If true, the field will not display text (password field)
## Label
- **height**, **width** : Size of the label. Actually for placement purpose, the text is not bound by this size
- **label**: Text displayed
- **direction**: ''horizontal'' (default) or ''vertical'' text direction
- **context**: Context var ?
- **meta**: Associated metadata if <-- Voir comment on peut faire ça; associer un noeud au contexte ?
| Formspec element | nofs widget | Status |
| --- | --- |
| size[W,H] | Form level | Done |
| position[X,Y] | Form level | Not started |
| anchor[X,Y] | Form level | Not started |
| no_prepend[] | Form level | Not started |
| container[X,Y] | | Useless |
| container_end[] | | Useless |
| list[invloc;listname;X,Y;W,H;] | ? | Not started |
| list[invloc;listname;X,Y;W,H;index] | ? | Not started |
| listring[invloc;listname] | ? | Not started |
| listring[] | ? | Not started |
| listcolors[bgcolor;bgcolorhover] | ? | Not started |
| listcolors[bgcolor;bgcolorhover;border] | ? | Not started |
| listcolors[bgcolor;bgcolorhover;border;tooltip_bg;tooltip_fontcolor] | ? | Not started |
| tooltip[id;text;bgcolor;fontcolor] | ? | Not started |
| tooltip[X,Y;W,H;text;bgcolor;fontcolor] | ? | Not started |
| image[X,Y;W,H;texture] | ? | Not started |
| item_image[X,Y;W,H;itemname] | ? | Not started |
| bgcolor[color;fullscreen] | Form level | Not started |
| background[X,Y;W,H;texture] | Form level | Not started |
| background[X,Y;W,H;texture;auto_clip] | Form level | Not started |
| pwdfield[X,Y;W,H;name;label] | field | Done |
| field[X,Y;W,H;name;label;default] | field | Done |
| field[name;label;default] | field | Done |
| field_close_on_enter[name;close_on_enter] | field | Not started |
| textarea[X,Y;W,H;name;label;default] | ? | Not started |
| label[X,Y;label] | label | Done |
| vertlabel[X,Y;label] | label | Done |
| button[X,Y;W,H;name;label] | button | Done |
| image_button[X,Y;W,H;image;name;label] | button | Done |
| image_button[X,Y;W,H;image;name;label;noclip;drawborder;pressedimage] | button | Not started |
| item_image_button[X,Y;W,H;item name;name;label] | button | Done |
| button_exit[X,Y;W,H;name;label] | button | Done |
| image_button_exit[X,Y;W,H;image;name;label] | button | Done |
| textlist[X,Y;W,H;name;listelem 1,listelem 2,...,listelem n] | ? | Not started |
| tabheader[X,Y;name;caption 1,caption 2,...,caption n;current_tab;transparent;draw_border] | ? | Not started |
| textlist[X,Y;W,H;name;listelem 1,listelem 2,...,listelem n;selected idx;transparent] | ? | Not started |
| box[X,Y;W,H;color] | vbox/hbox | Not started |
| checkbox[X,Y;label] | checkbox | WIP |
| dropdown[X,Y;W;name;item 1,item 2, ...,item n;selected idx] | ? | Not started |
| scrollbar[X,Y;W,H;orientation;name;value] | ? | Not started |
| table[X,Y;W,H;name;cell 1,cell 2,...,cell n;selected idx] | ? | Not started |
| tablecolumns[type 1,opt 1a,opt 1b,...;type 2,opt 2a,opt 2b;...] | ? Not started |

View File

@ -0,0 +1 @@
,pyrollo,localhost.localdomain,15.12.2018 12:49,file:///home/pyrollo/.config/libreoffice/4;

BIN
nofs_lib/doc/Widgets.ods Normal file

Binary file not shown.

58
nofs_lib/events.lua Normal file
View File

@ -0,0 +1,58 @@
--[[
nofs_lib for Minetest - NO FormSpec API
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
-- Event management
-- ================
-- TODO : Trigger on forms and containers ? = if one of the decendant matches
function nofs.trigger_event(player, form, fields, element, eventname)
if element['on_'..eventname] and element.id and fields[element.id] then
element['on_'..eventname](player, form, fields)
end
if element.children then
for index, child in ipairs(element.children) do
nofs.trigger_event(player, form, fields, child, eventname)
end
end
end
-- Generic trigger propagation
minetest.register_on_player_receive_fields(
function(player, formname, fields)
-- Find form
local form = nofs.stack_get_by_id(player, formname)
if form then
-- Trigger on_click event
nofs.trigger_event(player, form, fields, form, 'action')
-- If form exit, unstack
if fields.quit == "true" then
-- Trigger on_close event
nofs.trigger_event(player, form, fields, form, 'close')
nofs.stack_remove(player)
local form = nofs.stack_get_top(player)
if form then
nofs.refresh_form(form, player)
end
end
end
end
)

29
nofs_lib/init.lua Normal file
View File

@ -0,0 +1,29 @@
--[[
nofs_lib for Minetest - NO FormSpec API
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
nofs = {}
nofs.name = minetest.get_current_modname()
nofs.path = minetest.get_modpath(nofs.name)
dofile(nofs.path..'/stack.lua')
dofile(nofs.path..'/render.lua')
dofile(nofs.path..'/events.lua')
dofile(nofs.path..'/main.lua')

71
nofs_lib/main.lua Normal file
View File

@ -0,0 +1,71 @@
--[[
nofs_lib for Minetest - NO FormSpec API
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
-- Top level functions
-- ===================
-- Refreshes the top form
function nofs.refresh_form(player)
local player = player
local form = nofs.stack_get_top(player)
if type(player) ~= "string" then
player = player:get_player_name()
end
if form then
minetest.show_formspec(player, form.id,
nofs.render_form(form))
else
-- Hide form
minetest.show_formspec(player, "", "")
end
end
-- Show form
-- LE TRUC QUI NE VA PAS :
-- Dans render, on met en place une instance de la form d'apres sa definition
-- Mais toute la partie ID est utile pour la gestion des evenements. Il faudrait
-- avoir deux objets : form def et form instance
function nofs.show_form(player, form, params)
-- TODO : test form validity
nofs.stack_add(player, form)
if params then
form.context['params'] = params
end
nofs.trigger_event(player, form, { }, form, 'open')
nofs.refresh_form(player)
end
-- Close top form
function nofs.close_form(player)
local form = nofs.stack_get_top(player)
if form then
-- Like if "esc" key was pressed -- TODO: Check this
nofs.trigger_event(player, form, { quit = "true" }, form, 'close')
end
nofs.stack_remove(player)
nofs.refresh_form(player)
end

293
nofs_lib/render.lua Normal file
View File

@ -0,0 +1,293 @@
--[[
nofs_lib for Minetest - NO FormSpec API
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
local widgets
-- Helpers
local function message(message)
minetest.log('warning', '['..nofs.name..'] '..message)
end
-- Forward declarations
--local set_defs_and_collect_ids, add_missing_ids,
local size_element, render_element
-- Standard offset position and size
local function fspos(buffer, offset)
local widgetoffset = widgets[buffer.def.type].offset
if widgetoffset then
return string.format("%g,%g",
buffer.pos.x + offset.x + widgetoffset.x,
buffer.pos.y + offset.y + widgetoffset.y)
else
return string.format("%g,%g",
buffer.pos.x + offset.x, buffer.pos.y + offset.y)
end
end
local function fspossize(buffer, offset)
local widgetoffset = widgets[buffer.def.type].offset
if widgetoffset then
return string.format("%g,%g;%g,%g",
buffer.pos.x + offset.x + widgetoffset.x,
buffer.pos.y + offset.y + widgetoffset.y,
buffer.size.x, buffer.size.y)
else
return string.format("%g,%g;%g,%g",
buffer.pos.x + offset.x, buffer.pos.y + offset.y,
buffer.size.x, buffer.size.y)
end
end
-- Sizing vbox and hbox
--
local function size_box(buffer, boxtype)
buffer.size = { 0, 0 }
local main, other
-- Process vbox and hbox the same way, just inverting coordinates
if boxtype == 'h' then
main = 'x' other = 'y'
else
main = 'y' other = 'x'
end
local pos = 0 -- TODO:MARGIN
local size = 0
-- Main positionning and other size
for index, child in ipairs(buffer.children) do
size_element(buffer.children[index])
buffer.children[index].pos[main] = pos
pos = pos + buffer.children[index].size[main] -- TODO:Spacing
if buffer.children[index].size[other] > size then
size = buffer.children[index].size[other]
end
end
-- Size + margin*2
-- Other positionning
for index, child in ipairs(buffer.children) do
-- TODO: This is center, add left & right
buffer.children[index].pos[other] =
( size - buffer.children[index].size[other] ) / 2
end
buffer.size[main] = pos
buffer.size[other] = size
end
-- Containers generic rendering
--
local function render_container(buffer, offset)
local inneroffset = {
x = offset.x + buffer.pos.x,
y = offset.y + buffer.pos.y
}
local fs = ""
for index, child in ipairs(buffer.children) do
fs = fs..render_element(child, inneroffset)
end
return fs
end
-- Widget definitions
--
widgets = {
vbox = {
sizing = function(buffer)
size_box(buffer, 'v')
end,
rendering = render_container,
},
hbox = {
sizing = function(buffer)
size_box(buffer, 'h')
end,
rendering = render_container,
},
button = {
needs_id = true,
rendering = function(buffer, offset)
-- Some warnings
if buffer.def.item ~= nil then
if buffer.def.image ~= nil then
message('WARNING: Button can\'t have "image" and "item" attributes at once. '..
'Ignoring "item" attribute.')
end
if buffer.def.exit == 'true' then
message('WARNING: Button can\'t have exit=true and item attributes at once. '..
'Ignoring exit=true attribute.')
end
end
-- Now, render !
if buffer.def.image then
if buffer.def.exit == "true" then
return string.format("image_button_exit[%s;%s;%s;%s]",
fspossize(buffer, offset), buffer.def.image, buffer.id, buffer.def.label or "")
else
return string.format("image_button[%s;%s;%s;%s]",
fspossize(buffer, offset), buffer.def.image, buffer.id, buffer.def.label or "")
end
elseif buffer.def.item then
return string.format("item_image_button[%s;%s;%s;%s]",
fspossize(buffer, offset), buffer.def.item, buffer.id, buffer.def.label or "")
else
if buffer.def.exit == "true" then
return string.format("button_exit[%s;%s;%s]",
fspossize(buffer, offset), buffer.id, buffer.def.label or "")
else
return string.format("button[%s;%s;%s]",
fspossize(buffer, offset), buffer.id, buffer.def.label or "")
end
end
end,
},
field = {
needs_id = true,
offset = { x = 0.3, y = 0.32 },
rendering = function(buffer, offset)
-- Some warnings
if buffer.def.hidden == 'true' then
if buffer.def.default ~= nil then
message('WARNING: Hidden field can\'t have a default value. '..
'Ignoring "default" attribute.')
end
-- TODO : Can't have bound variable neither
end
-- Render
if buffer.def.hidden == 'true' then
return string.format("pwdfield[%s;%s;%s]", fspossize(buffer, offset),
buffer.id, (buffer.def.label or ""))
else
return string.format("field[%s;%s;%s;%s]", fspossize(buffer, offset),
buffer.id, (buffer.def.label or ""), (buffer.def.default or ""))
end
end
},
label = {
offset = { x = 0, y = 0.2 },
rendering = function(buffer, offset)
if buffer.def.direction and buffer.def.direction == 'vertical' then
return string.format("vertlabel[%s;%s]",
fspos(buffer, offset), (buffer.def.label or ""))
else
return string.format("label[%s;%s]",
fspos(buffer, offset), (buffer.def.label or ""))
end
end
},
}
-- Form rendering
-- ==============
-- Pass 1 : Basic controls, add a def member pointing to definition, collect ids
-- Pass 2 : Add missing IDs
-- Pass 3 : Compute parent size from children sizes (pre-render)
-- Pass 4 : Place everything and render (render)
-- Pass 1 : Basic controls, add a def member pointing to definition, collect ids
--
local function set_defs_and_collect_ids(buffer, elements_by_id)
local def = buffer.def
assert(def.type, 'Element must have a type.')
assert(widgets[def.type], 'Element type "'..def.type..'" unknown.')
if def.id then
assert(elements_by_id[def.id] == nil,
'Id "'..def.id..'" already used in the same form.')
buffer.id = def.id
elements_by_id[def.id] = buffer
end
buffer.children = {}
for index, child in ipairs(def) do
buffer.children[index] = { def = child }
set_defs_and_collect_ids(buffer.children[index], elements_by_id)
end
end
-- Pass 2 : Add missing IDs
--
local function add_missing_ids(buffer, elements_by_id)
local def = buffer.def
if not buffer.id and widgets[def.type].needs_id then
local i = 1
while elements_by_id[def.type..i] do
i = i + 1
end
buffer.id = def.type..i
elements_by_id[buffer.id] = buffer
end
if buffer.children then
for index, child in ipairs(buffer.children) do
add_missing_ids(child, elements_by_id)
end
end
end
-- Pass 3 : Compute parent size from children sizes (pre-render)
--
size_element = function(buffer)
local def = buffer.def
buffer.pos = { x = 0, y = 0 }
buffer.size = { x = 0, y = 0 }
local widget = widgets[def.type]
if widget.sizing then
widget.sizing(buffer)
else -- Default case for simple elements
buffer.size = { x=def.width, y=def.height }
end
end
-- Pass 4 : Place everything and render (render)
--
render_element = function(buffer, offset)
local widget = widgets[buffer.def.type]
if widget.rendering then
return widget.rendering(buffer, offset)
else
return ""
end
end
-- Entry point
function nofs.render_form(form)
local buffer = { def = form, elements_by_id = {} }
if not buffer.def.type then
buffer.def.type = "vbox"
end
set_defs_and_collect_ids(buffer, buffer.elements_by_id)
add_missing_ids(buffer, buffer.elements_by_id)
size_element(buffer)
local render = render_element(buffer, {x = 0, y = 0})
return string.format("size[%g,%g]%s", buffer.size.x, buffer.size.y, render)
end

84
nofs_lib/stack.lua Normal file
View File

@ -0,0 +1,84 @@
--[[
nofs_lib for Minetest - NO FormSpec API
(c) Pierre-Yves Rollo
This file is part of nofs_lib.
signs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
signs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with signs. If not, see <http://www.gnu.org/licenses/>.
--]]
-- Form stack mechanism
-- ====================
local stacks = {}
local function get_player_stack(player)
if type(player) ~= "string" then
player = player:get_player_name()
end
if stacks[player] == nil then
stacks[player] = {}
end
return stacks[player]
end
-- Get top form
function nofs.stack_get_top(player)
local stack = get_player_stack(player)
return stack[#stack]
end
-- Find a form by its id
function nofs.stack_get_by_id(player, form_id)
for _, form in pairs(get_player_stack(player)) do
if form.id == form_id then
return form
end
end
return nil
end
-- Remove top form from stack
function nofs.stack_remove(player)
local stack = get_player_stack(player)
if #stack then
table.remove(stack, #stack)
end
end
-- Add a form on top of the stack (and add some extra fields on form)
function nofs.stack_add(player, form)
if form and type(form)=="table" then
local stack = get_player_stack(player)
stack[#stack + 1] = form
-- Form complementation
if not form.context then
form.context = {}
end
-- Name form if it is not named
if not form.id then
form.id = nofs.name..":form"..#stack
end
print (form.id)
end
end