Use mkdocs for documentation

This commit is contained in:
luk3yx 2025-02-27 17:20:08 +13:00
parent fb069d203c
commit 71bbfd524e
15 changed files with 648 additions and 541 deletions

16
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,16 @@
# Modified from https://gitlab.com/pages/mkdocs/
image: python:3.13-slim
before_script:
- pip install mkdocs==1.6.1
pages:
stage: deploy
script:
- mkdocs build --strict --verbose -d public
artifacts:
paths:
- public
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH

545
README.md
View File

@ -157,544 +157,9 @@ These utilities likely aren't compatible with flow.
- kuto was the the source of the "on_event" function idea.
- [My web-based formspec editor](https://forum.luanti.org/viewtopic.php?f=14&t=24130) lets you add elements and drag+drop them, however it doesn't support all formspec features.
## Elements
## Documentation
You should do `local gui = flow.widgets` in your code.
### Layouting elements
These elements are used to lay out elements in the form. They don't have a
direct equivalent in formspecs.
#### `gui.VBox`
A vertical box, similar to a VBox in GTK. Elements inside a VBox are stacked
vertically.
```lua
gui.VBox{
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned underneath the first one.
gui.Label{label="I am a second label!"},
}
```
Elements inside boxes have a spacing of 0.2 between them. To change this, you
can add `spacing = <number>` to the box definition. For example, `spacing = 0`
will remove all spacing between the elements.
#### `gui.HBox`
Like `gui.VBox` but stacks elements horizontally instead.
```lua
gui.HBox{
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned to the right of first one.
gui.Label{label="I am a second label!"},
-- You can nest HBox and VBox elements
gui.VBox{
gui.Image{w=1, h=1, texture_name="default_dirt.png", align_h="centre"},
gui.Label{label="Dirt", expand=true, align_h="centre"},
}
}
```
#### `gui.ScrollableVBox`
Similar to `gui.VBox` but uses a scroll_container and automatically adds a
scrollbar. You must specify a width and height for the scroll container.
```lua
gui.ScrollableVBox{
-- A name must be provided for ScrollableVBox elements. You don't
-- have to use this name anywhere else, it just makes sure flow
-- doesn't mix up scrollbar states if one gets removed or if the
-- order changes.
name = "vbox1",
-- Specifying a height is optional but is probably a good idea.
-- If you don't specify a height, it will default to
-- min(height_of_content, 5).
h = 10,
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned underneath the first one.
gui.Label{label="I am a second label!"},
}
```
#### `gui.Spacer`
A "flexible space" element that expands by default. Example usage:
```lua
gui.HBox{
-- These buttons will be on the left-hand side of the screen
gui.Button{label = "Cancel"},
gui.Button{label = "< Back"},
gui.Spacer{},
-- These buttons will be on the right-hand side of the screen
gui.Button{label = "Next >"},
gui.Button{label = "Confirm"},
}
```
I advise against using spacers when `expand = true` and `align = ...` would
work just as well since spacers are implemented hackily and won't account for
some special cases.
You can replicate the above example without spacers, however the code doesn't
look as clean:
```lua
gui.HBox{
-- These buttons will be on the left-hand side of the screen
gui.Button{label = "Cancel"},
gui.Button{label = "< Back", expand = true, align_h = "left"},
-- These buttons will be on the right-hand side of the screen
gui.Button{label = "Next >"},
gui.Button{label = "Confirm"},
}
```
You should not use spacers to centre elements as it creates unnecessary boxes,
and labels may be slightly off-centre (because label widths depend on screen
size, DPI, etc and this code doesn't trigger the centering hack):
```lua
-- This is bad!
gui.HBox{
gui.Spacer{},
gui.Label{label="I am not properly centered!"},
gui.Spacer{},
}
```
You should do this instead:
```lua
gui.Label{label="I am centered!", align_h = "centre"},
```
This applies to other elements as well, because using HBox and Spacer to centre
elements creates unnecessary containers.
#### `gui.Nil`
A tool to allow for ternary-ish conditional widgets:
```lua
gui.VBox{
gui.Label{ label = "The box below is only present if the boolean is truthy" },
the_boolean and gui.Box{ color = "#FF0000" } or gui.Nil{},
}
```
Use sparingly, flow still has to process each `Nil` object to be able to know to
remove it, and thus could still slow things down. The fastest element is one
that doesn't exist, and thus doesn't need processing.
#### `gui.Stack`
This container element places its children on top of each other. All child
elements are expanded in both directions.
Note that some elements (such as centred labels) won't pass clicks through to
the element below them.
Example:
```lua
gui.Stack{
min_w = 10,
gui.Button{label = "Hello world!"},
gui.Image{w = 1, h = 1, texture_name = "air.png", padding = 0.2, align_h = "left"},
}
```
![Screenshot](https://user-images.githubusercontent.com/3182651/215946217-3705dbd1-4ec8-4aed-a9eb-381fecb2d8f2.png)
### Minetest formspec elements
There is an auto-generated
[`elements.md`](https://gitlab.com/luk3yx/minetest-flow/-/blob/main/elements.md)
file which contains a list of elements and parameters. Elements in this list
haven't been tested and might not work.
#### Dynamic element types
If you want to generate element types from a variable, you can use
`{type = "label", label = "Hello world!"}` instead of
`gui.Label{label="Hello world!"}`. HBoxes and VBoxes can be created
this way as well (with `type = "hbox"` and `type = "vbox"`), however other
layouting elements (such as ScrollableVBox and Spacer)
won't work correctly.
An example of this is in `example.lua`.
## Padding, spacing, and backgrounds
All elements can have a `padding` value, which will add the specified amount of
padding around the element. The "root" element of the form (the one returned by
`build_func`) has a default padding of 0.3, everything else has a default
padding of 0.
`HBox` and `VBox` have a `spacing` field which specifies how much spacing there
is between elements inside the box. If unspecified, `spacing` will default to
0.2.
Container elements (HBox and VBox) can optionally have `bgimg` and `bgimg_middle`
parameters that specify a background for the container. The background will be
drawn behind any padding that the container has.
Example:
```lua
gui.VBox{
padding = 0.5,
spacing = 0.1,
-- bgimg can be used without bgimg_middle
bgimg = "air.png",
bgimg_middle = 2,
gui.Button{label="Button 1"},
gui.Button{label="Button 2"},
}
```
![Screenshot](https://user-images.githubusercontent.com/3182651/198194381-4812c0fa-1909-48f8-b50d-6713c4c126ec.png)
The padding around the VBox is 0.5 and the spacing between the buttons inside
it is 0.1.
## Styling forms
### Inline 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 the 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.
### Separate style elements
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{
selectors = {"btn1"},
props = {
bgimg = "button.png",
border = false,
}
},
gui.Button{
name = "btn1",
label = "Button",
},
```
The `Style` and `StyleType` elements are invisible and won't affect padding.
## 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.
</details><details>
<summary><b>Using a form as an inventory</b></summary>
> [!TIP]
> Consider using [Sway](https://content.luanti.org/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.
<details>
<summary><b><code>no_prepend[]</code></b></summary>
You can set `no_prepend = true` on the "root" element to disable formspec
prepends.
Example:
```lua
local my_gui = flow.make_gui(function(player, ctx)
return gui.VBox{
no_prepend = true,
gui.Button{label = "Button 1"},
-- There will be an empty space where the second button would be
gui.Button{label = "Button 2", visible = false},
gui.Button{label = "Button 3"},
}
end)
```
![Screenshot](https://user-images.githubusercontent.com/3182651/212222545-baee3669-15cd-410d-a638-c63b65a8811b.png)
</details><details>
<summary><b><code>bgcolor[]</code></b></summary>
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://api.luanti.org/formspec/#bgcolorbgcolorfullscreenfbgcolor).
</details><details>
<summary><b>Putting the form somewhere else on the screen (likely required for most HUDs)</b></summary>
These values allow the position of the displayed form to be moved around and
adjust how it is scaled.
Example:
```lua
local my_gui = flow.make_gui(function(player, ctx)
return gui.VBox{
-- Adjusts where on the screen the form/HUD is rendered.
-- 0 is the top/left, 1 is the bottom/right
-- You probably want to set `window_position` and `window_anchor` to
-- the same value.
-- This puts the form in the bottom-right corner.
window_position = {x = 1, y = 1},
window_anchor = {x = 1, y = 1},
-- Equivalent to padding[0.1,0.2], adjusts the minimum amount of
-- padding around the form in terms of total screen size. If the form
-- is too big, it will be scaled down
-- Default for formspecs: {x = 0.05, y = 0.05} (i.e. 5% of screen size)
-- HUDs default to a hardcoded pixel size, if you want them to roughly
-- line up with formspecs then you may explicitly specify this.
window_padding = {x = 0.1, y = 0.2},
gui.Label{label = "Hello world"},
}
end)
```
See [the formspec documentation](https://api.luanti.org/formspec/#positionxy)
for more information.
</details><details>
<summary><b>Rendering to a formspec</b></summary>
This API should only be used when necessary and may have breaking changes in
the future.
Some APIs in other mods, such as sfinv, expect formspec strings. You can use
this API to embed flow forms inside them. To use flow with these mods, you can
call `form:render_to_formspec_string(player, ctx, standalone)`.
- By default the the `formspec_version` and `size` elements aren't included in
the returned formspec and are included in a third return value. Set
`standalone` to include them in the returned formspec string. The third
return value will not be returned.
- Returns `formspec, process_event[, info]`
- The `process_event(fields)` callback will return true if the formspec should
be redrawn, where `render_to_formspec_string` should be called and the new
`process_event` should be used in the future. This function may return true
even if fields.quit is sent.
> [!CAUTION]
> Do not use this API with node meta formspecs, it can and will break!
</details><details>
<summary><b>Embedding a form into another form</b></summary>
You can embed form objects inside others like this:
```lua
local parent_form = flow.make_gui(function(player, ctx)
return gui.VBox{
gui.Label{label = "Hello world"},
other_form:embed{
-- Passing in the player is required for now. You must use the same
-- player object that you get sent by flow to avoid breakages in
-- the future if this becomes optional.
player = player,
-- A name for the embed. If this is specified, the embedded form
-- will get its own context (accessible at ctx.my_embed_name) and
-- field names will be rewritten to avoid conflicts with the
-- parent form. If name is not specified, the embedded form will
-- share ctx and ctx.form with the parent, and will not have field
-- names rewritten.
name = "my_embed_name",
},
}
end)
```
Special characters (excluding `-` and `_`) are not allowed in embed names.
</details><details>
<summary><b>Running code when a form is closed</b></summary>
`gui.Container`, `gui.HBox`, `gui.VBox`, and `gui.Stack` elements support an
`on_quit` callback which gets run when a player closes a form.
Note that this function is not called in some cases, such as when the player
leaves without closing the form or when another form/formspec is shown.
This function must not return anything, behaviour may get added to return
values in the future.
```lua
local parent_form = flow.make_gui(function(player, ctx)
return gui.VBox{
on_quit = function(player, ctx)
core.chat_send_player(player:get_player_name(), "Form closed!")
end,
}
end)
```
If multiple `on_quit` callbacks are specified in different elements, they will
all get called.
</details><details>
<summary><b>Handling enter keypresses in fields</b></summary>
`gui.Field` and `gui.Pwdfield` support an `on_key_enter` callback that gets
called if enter is pressed:
```lua
local form = flow.make_gui(function(player, ctx)
return gui.VBox{
gui.Field{
label = "Press enter!",
name = "field",
on_key_enter = function(player, ctx)
core.chat_send_player(player:get_player_name(),
"Field value: " .. dump(ctx.form.field))
end,
-- You can also specify close_on_enter to close the form when enter
-- is pressed.
close_on_enter = true,
},
}
end)
```
Notes:
- If you're using this callback, please make sure there's some other way to
trigger the enter action (like a button) to support older flow versions and
in case I replace this API with a better one in the future.
- If you want recent mobile clients to call this callback when editing text,
add `enter_after_edit = true` to the field definition.
- The similarly named `on_event` gets called whenever the client submits the
field to the server, which could be at any time, and is not very useful, but
is still supported for compatibility (and there may be uses for it, such as
sanitising field values). Be careful not to accidentally use the wrong
callback.
</details>
More detailed documentation is available at
https://luk3yx.gitlab.io/minetest-flow/. Some code snippets have a "run" button
which will open them in a web-based playground, not all of these will work
properly as the playground doesn't support all formspec elements.

179
doc/experimental.md Normal file
View File

@ -0,0 +1,179 @@
# Experimental features
These features might be broken in the future.
## `no_prepend[]`
You can set `no_prepend = true` on the "root" element to disable formspec
prepends.
Example:
```lua
local my_gui = flow.make_gui(function(player, ctx)
return gui.VBox{
no_prepend = true,
gui.Button{label = "Button 1"},
-- There will be an empty space where the second button would be
gui.Button{label = "Button 2", visible = false},
gui.Button{label = "Button 3"},
}
end)
```
![Screenshot](https://user-images.githubusercontent.com/3182651/212222545-baee3669-15cd-410d-a638-c63b65a8811b.png)
## `bgcolor[]`
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://api.luanti.org/formspec/#bgcolorbgcolorfullscreenfbgcolor).
## Putting the form somewhere else on the screen (likely required for most HUDs)
These values allow the position of the displayed form to be moved around and
adjust how it is scaled.
Example:
```lua
local my_gui = flow.make_gui(function(player, ctx)
return gui.VBox{
-- Adjusts where on the screen the form/HUD is rendered.
-- 0 is the top/left, 1 is the bottom/right
-- You probably want to set `window_position` and `window_anchor` to
-- the same value.
-- This puts the form in the bottom-right corner.
window_position = {x = 1, y = 1},
window_anchor = {x = 1, y = 1},
-- Equivalent to padding[0.1,0.2], adjusts the minimum amount of
-- padding around the form in terms of total screen size. If the form
-- is too big, it will be scaled down
-- Default for formspecs: {x = 0.05, y = 0.05} (i.e. 5% of screen size)
-- HUDs default to a hardcoded pixel size, if you want them to roughly
-- line up with formspecs then you may explicitly specify this.
window_padding = {x = 0.1, y = 0.2},
gui.Label{label = "Hello world"},
}
end)
```
See [the formspec documentation](https://api.luanti.org/formspec/#positionxy)
for more information.
## Rendering to a formspec
This API should only be used when necessary and may have breaking changes in
the future.
Some APIs in other mods, such as sfinv, expect formspec strings. You can use
this API to embed flow forms inside them. To use flow with these mods, you can
call `form:render_to_formspec_string(player, ctx, standalone)`.
- By default the the `formspec_version` and `size` elements aren't included in
the returned formspec and are included in a third return value. Set
`standalone` to include them in the returned formspec string. The third
return value will not be returned.
- Returns `formspec, process_event[, info]`
- The `process_event(fields)` callback will return true if the formspec should
be redrawn, where `render_to_formspec_string` should be called and the new
`process_event` should be used in the future. This function may return true
even if fields.quit is sent.
> **Warning:**
> Do not use this API with node meta formspecs, it can and will break!
## Embedding a form into another form
You can embed form objects inside others like this:
```lua
local parent_form = flow.make_gui(function(player, ctx)
return gui.VBox{
gui.Label{label = "Hello world"},
other_form:embed{
-- Passing in the player is required for now. You must use the same
-- player object that you get sent by flow to avoid breakages in
-- the future if this becomes optional.
player = player,
-- A name for the embed. If this is specified, the embedded form
-- will get its own context (accessible at ctx.my_embed_name) and
-- field names will be rewritten to avoid conflicts with the
-- parent form. If name is not specified, the embedded form will
-- share ctx and ctx.form with the parent, and will not have field
-- names rewritten.
name = "my_embed_name",
},
}
end)
```
Special characters (excluding `-` and `_`) are not allowed in embed names.
## Running code when a form is closed
`gui.Container`, `gui.HBox`, `gui.VBox`, and `gui.Stack` elements support an
`on_quit` callback which gets run when a player closes a form.
Note that this function is not called in some cases, such as when the player
leaves without closing the form or when another form/formspec is shown.
This function must not return anything, behaviour may get added to return
values in the future.
```lua
local parent_form = flow.make_gui(function(player, ctx)
return gui.VBox{
on_quit = function(player, ctx)
core.chat_send_player(player:get_player_name(), "Form closed!")
end,
}
end)
```
If multiple `on_quit` callbacks are specified in different elements, they will
all get called.
## Handling enter keypresses in fields
`gui.Field` and `gui.Pwdfield` support an `on_key_enter` callback that gets
called if enter is pressed:
```lua
local form = flow.make_gui(function(player, ctx)
return gui.VBox{
gui.Field{
label = "Press enter!",
name = "field",
on_key_enter = function(player, ctx)
core.chat_send_player(player:get_player_name(),
"Field value: " .. dump(ctx.form.field))
end,
-- You can also specify close_on_enter to close the form when enter
-- is pressed.
close_on_enter = true,
},
}
end)
```
Notes:
- If you're using this callback, please make sure there's some other way to
trigger the enter action (like a button) to support older flow versions and
in case I replace this API with a better one in the future.
- If you want recent mobile clients to call this callback when editing text,
add `enter_after_edit = true` to the field definition.
- The similarly named `on_event` gets called whenever the client submits the
field to the server, which could be at any time, and is not very useful, but
is still supported for compatibility (and there may be uses for it, such as
sanitising field values). Be careful not to accidentally use the wrong
callback.

20
doc/hiding-elements.md Normal file
View File

@ -0,0 +1,20 @@
# Hiding elements
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.
## Example
```lua
gui.VBox{
gui.Button{label = "First button"},
gui.Button{label = "Hidden", visible = false},
gui.Button{label = "Last button"},
}
```
## Alternatives
If you don't want hidden elements to take up any space, see the documentation
for [gui.Nil](layout-elements.md#guinil).

1
doc/index.md Symbolic link
View File

@ -0,0 +1 @@
../README.md

38
doc/inventory.md Normal file
View File

@ -0,0 +1,38 @@
# Using a form as an inventory
> Consider using [Sway](https://content.luanti.org/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.

166
doc/layout-elements.md Normal file
View File

@ -0,0 +1,166 @@
# Layouting elements
You should do `local gui = flow.widgets` in your code to improve readability.
All examples will assume that this line exists.
These elements are used to lay out elements in the form. They don't have a
direct equivalent in formspecs.
## `gui.VBox`
A vertical box, similar to a VBox in GTK. Elements inside a VBox are stacked
vertically.
```lua
gui.VBox{
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned underneath the first one.
gui.Label{label="I am a second label!"},
}
```
Elements inside boxes have a spacing of 0.2 between them. To change this, you
can add `spacing = <number>` to the box definition. For example, `spacing = 0`
will remove all spacing between the elements.
## `gui.HBox`
Like `gui.VBox` but stacks elements horizontally instead.
```lua
gui.HBox{
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned to the right of first one.
gui.Label{label="I am a second label!"},
-- You can nest HBox and VBox elements
gui.VBox{
gui.Image{w=1, h=1, texture_name="default_dirt.png", align_h="centre"},
gui.Label{label="Dirt", expand=true, align_h="centre"},
}
}
```
## `gui.ScrollableVBox`
Similar to `gui.VBox` but uses a scroll_container and automatically adds a
scrollbar. You must specify a width and height for the scroll container.
```lua
gui.ScrollableVBox{
-- A name must be provided for ScrollableVBox elements. You don't
-- have to use this name anywhere else, it just makes sure flow
-- doesn't mix up scrollbar states if one gets removed or if the
-- order changes.
name = "vbox1",
-- Specifying a height is optional but is probably a good idea.
-- If you don't specify a height, it will default to
-- min(height_of_content, 5).
h = 10,
-- These elements are documented later on.
gui.Label{label="I am a label!"},
-- The second label will be positioned underneath the first one.
gui.Label{label="I am a second label!"},
}
```
## `gui.Spacer`
A "flexible space" element that expands by default. Example usage:
```lua
gui.HBox{
-- These buttons will be on the left-hand side of the screen
gui.Button{label = "Cancel"},
gui.Button{label = "< Back"},
gui.Spacer{},
-- These buttons will be on the right-hand side of the screen
gui.Button{label = "Next >"},
gui.Button{label = "Confirm"},
}
```
I advise against using spacers when `expand = true` and `align = ...` would
work just as well since spacers are implemented hackily and won't account for
some special cases.
You can replicate the above example without spacers, however the code doesn't
look as clean:
```lua
gui.HBox{
-- These buttons will be on the left-hand side of the screen
gui.Button{label = "Cancel"},
gui.Button{label = "< Back", expand = true, align_h = "left"},
-- These buttons will be on the right-hand side of the screen
gui.Button{label = "Next >"},
gui.Button{label = "Confirm"},
}
```
You should not use spacers to centre elements as it creates unnecessary boxes,
and labels may be slightly off-centre (because label widths depend on screen
size, DPI, etc and this code doesn't trigger the centering hack):
```lua
-- This is bad!
gui.HBox{
gui.Spacer{},
gui.Label{label="I am not properly centered!"},
gui.Spacer{},
}
```
You should do this instead:
```lua
gui.Label{label="I am centered!", align_h = "centre"},
```
This applies to other elements as well, because using HBox and Spacer to centre
elements creates unnecessary containers.
## `gui.Nil`
A tool to allow for ternary-ish conditional widgets:
```lua
gui.VBox{
gui.Label{ label = "The box below is only present if the boolean is truthy" },
the_boolean and gui.Box{ color = "#FF0000" } or gui.Nil{},
}
```
Use sparingly, flow still has to process each `Nil` object to be able to know to
remove it, and thus could still slow things down. The fastest element is one
that doesn't exist, and thus doesn't need processing.
## `gui.Stack`
This container element places its children on top of each other. All child
elements are expanded in both directions.
Note that some elements (such as centred labels) won't pass clicks through to
the element below them.
### Example
```lua
gui.Stack{
min_w = 10,
gui.Button{label = "Hello world!"},
gui.Image{w = 1, h = 1, texture_name = "air.png", padding = 0.2, align_h = "left"},
}
```
![Screenshot](https://user-images.githubusercontent.com/3182651/215946217-3705dbd1-4ec8-4aed-a9eb-381fecb2d8f2.png)

40
doc/manual-positioning.md Normal file
View File

@ -0,0 +1,40 @@
# Manual positioning
## Dynamic element types
This is only recommended for elements inside `gui.Container` (see below)
outside of some rare use cases that need it, as it does not support flow's
layouting elements.
If you want to generate element types from a variable, you can use
`{type = "label", label = "Hello world!"}` instead of
`gui.Label{label="Hello world!"}`. HBoxes and VBoxes can be created
this way as well (with `type = "hbox"` and `type = "vbox"`), however other
layouting elements (such as ScrollableVBox and Spacer) won't work correctly.
An example of this is in `example.lua`.
## Manual positioning of elements
You can use `gui.Container` elements to contain manually positioned elements.
```lua
gui.VBox{
gui.Label{label = "Automatically positioned"},
gui.Container{
-- You can specify a width and height if you don't want flow to try and
-- guess at the size of the container.
-- w = 3, h = 2,
-- You may embed most formspec_ast elements inside gui.Container
{type = "box", x = 0, y = 0, w = 1, h = 1, color = "red"},
{type = "box", x = 0.3, y = 0.3, w = 1, h = 1, color = "green"},
{type = "box", x = 0.6, y = 0.6, w = 1, h = 1, color = "blue"},
{type = "label", x = 2, y = 1.1, label = "Manually positioned"}
},
}
```
Note that you cannot nest layouted elements (like `gui.VBox`) inside
`gui.Container`.

35
doc/padding.md Normal file
View File

@ -0,0 +1,35 @@
## Padding, spacing, and backgrounds
All elements can have a `padding` value, which will add the specified amount of
padding around the element. The "root" element of the form (the one returned by
`build_func`) has a default padding of 0.3, everything else has a default
padding of 0.
`HBox` and `VBox` have a `spacing` field which specifies how much spacing there
is between elements inside the box. If unspecified, `spacing` will default to
0.2.
Container elements (HBox and VBox) can optionally have `bgimg` and `bgimg_middle`
parameters that specify a background for the container. The background will be
drawn behind any padding that the container has.
Example:
```lua
gui.VBox{
padding = 0.5,
spacing = 0.1,
-- bgimg can be used without bgimg_middle
bgimg = "air.png",
bgimg_middle = 2,
gui.Button{label="Button 1"},
gui.Button{label="Button 2"},
}
```
![Screenshot](https://user-images.githubusercontent.com/3182651/198194381-4812c0fa-1909-48f8-b50d-6713c4c126ec.png)
The padding around the VBox is 0.5 and the spacing between the buttons inside
it is 0.1.

51
doc/playground-links.js Normal file
View File

@ -0,0 +1,51 @@
"use strict";
const PLAYGROUND_URL = "https://luk3yx.gitlab.io/minetest-flow-playground/";
function getPlaygroundLink(code) {
return PLAYGROUND_URL + "#code=" + encodeURIComponent(code);
}
function addPlaygroundBtn(el, code) {
const btn = document.createElement("button");
btn.onclick = () => {
window.open(getPlaygroundLink(code));
};
btn.textContent = "\u25b6";
btn.title = "Run this code online";
btn.style.float = "right";
el.insertBefore(btn, el.firstChild);
}
const TEMPLATE = `
local gui = flow.widgets
local form = flow.make_gui(function(player, ctx)
%
end)
form:show(core.get_player_by_name("playground"))
`.trim();
function addPlayBtn({el, result, text}) {
if (!el.classList.contains("language-lua"))
return;
// The playground does not support styling
if (text.indexOf("style = {") > 0 || text.indexOf("gui.Style") > 0)
return;
if (text.startsWith("gui.")) {
addPlaygroundBtn(el, TEMPLATE.replace("%",
" return " + text.trim().replaceAll("\n", "\n ")));
} else if (/^local (my_gui|form) = flow.make_gui\(function\(player, ctx\)\n/.test(text) &&
text.trim().endsWith("\nend)")) {
addPlaygroundBtn(el, TEMPLATE.replace("%",
text.trim().split("\n").slice(1, -1).join("\n")));
}
}
hljs.addPlugin({
'after:highlightElement': addPlayBtn,
})

65
doc/styling.md Normal file
View File

@ -0,0 +1,65 @@
# Styling forms
## Inline 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 the 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.
## Separate style elements
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{
selectors = {"btn1"},
props = {
bgimg = "button.png",
border = false,
}
},
gui.Button{
name = "btn1",
label = "Button",
},
```
The `Style` and `StyleType` elements are invisible and won't affect padding.

11
doc/tooltips.md Normal file
View File

@ -0,0 +1,11 @@
# Tooltips
You can add tooltips to elements using the `tooltip` field:
```lua
gui.Image{
w = 2, h = 2,
texture_name = "air.png",
tooltip = "Air",
}
```

View File

@ -169,7 +169,7 @@ if __name__ == '__main__':
elements = fetch_elements()
print('Done.')
with open('elements.md', 'w') as f:
with open('doc/elements.md', 'w') as f:
f.write('# Auto-generated elements list\n\n')
f.write('This is probably broken.')
for element_name, variants in elements.items():

20
mkdocs.yml Normal file
View File

@ -0,0 +1,20 @@
site_name: Flow Documentation
docs_dir: doc
theme: readthedocs
extra_javascript:
- playground-links.js
markdown_extensions:
- footnotes
nav:
- Introduction: index.md
- Elements/widgets:
- layout-elements.md
- All other elements: elements.md
- Other features:
- styling.md
- padding.md
- tooltips.md
- hiding-elements.md
- inventory.md
- manual-positioning.md
- experimental.md