forked from your-land-mirror/minetest-flow
Add inline styles and tooltips
This commit is contained in:
parent
1d6ddb368d
commit
5215229d8c
151
README.md
151
README.md
@ -377,7 +377,52 @@ it is 0.1.
|
||||
|
||||
## Styling forms
|
||||
|
||||
To style forms, you use the `gui.Style` and `gui.StyleType` elements:
|
||||
### Experimental new syntax
|
||||
|
||||
At the moment I suggest only using this syntax if your form won't look broken
|
||||
without the style - older versions of flow don't support this syntax, and I may
|
||||
make breaking changes to it (such as sub-style syntax) in the future.
|
||||
|
||||
You can add inline styles to elements with the `style` field:
|
||||
|
||||
```lua
|
||||
gui.Button{
|
||||
label = "Test",
|
||||
style = {
|
||||
bgcolor = "red",
|
||||
|
||||
-- You can style specific states of elements:
|
||||
{sel = "$hovered", bgcolor = "green"},
|
||||
|
||||
-- Or a combination of states:
|
||||
{sel = "$hovered, $pressed", bgcolor = "blue"},
|
||||
{sel = "$hovered+pressed", bgcolor = "white"},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
If you need to style multiple elements, you can reuse the `style` table:
|
||||
|
||||
```lua
|
||||
local my_style = {bgcolor = "red", {sel = "$hovered", bgcolor = "green"}}
|
||||
|
||||
local gui = flow.make_gui(function(player, ctx)
|
||||
return gui.VBox{
|
||||
gui.Button{label = "Styled button", style = my_style},
|
||||
gui.Button{label = "Unstyled button"},
|
||||
gui.Button{label = "Second styled button", style = my_style},
|
||||
}
|
||||
end)
|
||||
```
|
||||
|
||||
Note that this may inadvertently reset styles on subsequent elements if used on
|
||||
elements without a name due to formspec limitations.
|
||||
|
||||
### Alternative more stable syntax
|
||||
|
||||
Alternatively, you can use the `gui.Style` and `gui.StyleType` elements if you
|
||||
need to style a large group of elements or need to support older versions of
|
||||
flow:
|
||||
|
||||
```lua
|
||||
gui.Style{
|
||||
@ -394,15 +439,74 @@ gui.Button{
|
||||
},
|
||||
```
|
||||
|
||||
The style elements are invisible and won't affect padding.
|
||||
The `Style` and `StyleType` elements are invisible and won't affect padding.
|
||||
|
||||
## Hiding elements
|
||||
## Other features
|
||||
|
||||
<details>
|
||||
<summary><b>Tooltips</b></summary>
|
||||
|
||||
You can add tooltips to elements using the `tooltip` field:
|
||||
|
||||
```lua
|
||||
gui.Image{
|
||||
w = 2, h = 2,
|
||||
texture_name = "air.png",
|
||||
tooltip = "Air",
|
||||
}
|
||||
```
|
||||
|
||||
</details><details>
|
||||
<summary><b>Hiding elements</b></summary>
|
||||
|
||||
Elements inside boxes can have `visible = false` set to hide them from the
|
||||
player. Elements hidden this way will still take up space like with
|
||||
`visibility: hidden;` in CSS.
|
||||
|
||||
## Experimental features
|
||||
</details><details>
|
||||
<summary><b>Using a form as an inventory</b></summary>
|
||||
|
||||
> [!TIP]
|
||||
> Consider using [Sway](https://content.minetest.net/packages/lazerbeak12345/sway/)
|
||||
> instead if you want to use flow as an inventory replacement while still
|
||||
> having some way for other mods to extend the inventory.
|
||||
|
||||
A form can be set as the player inventory. Flow internally generates the
|
||||
formspec and passes it to `player:set_inventory_formspec()`. This will
|
||||
completely replace your inventory and isn't compatible with inventory mods like
|
||||
sfinv.
|
||||
|
||||
```lua
|
||||
local example_inventory = flow.make_gui(function (player, context)
|
||||
return gui.Label{ label = "Inventory goes here!" }
|
||||
end)
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
example_inventory:set_as_inventory_for(player)
|
||||
end)
|
||||
```
|
||||
|
||||
Like with the `show_hud` function, `update*` functions don't do anything, so to
|
||||
update it, call `set_as_inventory_for` again with the new context. If the
|
||||
context is not provided, it will reuse the existing context.
|
||||
|
||||
```lua
|
||||
example_inventory:set_as_inventory_for(player, new_context)
|
||||
```
|
||||
|
||||
While the form will of course be cleared when the player leaves, if you'd like
|
||||
to unset the inventory manually, call `:unset_as_inventory_for(player)`,
|
||||
analogue to `close_hud`:
|
||||
|
||||
```lua
|
||||
example_inventory:unset_as_inventory_for(player)
|
||||
```
|
||||
|
||||
This will set the inventory formspec string to `""` and stop flow from
|
||||
processing inventory formspec input.
|
||||
|
||||
</details>
|
||||
|
||||
### Experimental features
|
||||
|
||||
These features might be broken in the future.
|
||||
|
||||
@ -438,42 +542,6 @@ You can set `bgcolor = "#123"`, `fbgcolor = "#123"`, and
|
||||
`bg_fullscreen = true` on the root element to set a background colour. The
|
||||
values for these correspond to the [`bgcolor` formspec element](https://minetest.gitlab.io/minetest/formspec/#bgcolorbgcolorfullscreenfbgcolor).
|
||||
|
||||
</details><details>
|
||||
<summary><b>Using a form as an inventory</b></summary>
|
||||
|
||||
A form can be set as the player inventory. Flow internally generates the
|
||||
formspec and passes it to `player:set_inventory_formspec()`. This will
|
||||
completely replace your inventory and isn't compatible with inventory mods like
|
||||
sfinv.
|
||||
|
||||
```lua
|
||||
local example_inventory = flow.make_gui(function (player, context)
|
||||
return gui.Label{ label = "Inventory goes here!" }
|
||||
end)
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
example_inventory:set_as_inventory_for(player)
|
||||
end)
|
||||
```
|
||||
|
||||
Like with the `show_hud` function, `update*` functions don't do anything, so to
|
||||
update it, call `set_as_inventory_for` again with the new context. If the
|
||||
context is not provided, it will reuse the existing context.
|
||||
|
||||
```lua
|
||||
example_inventory:set_as_inventory_for(player, new_context)
|
||||
```
|
||||
|
||||
While the form will of course be cleared when the player leaves, if you'd like
|
||||
to unset the inventory manually, call `:unset_as_inventory_for(player)`,
|
||||
analogue to `close_hud`:
|
||||
|
||||
```lua
|
||||
example_inventory:unset_as_inventory_for(player)
|
||||
```
|
||||
|
||||
This will set the inventory formspec string to `""` and stop flow from
|
||||
processing inventory formspec input.
|
||||
|
||||
</details><details>
|
||||
<summary><b>Rendering to a formspec</b></summary>
|
||||
|
||||
@ -495,6 +563,7 @@ call `form:render_to_formspec_string(player, ctx, standalone)`.
|
||||
even if fields.quit is sent.
|
||||
|
||||
|
||||
**Do not use this API with node meta formspecs, it can and will break!**
|
||||
> [!CAUTION]
|
||||
> Do not use this API with node meta formspecs, it can and will break!
|
||||
|
||||
</details>
|
||||
|
104
init.lua
104
init.lua
@ -338,6 +338,7 @@ function align_types.fill(node, x, w, extra_space)
|
||||
x = 0, y = 0,
|
||||
w = node.w + extra_space, h = node.h,
|
||||
name = "\1", label = node.label,
|
||||
style = node.style,
|
||||
}
|
||||
|
||||
-- Overlay button to prevent clicks from doing anything
|
||||
@ -352,6 +353,7 @@ function align_types.fill(node, x, w, extra_space)
|
||||
|
||||
node.y = node.y - LABEL_OFFSET
|
||||
node.label = nil
|
||||
node.style = nil
|
||||
node._label_hack = true
|
||||
assert(#node == 4)
|
||||
end
|
||||
@ -901,6 +903,104 @@ function flow.get_context()
|
||||
return current_ctx
|
||||
end
|
||||
|
||||
local function insert_style_elem(tree, i, node, props, sels)
|
||||
local base_selector = node.name or node.type
|
||||
local selectors = {}
|
||||
if sels then
|
||||
for i, sel in ipairs(sels) do
|
||||
local suffix = sel:match("^%s*$(.-)%s*$")
|
||||
if suffix then
|
||||
selectors[i] = base_selector .. ":" .. suffix
|
||||
else
|
||||
minetest.log("warning", "[flow] Invalid style selector: " ..
|
||||
tostring(sel))
|
||||
end
|
||||
end
|
||||
else
|
||||
selectors[1] = base_selector
|
||||
end
|
||||
|
||||
|
||||
table.insert(tree, i, {
|
||||
type = node.name and "style" or "style_type",
|
||||
selectors = selectors,
|
||||
props = props,
|
||||
})
|
||||
|
||||
if not node.name then
|
||||
-- Undo style_type modifications
|
||||
local reset_props = {}
|
||||
for k in pairs(props) do
|
||||
-- The style table might have substyles which haven't been removed
|
||||
-- yet
|
||||
reset_props[k] = ""
|
||||
end
|
||||
|
||||
table.insert(tree, i + 2, {
|
||||
type = "style_type",
|
||||
selectors = selectors,
|
||||
props = reset_props,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function extract_props(t)
|
||||
local res = {}
|
||||
for k, v in pairs(t) do
|
||||
if k ~= "sel" and type(k) == "string" then
|
||||
res[k] = v
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
-- I don't like the idea of making yet another pass over the element tree but I
|
||||
-- can't think of a clean way of integrating shorthand elements into one of the
|
||||
-- other loops.
|
||||
local function insert_shorthand_elements(tree)
|
||||
for i = #tree, 1, -1 do
|
||||
local node = tree[i]
|
||||
|
||||
-- Insert styles
|
||||
if node.style then
|
||||
local props = node.style
|
||||
if #node.style > 0 then
|
||||
-- Make a copy of node.style without the numeric keys. This
|
||||
-- avoids modifying node.style in case it's used for multiple
|
||||
-- elements.
|
||||
props = extract_props(props)
|
||||
end
|
||||
insert_style_elem(tree, i, node, props)
|
||||
|
||||
for j, substyle in ipairs(node.style) do
|
||||
insert_style_elem(tree, i + j, node, extract_props(substyle),
|
||||
substyle.sel:split(","))
|
||||
end
|
||||
end
|
||||
|
||||
-- Insert tooltips
|
||||
if node.tooltip then
|
||||
if node.name then
|
||||
table.insert(tree, i, {
|
||||
type = "tooltip",
|
||||
gui_element_name = node.name,
|
||||
tooltip_text = node.tooltip,
|
||||
})
|
||||
else
|
||||
local w, h = get_and_fill_in_sizes(node)
|
||||
table.insert(tree, i, {
|
||||
type = "tooltip",
|
||||
x = node.x, y = node.y, w = w, h = h,
|
||||
tooltip_text = node.tooltip,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if node.type == "container" or node.type == "scroll_container" then
|
||||
insert_shorthand_elements(node)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Renders a GUI into a formspec_ast tree and a table with callbacks.
|
||||
function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
||||
@ -938,6 +1038,10 @@ function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
||||
tree, orig_form, id1, embedded
|
||||
)
|
||||
|
||||
-- This should be after parse_callbacks so it can take advantage of
|
||||
-- automatic field naming
|
||||
insert_shorthand_elements(tree)
|
||||
|
||||
local redraw_if_changed = {}
|
||||
for var in pairs(used_ctx_vars) do
|
||||
-- Only add it if there is no callback and the name exists in the
|
||||
|
132
test.lua
132
test.lua
@ -734,4 +734,136 @@ describe("Flow", function()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("inline style parser", function()
|
||||
it("parses inline styles correctly", function()
|
||||
test_render(gui.Box{
|
||||
w = 1, h = 1, color = "blue",
|
||||
style = {hello = "world"}
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
style_type[box;hello=world]
|
||||
box[0.3,0.3;1,1;blue]
|
||||
style_type[box;hello=]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("parses inline styles correctly", function()
|
||||
test_render(gui.Button{
|
||||
w = 1, h = 1, name = "mybtn",
|
||||
style = {hello = "world"}
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
style[mybtn;hello=world]
|
||||
button[0.3,0.3;1,1;mybtn;]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("takes advantage of auto-generated names", function()
|
||||
test_render(gui.Button{
|
||||
w = 1, h = 1, on_event = function() end,
|
||||
style = {hello = "world"}
|
||||
}, ([[
|
||||
size[1.6,1.6]
|
||||
style[\10;hello=world]
|
||||
button[0.3,0.3;1,1;\10;]
|
||||
]]):gsub("\\1", "\1"))
|
||||
end)
|
||||
|
||||
it("supports advanced selectors", function()
|
||||
test_render(gui.Button{
|
||||
w = 1, h = 1, name = "mybtn",
|
||||
style = {
|
||||
bgimg = "btn.png",
|
||||
{sel = "$hovered", bgimg = "hover.png"},
|
||||
{sel = "$focused", bgimg = "focus.png"},
|
||||
},
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
style[mybtn;bgimg=btn.png]
|
||||
style[mybtn:hovered;bgimg=hover.png]
|
||||
style[mybtn:focused;bgimg=focus.png]
|
||||
button[0.3,0.3;1,1;mybtn;]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("supports advanced selectors on non-named nodes", function()
|
||||
test_render(gui.Box{
|
||||
w = 1, h = 1, color = "blue",
|
||||
style = {
|
||||
bgimg = "btn.png",
|
||||
{sel = "$hovered", bgimg = "hover.png"},
|
||||
{sel = "$focused", bgimg = "focus.png"},
|
||||
},
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
style_type[box;bgimg=btn.png]
|
||||
style_type[box:hovered;bgimg=hover.png]
|
||||
style_type[box:focused;bgimg=focus.png]
|
||||
box[0.3,0.3;1,1;blue]
|
||||
style_type[box:focused;bgimg=]
|
||||
style_type[box:hovered;bgimg=]
|
||||
style_type[box;bgimg=]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("supports multiple selectors", function()
|
||||
test_render(gui.Button{
|
||||
w = 1, h = 1, name = "mybtn",
|
||||
style = {
|
||||
bgimg = "btn.png",
|
||||
{sel = "$hovered, $focused,$pressed", bgimg = "hover.png"},
|
||||
},
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
style[mybtn;bgimg=btn.png]
|
||||
style[mybtn:hovered,mybtn:focused,mybtn:pressed;bgimg=hover.png]
|
||||
button[0.3,0.3;1,1;mybtn;]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("allows reuse of the same table", function()
|
||||
local style = {
|
||||
bgimg = "btn.png",
|
||||
{sel = "$hovered", bgimg = "hover.png"},
|
||||
}
|
||||
test_render(gui.VBox{
|
||||
gui.Button{w = 1, h = 1, name = "btn1", style = style},
|
||||
gui.Button{w = 1, h = 1, name = "btn2", style = style},
|
||||
}, [[
|
||||
size[1.6,2.8]
|
||||
style[btn1;bgimg=btn.png]
|
||||
style[btn1:hovered;bgimg=hover.png]
|
||||
button[0.3,0.3;1,1;btn1;]
|
||||
style[btn2;bgimg=btn.png]
|
||||
style[btn2:hovered;bgimg=hover.png]
|
||||
button[0.3,1.5;1,1;btn2;]
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("tooltip insertion", function()
|
||||
it("works with named elements", function()
|
||||
test_render(gui.Button{
|
||||
w = 1, h = 1, name = "mybtn",
|
||||
tooltip = "test",
|
||||
}, [[
|
||||
size[1.6,1.6]
|
||||
tooltip[mybtn;test]
|
||||
button[0.3,0.3;1,1;mybtn;]
|
||||
]])
|
||||
end)
|
||||
|
||||
it("works with unnamed elements", function()
|
||||
-- The tooltip[] added here takes the list spacing into account
|
||||
test_render(gui.List{
|
||||
w = 2, h = 2, padding = 1,
|
||||
tooltip = "test"
|
||||
}, [[
|
||||
size[4.25,4.25]
|
||||
tooltip[1,1;2.25,2.25;test]
|
||||
list[;;1,1;2,2]
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user