Initial commit.
This commit is contained in:
commit
1040034efa
4 changed files with 1667 additions and 0 deletions
121
LICENSE.txt
Normal file
121
LICENSE.txt
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
377
README.md
Normal file
377
README.md
Normal file
|
|
@ -0,0 +1,377 @@
|
||||||
|
# What is this?
|
||||||
|
|
||||||
|
Imfs is a reactive, immediate-mode functional abstration over Minetest's often cumbersome string-based formspec format.
|
||||||
|
What this means is that:
|
||||||
|
* An interface (or "formspec") is represented by a builder function, which creates and returns an imfs element tree.
|
||||||
|
* Every time state changes, the builder function is called again to create a new tree reflecting the new state.
|
||||||
|
* Elements and containers are represented as linear function calls. You don't have to care about child management or tree manipulation; imfs does that internally.
|
||||||
|
* You don't have to manually interpolate variables.
|
||||||
|
* You don't have to remember to update the UI every time you make a state change. By wrapping data in states, you can make a fully reactive UI contained almost entirely in one builder function.
|
||||||
|
* You don't have to care about element names and event handling. With imfs, event handlers can be registered inline, while the library does the rest.
|
||||||
|
|
||||||
|
# Concepts
|
||||||
|
|
||||||
|
### Builder functions
|
||||||
|
|
||||||
|
A builder function is a function that creates and returns an element tree to be rendered. There is thus only one requirement of such a function: it must call `imfs.begin(window_width, window_height)`, then return `imfs.end_()`. Elements may then be added between the calls to `start` and `end_`. The most important advantage of this approach is that it allows you to perform conditional rendering merely by using if statements, in addition to any other operations you may see fit.
|
||||||
|
|
||||||
|
Builder functions are used by either `imfs.show` (to show an interface to a player) or `imfs.set_inventory` (to set the player's inventory). These functions are described in more detail below.
|
||||||
|
|
||||||
|
You can pass a table as the last argument to an entry point like `imfs.show` to hold state external to the builder function; it will then be passed as an argument to the builder function during every rebuild. This way, you can declare the builder function itself in one place and use it in another without having to rely entirely on closure capture or global variables.
|
||||||
|
|
||||||
|
Imfs does not strictly require using a builder function: if an interface is purely static and need never change, you can use a prebuilt element tree instead of a builder function, which avoids rebuilding the tree every time the interface is shown.
|
||||||
|
|
||||||
|
### Elements
|
||||||
|
|
||||||
|
Elements are created with function calls, like `imfs.box(0, 0, 2, 2, "#faa")`. This creates an element, adds it to the current container, and returns it. For simple elements like `label`, this is all that needs to happen; however, some elements, like `field`, support further configuring the element using chainable methods, like so:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
imfs.field(4, 2, 4, 0.75, "Test", str)
|
||||||
|
:multiline()
|
||||||
|
:onchange(function(value)
|
||||||
|
print(value)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that you can register an event handler with `:onchange`; in this case, the event handler will be called whenever a change in the field's value is detected. This makes event handling quite straightforward, since event responses are tied directly to the element that is expected to trigger them (rather than being crammed together in a single callback registration somewhere else).
|
||||||
|
|
||||||
|
Because elements are just function calls, you can easily create your own widgets simply by creating a function that creates a predefined sequence of elements. For example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Adds four colored boxes using a single call.
|
||||||
|
local function quadbox(x, y, w, h)
|
||||||
|
imfs.box(x, y, w/2, h/2, "#faa")
|
||||||
|
imfs.box(x + w/2, y, w/2, h/2, "#afa")
|
||||||
|
imfs.box(x, y + h/2, w/2, h/2, "#aaf")
|
||||||
|
imfs.box(x + w/2, y + h/2, w/2, h/2, "#ffa")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function builder()
|
||||||
|
imfs.begin(12, 10)
|
||||||
|
|
||||||
|
imfs.label(0.5, 0.5, "Quadbox:")
|
||||||
|
|
||||||
|
quadbox(0.5, 1, 4, 4)
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
More advanced widgets with chainable methods can be created using Lua classes (the implementations of the builtin elements typify this technique).
|
||||||
|
|
||||||
|
### Containers
|
||||||
|
|
||||||
|
Containers are, in essence, lists of elements. There are three types of containers: scroll containers, layouting managers, and the formspec itself. The top-level formspec container performs minimal layouting; it only evaluates percentage units to always be relative to its own size. The scroll container functions similarly, but wraps its children in a `scroll_container[]` element internally.
|
||||||
|
|
||||||
|
Besides these basic containers, however, imfs also provides several layouting containers. These are:
|
||||||
|
* `imfs.group`: The same type of container as the root; the positions and percentage units of its children are relative to the position and size of the container respectively.
|
||||||
|
* `imfs.row`: A flex row. This automatically positions elements within it based on the provided gap and alignment. Child elements may specify their width as a grow ratio like `"1x"` to dynamically resize to fill any empty space in the row. Higher grow ratios will cause the element to take up a larger portion of the available space compared to other dynamically sized elements.
|
||||||
|
* `imfs.column`: The same as `imfs.row`, but in the vertical direction.
|
||||||
|
|
||||||
|
To create a custom layouting container, the procedure is essentially as follows:
|
||||||
|
1. Create a Lua class for the container.
|
||||||
|
2. In the class constructor, call `imfs.container_start(container)` with the newly created container instance.
|
||||||
|
3. Create an end function that wraps `imfs.container_end()`.
|
||||||
|
4. In the class's `render(self)` method, `for _, child in ipairs(self) do child:render() end`.
|
||||||
|
5. Add custom layouting calculations to `render()`, and pass modifications to `child:render()`.
|
||||||
|
|
||||||
|
(Be advised that certain elements, like labels, have no knowable size.)
|
||||||
|
|
||||||
|
### State
|
||||||
|
|
||||||
|
Being able to imperatively create a UI is nice, and already better than formspecs, but many UIs must dynamically update to reflect state. Doing this manually can become bothersome, which is why imfs is also reactive. This means that it can trigger a rebuild automatically whenever state changes, saving you the trouble of remembering to update the UI when you update a variable. To do this, you must set any of an element's properties to a state object.
|
||||||
|
|
||||||
|
State is created by wrapping the initial value in an `imfs.state`, like so: `local counter = imfs.state(0)`. To read the state's value (and register it as a dependency of any active observers, like an imfs UI), call it like a function: `counter()`. To set the state's value (and notify any dependents), call it with the new value: `counter(3)`.
|
||||||
|
|
||||||
|
Imfs also provides `imfs.derive`, which creates a derived state from a function based on the states accessed in the funtion. This way, you can perform operations on a state's value for display while still depending on the state from the element involved. It should be noted, however, that beause full rebuilds for every change are an intrinsic property of formspecs, there is not currently much benefit to having a certain element depend on a state rather than the formspec itself, so in most situations derived states will not really be useful. An exception is if you want to create side effects; since `imfs.derive` takes a function, you can do anything you like in that function, so it might sometimes be useful to have a function that is called whenever a dependent state's value is changed. (Indeed, this mechanism may prove more useful in non-UI applications than in actual imfs UI.)
|
||||||
|
|
||||||
|
Each state object includes an `_old_val` property, which holds the value the state had before it was last changed. In addition, you can also use the `_val` property to access a state's value directly and bypass dependency resolution.
|
||||||
|
|
||||||
|
If you want to use the state API for a system of your own, remember that:
|
||||||
|
* State objects store dependents in `_getters`.
|
||||||
|
* When set, a state notifies each entry in `_getters` by calling that object's `:update()` method.
|
||||||
|
* To collect state dependencies from a given function, 1) create a tracking table and `table.insert(imfs.state.observers, tracker)`, 2) call the function, and 3) `table.remove(imfs.state.observers)`. The tracking table will then be filled with all states that were accessed between the `table.insert` and `table.remove`.
|
||||||
|
|
||||||
|
# Example code
|
||||||
|
|
||||||
|
Create a mod with this code and run `/demo` to see the example in action.
|
||||||
|
Note particularly that if you join multiple users and have them all run `/demo`, any action by one will also update all the others' formspecs, as the state used here is global.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local counter = imfs.state(0)
|
||||||
|
local str = imfs.state("")
|
||||||
|
local pressed = imfs.state(false)
|
||||||
|
|
||||||
|
local function fs()
|
||||||
|
imfs.begin(12, 10)
|
||||||
|
|
||||||
|
imfs.label(1, 2, "Counter: "..counter()) -- We don't need to use a derived state here because the window will detect this dependency.
|
||||||
|
imfs.label(1, 3, derive(function() return "Length: "..#str() end)) -- However, using a derived state will still work just fine.
|
||||||
|
|
||||||
|
if pressed() then -- Reactive conditional rendering can be achieved using states.
|
||||||
|
imfs.label(1, 4, "Pressed!!")
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.button(4, 0.5, 4, 0.75, "Increment")
|
||||||
|
:style {
|
||||||
|
bgcolor = "#aaf"
|
||||||
|
}
|
||||||
|
:style("hovered", {
|
||||||
|
bgcolor = "#faa"
|
||||||
|
})
|
||||||
|
:style("pressed", {
|
||||||
|
bgcolor = "#afa"
|
||||||
|
})
|
||||||
|
:onclick(function()
|
||||||
|
counter(counter() +1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
imfs.field(4, 2, 4, 0.75, "Test", str)
|
||||||
|
:onchange(function(value)
|
||||||
|
str(value)
|
||||||
|
end)
|
||||||
|
:onenter(function(value)
|
||||||
|
str(value)
|
||||||
|
pressed(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
imfs.scroll_container(4, 3, 8, 7)
|
||||||
|
:named("test")
|
||||||
|
|
||||||
|
for i = 0, 10 do -- One benefit of functional style over table style is that you can use loops seamlessly.
|
||||||
|
imfs.label(0, i, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
imfs.row(0, 1, 8, 4)
|
||||||
|
|
||||||
|
imfs.box(0, 0, "1x", 2, "#f88") -- The "1x" unit for the width marks this element as dynamically resizing.
|
||||||
|
imfs.box(0, 0, counter() *0.25, 2, "#8f8")
|
||||||
|
imfs.box(0, 0, 2, 2, "#88f")
|
||||||
|
|
||||||
|
imfs.row_end()
|
||||||
|
|
||||||
|
imfs.column(0, 3, 4, 8)
|
||||||
|
:align "center" -- Notice that the elements are centered vertically in the column.
|
||||||
|
|
||||||
|
imfs.box(0, 0, 2, counter() *0.25, "#8f8")
|
||||||
|
imfs.box(0, 0, 2, 2, "#88f")
|
||||||
|
|
||||||
|
imfs.column_end()
|
||||||
|
|
||||||
|
imfs.scroll_container_end()
|
||||||
|
|
||||||
|
return imfs.end_()
|
||||||
|
end
|
||||||
|
|
||||||
|
minetest.register_chatcommand("demo", {
|
||||||
|
func = function(name)
|
||||||
|
imfs.show(name, fs)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
# API Reference
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### `imfs.export()`
|
||||||
|
|
||||||
|
Export the imfs API to \_G, with "fs_" prefixes. This is mainly intended for custom, from-scratch games to make life a bit easier.
|
||||||
|
|
||||||
|
### `imfs.begin(window_width, window_height)`
|
||||||
|
|
||||||
|
Begin a root imfs container. This returns a `Window` object with the following methods:
|
||||||
|
* `Window:no_prepend()`: Disables formspec prepends (i.e. global theme) in this interface.
|
||||||
|
* `Window:position(x, y)`: Set the position of the formspec window. See `position[]` in the [Minetest API documentation](https://api.luanti.org/formspec/).
|
||||||
|
* `Window:anchor(x, y)`: Set the anchor point of the formspec window. See `anchor[]` in the [Minetest API documentation](https://api.luanti.org/formspec/).
|
||||||
|
* `Window:padding(x, y)`: Set the padding of the formspec window. See `padding[]` in the [Minetest API documentation](https://api.luanti.org/formspec/).
|
||||||
|
* `Window:modal([modal])`: Make this window modal (meaning that it cannot be directly closed by the user with Esc). If `modal` is false, there will be no effect (this can be useful for making modality state-dependent).
|
||||||
|
* `Window:onclose(fn)`: Registers `fn` to be called when the window closes. (This may be triggered when the target player leaves the game.)
|
||||||
|
|
||||||
|
### `imfs.end_()`
|
||||||
|
|
||||||
|
Ends the current window, and returns it. Note: The name ends with an underscore so as not to conflict with the Lua keyword (I couldn't think of a better word for ending the window than 'end').
|
||||||
|
|
||||||
|
### `imfs.show(player, builder, state)`
|
||||||
|
|
||||||
|
Show the interface defined by the provided builder function to the given player. The generated context will remain active until the player closes the formspec or leaves the game.
|
||||||
|
|
||||||
|
This returns a `Context` object, which is the the top-level dependent for all states used in the interface. Unlike in some other libraries, you don't really store state on the `Context` object (the builder function can't access it directly). Instead, you should store state as local variables in the function from which you call `show` (or somewhere else if they should be global) along with the builder function; that way, the builder function can capture the state specific to the action and the player that invoked the interface and only that. You can alternatively declare the builder function externally, then pass the new state objects in the `args` table, as it will be passed on to the builder function and will persist across rebuilds. This is arguably the better option since it avoids re-allocating the builder function every time it's first shown.
|
||||||
|
|
||||||
|
You can pass a static imfs tree instead of a builder if you so desire.
|
||||||
|
|
||||||
|
### `imfs.set_inventory(player, builder, state)`
|
||||||
|
|
||||||
|
Set `player`'s inventory to the interface defined by `builder`. This context will persist indefinitely until it is manually removed or the player leaves the game. Otherwise, this behaves in the same way as `imfs.show`.
|
||||||
|
|
||||||
|
### `imfs.clear_inventory(player)`
|
||||||
|
|
||||||
|
Manually invalidate the imfs context associated with `player`'s inventory formspec. Doing this will _not_ clear the player's inventory formspec; it is intended for interoperability with mods that use raw formspecs for the player's inventory (e.g. if your main inventory uses imfs but can switch to a view from a mod that doesn't), so that imfs will not erroneously keep handling events for such formspecs.
|
||||||
|
|
||||||
|
### `imfs.add_to_context(element)`
|
||||||
|
|
||||||
|
Add this element to the current container. This can be used in the creation of custom elements if the element needs to render itself in its container (rather than merely being an alias for a longer series of elements).
|
||||||
|
|
||||||
|
### `imfs.container_start()`
|
||||||
|
|
||||||
|
Creates and returns a new container base table, adds it to the current container, then makes it the new context to which subsequent elements are added. This can be used to create custom containers.
|
||||||
|
|
||||||
|
### `imfs.container_end()`
|
||||||
|
|
||||||
|
End the current container and revert to its parent. This can be used to create custom containers.
|
||||||
|
|
||||||
|
|
||||||
|
## Elements
|
||||||
|
|
||||||
|
Note that all elements have a `:render()` method that outputs their formspec representation, and accepts overrides for x, y, width, and height as extra arguments. This is used for creating custom layouting containers which interpret position and size in a non-absolute way.
|
||||||
|
|
||||||
|
Many sized elements have a `:tooltip(text[, background_color[, text_color]])` method that creates a tooltip for the element. Note that for elements without a `name` attribute in the formspec format, this will be an area tooltip and will not be occluded by any overlaying elements. Since imfs doesn't track which elements occlude which, it is up to you to not add area tooltips to occluded elements.
|
||||||
|
|
||||||
|
### `imfs.label(x, y, text)`
|
||||||
|
|
||||||
|
Creates a label element at the given position with the given text.
|
||||||
|
|
||||||
|
### `imfs.arealabel(x, y, width, height, text)`
|
||||||
|
|
||||||
|
Similar to `imfs.label`, but the created label will line-wrap. By default, overflowing text will be clipped to the label's bounding box.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:scrollable()`: Causes overflowing text to make the label scrollable rather than being clipped.
|
||||||
|
|
||||||
|
### `imfs.box(x, y, width, height, color)`
|
||||||
|
|
||||||
|
Creates a colored rectangle.
|
||||||
|
|
||||||
|
### `imfs.hypertext(x, y, width, height, hypertext)`
|
||||||
|
|
||||||
|
Creates a hypertext element. Refer to the markup language reference in the [Minetest API documentation](https://api.luanti.org/formspec/) for what `hypertext` may consist of.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:onaction(fn)`: Registers `fn` to be called when an `<action>` element is triggered in this element.
|
||||||
|
|
||||||
|
### `imfs.style(name[, state], properties, [internal])`
|
||||||
|
|
||||||
|
Creates a style element. Refer to the style reference in the [Minetest API documentation](https://api.luanti.org/formspec/) for which properties may be used and on which element types.
|
||||||
|
|
||||||
|
If `internal` is true, this element will not be automatically added to the element tree. This is used to allow inline styles on elements, since the style element must come first and the target element's name is not known beforehand.
|
||||||
|
|
||||||
|
### `imfs.tooltip(x, y, width, height, text[, background_color[, text_color]])`
|
||||||
|
|
||||||
|
Creates a tooltip that will be shown when the user hovers over the target area.
|
||||||
|
|
||||||
|
### `imfs.image(x, y, width, height, texture, [middle])`
|
||||||
|
|
||||||
|
Creates an image element. If `middle` is provided, the image will be rendered 9-sliced with `middle` as the center tile.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
`:animated(frames, duration = 50, start = 1)`: The image will be interpreted as a tile animation with the given properties.
|
||||||
|
|
||||||
|
### `imfs.item_image(x, y, width, height, item)`
|
||||||
|
|
||||||
|
Creates an item image element.
|
||||||
|
|
||||||
|
### `imfs.model(x, y, width, height, mesh, textures)`
|
||||||
|
|
||||||
|
Creates a model element.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:style([state, ]props)`: Applies the styling properties `props` to the model when in the `state` state. If omitted, `state` is `"default"`.
|
||||||
|
* `:rotation(rotation[, continuous])`: Set the rotation of the model in the view, and optionally whether it is continuous.
|
||||||
|
* `:mouse_control(state)`: Passing `false` will prevent the user from changing the model's rotation by clicking and dragging.
|
||||||
|
* `:animated(frames, speed)`: Sets an animation on the model defined by the given frame range and speed.
|
||||||
|
|
||||||
|
### `imfs.button(x, y, width, height, label)`
|
||||||
|
|
||||||
|
Creates a button element.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:style([state, ]props)`: Applies the styling properties `props` to the button when in the `state` state. If omitted, `state` is `"default"`.
|
||||||
|
* `:item_image(item)`: Convert this button to an item image button, representing the given item.
|
||||||
|
* `:image(image[, pressed_image])`: Convert this button to an image button, with the texture `image`. If specified, `pressed_image` will be the image shown by the button in pressed state. (Note that this can also be achieved using styles.) This will have no effect if `:item_image` is called on the same button.
|
||||||
|
* `:exit()`: This button will close the formspec when pressed. This will have no effect if `:item_image` or `:image` with a `pressed_image` is called on the same button.
|
||||||
|
* `:onclick(fn)`: Registers `fn` to be called when this button is pressed.
|
||||||
|
|
||||||
|
### `imfs.list(x, y, width, height, location = "current_player", list = "main"[, start])`
|
||||||
|
|
||||||
|
Creates an inventory element.
|
||||||
|
|
||||||
|
`imfs.inventory` is an alias for this element.
|
||||||
|
|
||||||
|
### `imfs.listring([location, list])`
|
||||||
|
|
||||||
|
Inserts a `listring[]` element. Refer to the [Minetest API documentation](https://api.luanti.org/formspec/) for an explanation of listrings.
|
||||||
|
|
||||||
|
### `imfs.field(x, y, width, height[, label], value)`
|
||||||
|
|
||||||
|
Creates a field element.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:style([state, ]props)`: Applies the styling properties `props` to the field when in the `state` state. If omitted, `state` is `"default"`.
|
||||||
|
* `:onchange(fn)`: Registers `fn` to be called with the new value when the field's value changes.
|
||||||
|
* `:onenter(fn)`: Registers `fn` to be called with the field's value when Enter is pressed in the field. (This will never trigger if the field is multiline.)
|
||||||
|
* `:multiline()`: Makes this field multiline. `:close_on_enter()` will have no effect if this is present.
|
||||||
|
* `:close_on_enter()`: This field will close the formspec when Enter is pressed. (Note that the default in imfs is _not_ to close the formspec when Enter is pressed, contrary to ordinary formspecs.)
|
||||||
|
* `:password()`: Makes this field a password field. `:multiline()` will have no effect if this is present.
|
||||||
|
|
||||||
|
### `imfs.textarea(x, y, width, height[, label], value)`
|
||||||
|
|
||||||
|
An alias for `imfs.field(...):multiline()`.
|
||||||
|
|
||||||
|
### `imfs.checkbox(x, y, label, checked)`
|
||||||
|
|
||||||
|
Creates a checkbox element.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:onchange(fn)`: Registers `fn` to be called with the new value when the checkbox's value changes.
|
||||||
|
|
||||||
|
### `imfs.scrollbar(x, y, width, height[, orientation = "vertial"], value)`
|
||||||
|
|
||||||
|
Creates a scrollbar element.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:style([state, ]props)`: Applies the styling properties `props` to the scrollbar when in the `state` state. If omitted, `state` is `"default"`. (This isn't very useful right now, but it might be if scrollbars become themable.)
|
||||||
|
* `:options(optios)`: Set the scrollbar options to `options`. Refer to the [Minetest API documentation](https://api.luanti.org/formspec/) for which options are permitted.
|
||||||
|
* `:onchange(fn)`: Registers `fn` to be called with the action and value value of each scroll event for this scrollbar.
|
||||||
|
|
||||||
|
### `imfs.scroll_container(x, y, width, height, orientation = "vertical"[, factor][, padding])`
|
||||||
|
|
||||||
|
Begins a scroll container. If you wish to manually set up the scroll container's scrollbar, you must pass `""` for `padding`. Note that if `padding` is not provided, `factor` will serve as an alias to it.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:scrollbar(fn, ...)`: Use the scrollbar returned by `fn()` instead of a generated one. Alternatively, if `fn` is not a function, this will just generate a scrollbar with `imfs.scrollbar(fn, ...)`.
|
||||||
|
* `:named(name)`: Set the name of this scroll container. If you want to persist the container's scroll position during rebuilds without putting forth much effort into tracking state and managing the scrollbar, passing a static string here will do so.
|
||||||
|
|
||||||
|
### `imfs.scroll_container_end()`
|
||||||
|
|
||||||
|
Ends the current scroll container.
|
||||||
|
|
||||||
|
### `imfs.group(x, y, width, height)`
|
||||||
|
|
||||||
|
Creates a group container. Group containers exist solely to position all their children relative to themselves, rather than to the top-level formspec. Note that percentage units will also be relative to the group's size rather than the window's.
|
||||||
|
|
||||||
|
### `imfs.group_end()`
|
||||||
|
|
||||||
|
Ends the current group.
|
||||||
|
|
||||||
|
### `imfs.row(x, y, width, height)`
|
||||||
|
|
||||||
|
Creates a flex row container. Flex containers will automatically lay out their content according to the provided alignment and gap, ignoring user-provided position. Immediate children of flex containers may specify their width (or height) property as a grow ratio (e.g. "1x"), which will cause them to automatially resize along the container's axis to fill any empty space. If an element has a higher grow ratio than another, it will take up a greater proportion of the available space.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
* `:align(alignment)`: Set the alignment of elements in this row. May be "left" (default), "right", or "center".
|
||||||
|
* `:gap(gap)`: Set the spacing between elements in this row.
|
||||||
|
* `:direction(direction)`: Set the direction in which this row will lay out its children. May be "row" (X axis, default) or "column" (Y axis).
|
||||||
|
|
||||||
|
### `imfs.row_end()`
|
||||||
|
|
||||||
|
Ends the current row.
|
||||||
|
|
||||||
|
### `imfs.column(x, y, width, height)`
|
||||||
|
|
||||||
|
An alias for `imfs.row(...):direction("column")`.
|
||||||
|
|
||||||
|
### `imfs.column_end()`
|
||||||
|
|
||||||
|
An alias for `imfs.row_end()`.
|
||||||
1
mod.conf
Normal file
1
mod.conf
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
name = imfs
|
||||||
Loading…
Add table
Add a link
Reference in a new issue