Skip to content

Latest commit

 

History

History
165 lines (127 loc) · 6.58 KB

README.md

File metadata and controls

165 lines (127 loc) · 6.58 KB

flow

ContentDB

An experimental layout manager and formspec API replacement for Luanti (formerly Minetest). Vaguely inspired by Flutter and GTK.

Online tutorial/demo (source)

Features

Layouting

  • No manual positioning of elements.
  • Automatic layouting using HBox and VBox containers
  • Some elements have an automatic size.
  • The size of elements can optionally expand to fit larger spaces
  • Elements which get their size based on their label length automatically become larger/smaller to fit long translations1.

Other features

  • No form names. Form names are still used internally, however they are hidden from the API.
  • No having to worry about state.
  • Values of fields, scrollbars, checkboxes, etc are remembered when redrawing a form and are automatically applied.
  • Has an inspector mod to help with developing and debugging forms.
  • Some common security issues with formspec input handling are mitigated.

Limitations

  • This mod doesn't support all of the features that regular formspecs do.
  • FS51 is required if you want to have full support for Minetest 5.3 and below.
  • Make sure you're using the latest version of flow if you are on MT 5.10-dev or later, older versions used a hack which no longer works.

Basic example

See example.lua for a more comprehensive example which demonstrates how layouting and alignment works.

-- GUI elements are accessible with flow.widgets. Using
-- `local gui = flow.widgets` is recommended to reduce typing.
local gui = flow.widgets

-- GUIs are created with flow.make_gui(build_func).
local my_gui = flow.make_gui(function(player, ctx)
    -- The build function should return a GUI element such as gui.VBox.
    -- `ctx` can be used to store context. `ctx.form` is reserved for storing
    -- the state of elements in the form. For example, you can use
    -- `ctx.form.my_checkbox` to check whether `my_checkbox` is checked. Note
    -- that ctx.form.element may be nil instead of its default value.

    -- This function may be called at any time by flow.

    -- gui.VBox is a "container element" added by this mod.
    return gui.VBox {
        gui.Label {label = "Here is a dropdown:"},
        gui.Dropdown {
            -- The value of this dropdown will be accessible from ctx.form.my_dropdown
            name = "my_dropdown",
            items = {'First item', 'Second item', 'Third item'},
            index_event = true,
        },
        gui.Button {
            label = "Get dropdown index",
            on_event = function(player, ctx)
                -- flow should guarantee that `ctx.form.my_dropdown` exists, even if the client doesn't send my_dropdown to the server.
                local selected_idx = ctx.form.my_dropdown
                core.chat_send_player(player:get_player_name(), "You have selected item #" .. selected_idx .. "!")
            end,
        }
    }
end)

-- Show the GUI to player as an interactive form
-- Note that `player` is a player object and not a player name.
my_gui:show(player)

-- You can provide an initial value for `ctx` by adding a second parameter to
-- show(). In the below example, `ctx.value` will be "test".
my_gui:show(player, {value = "test"})

-- Close the form
my_gui:close(player)

-- Alternatively, the GUI can be shown as a non-interactive HUD (requires
-- hud_fs to be installed).
my_gui:show_hud(player)
my_gui:close_hud(player)

Updating forms

If some data displayed inside a form changes (for example a timer or progress indicator), you can use form:update to update the form without resetting ctx or showing the form again if the player has closed it.

Due to formspec limitations, players may lose text typed into fields that hasn't been sent to the server when form:update is called.

-- Re-shows the form for one player if they have the form open
my_gui:update(player)

-- Re-shows the form for all players that have the form open and where
-- ctx.test == 123
my_gui:update_where(function(player, ctx)
    return ctx.test == 123
end)

-- Re-shows the form for all players with the "server" privilege
my_gui:update_where(function(player, ctx)
    return core.check_player_privs(player, "server")
end)

-- Re-shows the form for all players with the form open
my_gui:update_where(function() return true end)

Inside an on_event handler, you can use return true instead.

gui.Button{
    label = "Update form",
    on_event = function(player, ctx)
        return true
    end,
}

If you're using a form as a HUD, you must call form:show_hud to update it.

Security

Flow ignores potentially malicious formspec input from clients, such as buttons or fields that haven't been shown to the client, out-of-bounds dropdown selections, and newlines in Field elements (where it's impossible to enter a newline without pasting it in).

Other formspec libraries/utilities

These utilities likely aren't compatible with flow.

  • fs_layout is another mod library that does automatic formspec element positioning.
  • fslib is a small mod library that lets you build formspec strings.
  • Just_Visiting's formspec editor is a Minetest (sub)game that lets you edit formspecs and preview them as you go
  • kuto is a formspec library that has some extra widgets/components and has a callback API. Some automatic sizing can be done for buttons.
    • It may be possible to use kuto's components with flow somehow as they both use formspec_ast internally.
    • kuto was the the source of the "on_event" function idea.
  • My web-based formspec editor lets you add elements and drag+drop them, however it doesn't support all formspec features.

Documentation

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.

Footnotes

  1. This isn't perfect, the actual size of labels depends on the client's font, DPI, window size, and formspec-related scaling settings.