forked from your-land-mirror/minetest-flow
Use mkdocs for documentation
This commit is contained in:
parent
fb069d203c
commit
71bbfd524e
16
.gitlab-ci.yml
Normal file
16
.gitlab-ci.yml
Normal 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
545
README.md
@ -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"},
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 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"},
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
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)
|
||||
```
|
||||
|
||||

|
||||
|
||||
</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
179
doc/experimental.md
Normal 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)
|
||||
```
|
||||
|
||||

|
||||
|
||||
## `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
20
doc/hiding-elements.md
Normal 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
1
doc/index.md
Symbolic link
@ -0,0 +1 @@
|
||||
../README.md
|
38
doc/inventory.md
Normal file
38
doc/inventory.md
Normal 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
166
doc/layout-elements.md
Normal 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"},
|
||||
}
|
||||
```
|
||||
|
||||

|
40
doc/manual-positioning.md
Normal file
40
doc/manual-positioning.md
Normal 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
35
doc/padding.md
Normal 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"},
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
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
51
doc/playground-links.js
Normal 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
65
doc/styling.md
Normal 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
11
doc/tooltips.md
Normal 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",
|
||||
}
|
||||
```
|
@ -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
20
mkdocs.yml
Normal 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
|
Loading…
Reference in New Issue
Block a user