From 71bbfd524e1488111028a48d88f3707d6a72f4ea Mon Sep 17 00:00:00 2001 From: luk3yx Date: Thu, 27 Feb 2025 17:20:08 +1300 Subject: [PATCH] Use mkdocs for documentation --- .gitlab-ci.yml | 16 + README.md | 545 +-------------------------------- elements.md => doc/elements.md | 0 doc/experimental.md | 179 +++++++++++ doc/hiding-elements.md | 20 ++ doc/index.md | 1 + doc/inventory.md | 38 +++ doc/layout-elements.md | 166 ++++++++++ doc/manual-positioning.md | 40 +++ doc/padding.md | 35 +++ doc/playground-links.js | 51 +++ doc/styling.md | 65 ++++ doc/tooltips.md | 11 + generate_docs.py | 2 +- mkdocs.yml | 20 ++ 15 files changed, 648 insertions(+), 541 deletions(-) create mode 100644 .gitlab-ci.yml rename elements.md => doc/elements.md (100%) create mode 100644 doc/experimental.md create mode 100644 doc/hiding-elements.md create mode 120000 doc/index.md create mode 100644 doc/inventory.md create mode 100644 doc/layout-elements.md create mode 100644 doc/manual-positioning.md create mode 100644 doc/padding.md create mode 100644 doc/playground-links.js create mode 100644 doc/styling.md create mode 100644 doc/tooltips.md create mode 100644 mkdocs.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..40756a7 --- /dev/null +++ b/.gitlab-ci.yml @@ -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 diff --git a/README.md b/README.md index 8b6d5b2..a6bde7f 100644 --- a/README.md +++ b/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 = ` 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 - -
-Tooltips - -You can add tooltips to elements using the `tooltip` field: - -```lua -gui.Image{ - w = 2, h = 2, - texture_name = "air.png", - tooltip = "Air", -} -``` - -
-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. - -
-Using a form as an inventory - -> [!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. - -
- -### 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. - - -> [!CAUTION] -> 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. - -
+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. diff --git a/elements.md b/doc/elements.md similarity index 100% rename from elements.md rename to doc/elements.md diff --git a/doc/experimental.md b/doc/experimental.md new file mode 100644 index 0000000..8f54923 --- /dev/null +++ b/doc/experimental.md @@ -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. diff --git a/doc/hiding-elements.md b/doc/hiding-elements.md new file mode 100644 index 0000000..c9e1a04 --- /dev/null +++ b/doc/hiding-elements.md @@ -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). diff --git a/doc/index.md b/doc/index.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/doc/index.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/doc/inventory.md b/doc/inventory.md new file mode 100644 index 0000000..11e1fe9 --- /dev/null +++ b/doc/inventory.md @@ -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. diff --git a/doc/layout-elements.md b/doc/layout-elements.md new file mode 100644 index 0000000..b4ccd56 --- /dev/null +++ b/doc/layout-elements.md @@ -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 = ` 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) diff --git a/doc/manual-positioning.md b/doc/manual-positioning.md new file mode 100644 index 0000000..5b7e4b4 --- /dev/null +++ b/doc/manual-positioning.md @@ -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`. diff --git a/doc/padding.md b/doc/padding.md new file mode 100644 index 0000000..b806a57 --- /dev/null +++ b/doc/padding.md @@ -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. diff --git a/doc/playground-links.js b/doc/playground-links.js new file mode 100644 index 0000000..b30ea53 --- /dev/null +++ b/doc/playground-links.js @@ -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, +}) diff --git a/doc/styling.md b/doc/styling.md new file mode 100644 index 0000000..d21745d --- /dev/null +++ b/doc/styling.md @@ -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. diff --git a/doc/tooltips.md b/doc/tooltips.md new file mode 100644 index 0000000..398ad6d --- /dev/null +++ b/doc/tooltips.md @@ -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", +} +``` diff --git a/generate_docs.py b/generate_docs.py index ab40f44..d0fbe38 100644 --- a/generate_docs.py +++ b/generate_docs.py @@ -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(): diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..1746c7b --- /dev/null +++ b/mkdocs.yml @@ -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