mirror of
https://gitlab.com/luk3yx/minetest-flow.git
synced 2025-10-22 13:23:08 +02:00
Add experimental low-level popover API
This commit is contained in:
parent
d7c6b66969
commit
1960dbd502
82
doc/popovers.md
Normal file
82
doc/popovers.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Popovers (highly experimental)
|
||||
|
||||
**This API will likely have breaking changes in the future.**
|
||||
|
||||
Flow supports a highly experimental low-level API for defining popovers.
|
||||
Popovers appear over top of all other elements and are anchored to their parent.
|
||||
|
||||
This API is intended to be used for creating themed widgets like dropdowns, or
|
||||
for a higher level popover API that adds styling and is easier to use.
|
||||
|
||||
You add a `popover` attribute to any container element to define a popover.
|
||||
Flow does not manage the lifecycle (opening/closing) of popovers, you must
|
||||
ensure that you do this yourself like in the example.
|
||||
|
||||
See the below example for details on how to use the API:
|
||||
|
||||
```lua
|
||||
gui.Stack{
|
||||
gui.Button{
|
||||
label = "Open popover",
|
||||
|
||||
-- When the popover button is clicked, open the popover
|
||||
on_event = function(player, ctx)
|
||||
ctx.show_popover = true
|
||||
return true
|
||||
end,
|
||||
},
|
||||
|
||||
-- The actual element to overlay
|
||||
-- "popover" is set to the gui.VBox element if (and only if)
|
||||
-- ctx.show_popover is true, otherwise it's nil so no popover is shown.
|
||||
popover = ctx.show_popover and gui.VBox{
|
||||
-- Specifying padding and bgcolor is optional, but is probably a good
|
||||
-- idea since flow doesn't do any styling on its own.
|
||||
padding = 0.2,
|
||||
bgcolor = "#222e",
|
||||
|
||||
-- "anchor" specifies how the popover is positioned relative to the
|
||||
-- parent, and can be "bottom" (default), "top", "left", or "right".
|
||||
anchor = ctx.form.anchor,
|
||||
|
||||
-- align_h and align_v align the popover according to its parent
|
||||
-- element. You only need to specify align_h for
|
||||
-- anchor = "top"/"bottom" or align_v for anchor = "left"/"right", this
|
||||
-- example specifies both so that it can demonstrate switching between
|
||||
-- different anchor types.
|
||||
align_h = "fill",
|
||||
align_v = "center",
|
||||
|
||||
-- Popover contents
|
||||
gui.Label{label = "Hi there!"},
|
||||
gui.Dropdown{
|
||||
name = "anchor",
|
||||
items = {
|
||||
"bottom",
|
||||
"top",
|
||||
"left",
|
||||
"right",
|
||||
},
|
||||
},
|
||||
} or nil,
|
||||
|
||||
-- Clicking outside the popover will call this function, which should
|
||||
-- probably close the popover.
|
||||
on_close_popover = function(player, ctx)
|
||||
ctx.show_popover = false
|
||||
return true
|
||||
end,
|
||||
}
|
||||
```
|
||||
|
||||
There are some restrictions on popovers:
|
||||
|
||||
- They must not extend outside the form, otherwise they'll only be partially
|
||||
visible (unless everything is styled with `noclip = true`). Flow does not
|
||||
attempt to detect this.
|
||||
- You can only define `popover` on container elements, like gui.Stack,
|
||||
gui.HBox, and gui.VBox.
|
||||
- Only one popover is shown at a time.
|
||||
- Players can still use tab to interact with things behind the popover,
|
||||
despite being unable to use their mouse to do so.
|
||||
- You cannot show popovers inside of other popovers.
|
13
expand.lua
13
expand.lua
@ -17,8 +17,8 @@
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
--
|
||||
|
||||
local DEFAULT_SPACING, LABEL_HEIGHT, get_and_fill_in_sizes,
|
||||
invisible_elems = ...
|
||||
local DEFAULT_SPACING, LABEL_HEIGHT, apply_padding, get_and_fill_in_sizes,
|
||||
invisible_elems, modpath = ...
|
||||
local align_types = {}
|
||||
|
||||
function align_types.fill(node, x, w, extra_space)
|
||||
@ -132,7 +132,7 @@ function align_types.auto(node, x, w, extra_space, cross)
|
||||
end
|
||||
end
|
||||
|
||||
local expand_child_boxes
|
||||
local expand_child_boxes, handle_popovers
|
||||
local function expand(box)
|
||||
local x, w, align_h, y, h, align_v
|
||||
local box_type = box.type
|
||||
@ -164,6 +164,7 @@ local function expand(box)
|
||||
node.w = box.w
|
||||
end
|
||||
expand(node)
|
||||
handle_popovers(box, node)
|
||||
end
|
||||
return
|
||||
elseif box_type == "padding" then
|
||||
@ -256,7 +257,13 @@ function expand_child_boxes(box)
|
||||
else
|
||||
expand(node)
|
||||
end
|
||||
|
||||
handle_popovers(box, node)
|
||||
end
|
||||
end
|
||||
|
||||
handle_popovers = assert(loadfile(modpath .. "/popover.lua"))(
|
||||
align_types, apply_padding, get_and_fill_in_sizes, expand
|
||||
)
|
||||
|
||||
return expand
|
||||
|
30
init.lua
30
init.lua
@ -26,7 +26,8 @@ local apply_padding, get_and_fill_in_sizes, set_current_lang,
|
||||
dofile(modpath .. "/layout.lua")
|
||||
|
||||
local expand = assert(loadfile(modpath .. "/expand.lua"))(
|
||||
DEFAULT_SPACING, LABEL_HEIGHT, get_and_fill_in_sizes, invisible_elems
|
||||
DEFAULT_SPACING, LABEL_HEIGHT, apply_padding, get_and_fill_in_sizes,
|
||||
invisible_elems, modpath
|
||||
)
|
||||
|
||||
local parse_callbacks = dofile(modpath .. "/input.lua")
|
||||
@ -107,6 +108,25 @@ local function render_ast(node, embedded)
|
||||
end
|
||||
|
||||
res[#res + 1] = node
|
||||
if node.popover and node.type == "container" then
|
||||
node.popover.x = node.popover.x + node.x
|
||||
node.popover.y = node.popover.y + node.y
|
||||
|
||||
res[#res + 1] = {
|
||||
type = "image_button",
|
||||
x = -99,
|
||||
y = -99,
|
||||
w = 999,
|
||||
h = 999,
|
||||
drawborder = false,
|
||||
noclip = true,
|
||||
on_event = node.on_close_popover,
|
||||
}
|
||||
res[#res + 1] = node.popover
|
||||
|
||||
assert(not node.popover.popover,
|
||||
"Nested popovers are not supported")
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
@ -266,9 +286,6 @@ function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
||||
current_player = player
|
||||
current_ctx = ctx
|
||||
local box = self._build(player, ctx)
|
||||
current_player = nil
|
||||
current_ctx = nil
|
||||
gui.formspec_version = 0
|
||||
|
||||
-- Restore the original ctx.form
|
||||
assert(ctx.form == wrapped_form,
|
||||
@ -280,6 +297,11 @@ function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
||||
if not id1 or id1 > 1e6 then id1 = 0 end
|
||||
|
||||
local tree = render_ast(box, embedded)
|
||||
|
||||
current_player = nil
|
||||
current_ctx = nil
|
||||
gui.formspec_version = 0
|
||||
|
||||
local callbacks, btn_callbacks, saved_fields, id2, on_key_enters =
|
||||
parse_callbacks(tree, orig_form, id1, embedded, formspec_version)
|
||||
|
||||
|
@ -18,6 +18,7 @@ nav:
|
||||
- inventory.md
|
||||
- manual-positioning.md
|
||||
- experimental.md
|
||||
- popovers.md
|
||||
- Development tools:
|
||||
- Flow Inspector: https://content.luanti.org/packages/luk3yx/flow_inspector/
|
||||
- Flow Playground / Tutorial: https://luk3yx.gitlab.io/minetest-flow-playground/
|
||||
|
58
popover.lua
Normal file
58
popover.lua
Normal file
@ -0,0 +1,58 @@
|
||||
local align_types, apply_padding, get_and_fill_in_sizes, expand = ...
|
||||
|
||||
local function handle_popovers(box, node)
|
||||
local popover = node.popover
|
||||
if not popover then
|
||||
return
|
||||
end
|
||||
|
||||
-- Copy popovers to their parent
|
||||
assert(node.type == "container" or node.type == "scroll_container",
|
||||
"Popovers are currently only supported on container elements")
|
||||
|
||||
local offset_x, offset_y = node.x, node.y
|
||||
if not popover._flow_popover_root then
|
||||
local p_w, p_h = apply_padding(popover, 0, 0)
|
||||
local n_w, n_h = get_and_fill_in_sizes(node)
|
||||
|
||||
if popover.anchor == "top" then
|
||||
offset_y = offset_y - p_h
|
||||
elseif popover.anchor == "left" then
|
||||
offset_x = offset_x - p_w
|
||||
elseif popover.anchor == "right" then
|
||||
offset_x = offset_x + n_w
|
||||
else
|
||||
offset_y = offset_y + n_h
|
||||
end
|
||||
|
||||
if popover.anchor == "left" or popover.anchor == "right" then
|
||||
align_types[popover.align_v or "auto"](popover, "y", "h", n_h - p_h)
|
||||
else
|
||||
align_types[popover.align_h or "auto"](popover, "x", "w", n_w - p_w)
|
||||
end
|
||||
|
||||
popover._flow_popover_root = true
|
||||
end
|
||||
|
||||
if node.type == "scroll_container" then
|
||||
local ctx = flow.get_context()
|
||||
local offset = (ctx.form[node.scrollbar_name] or 0) * (node.scroll_factor or 0.1)
|
||||
if node.orientation == "horizontal" then
|
||||
offset_x = offset_x - offset
|
||||
else
|
||||
offset_y = offset_y - offset
|
||||
end
|
||||
end
|
||||
|
||||
popover.x = popover.x + offset_x
|
||||
popover.y = popover.y + offset_y
|
||||
|
||||
box.popover = popover
|
||||
box.on_close_popover = node.on_close_popover
|
||||
|
||||
expand(popover)
|
||||
popover = nil -- Reduce the impact of API misuse
|
||||
node.on_close_popover = nil
|
||||
end
|
||||
|
||||
return handle_popovers
|
Loading…
Reference in New Issue
Block a user