///|
/// Each opening owns its measurement state and effect. Closing disposes that
/// scope, so an old asynchronous result cannot position a reopened surface.
fn floating_bounds(
context : UiContext,
id : String,
open : @minimoon.Val[Bool],
placement : @headless.Placement,
side_offset? : Double = 8.0,
align_offset? : Double = 0.0,
surface_id? : String,
) -> @minimoon.Val[@headless.Bounds?] {
@minimoon.Val::map2(open, context.visible, (open, visible) => open && visible).switch_by(
(active, _) => {
if !active {
return @minimoon.Val::constant(None)
}
let (bounds, _) = @minimoon.create_state_with_init(
init=emit => {
let initial : @headless.Bounds? = None
(
initial,
@minimoon.measure_nodes(
[
id + "-trigger",
surface_id.unwrap_or(id + "-surface"),
"mmui-viewport",
],
emit,
),
)
},
update=(
previous,
result : Result[Array[@minimoon.NodeRect?], @minimoon.HostError],
_,
) => {
@minimoon.no_cmd(
match result {
Ok([Some(anchor), Some(surface), Some(viewport)]) =>
Some(
@headless.place(
rect_bounds(anchor),
rect_bounds(surface),
rect_bounds(viewport),
placement,
gap=side_offset,
align_offset~,
),
)
_ => previous
},
)
},
)
bounds
},
by=active => if active { "open" } else { "closed" },
)
}