feat(ui): dialogs as ordinary nodes, with capture, border and fill
A dialog is a component the app includes when its state says so, placed absolutely so it covers the flow instead of joining it, and dismissed by rebuilding without it. No layer stack, no module state, no lifecycle: "on top" already means "later in the child list", which the draw walk gives for free, and nothing needs to survive a rebuild because the tree is derived from state rather than mutated beside it. The alternative was a ui.push/ui.pop stack of roots. It would have been the only imperative thing in an otherwise declarative program, and needed rules to reconcile two ways for something to reach the screen -- the rule that a rebuild must only replace the base being the one that would eventually be forgotten. Three primitives were missing and are now here. capture makes a component swallow the taps its children missed, without which the hit walk falls back to earlier siblings and a dialog can be tapped through. border draws a box as a rounded rect. fill names a size a fraction cannot express, since any number >= 1 is read as pixels, so w = 1.0 asked for one pixel. The root now measures as "fill" rather than auto. It is placed at the full panel rect already, so its own children could not resolve a fraction of the one component whose size is never in doubt. Settings asks before forgetting a network, which is the first caller.
This commit is contained in:
@@ -87,6 +87,38 @@ app:down(100, 20)
|
||||
app:up(100, 20)
|
||||
assert(fired == 1, "release inside must fire once")
|
||||
|
||||
-- A dialog is an ordinary node the app includes, so it covers the flow and swallows the
|
||||
-- taps that would otherwise reach what is underneath it.
|
||||
local underneath = 0
|
||||
local answered
|
||||
local buried = ui.button{label = "do not press", on_press = function() underneath = underneath + 1 end}
|
||||
local dialog = ui.confirm{
|
||||
title = "forget network?",
|
||||
ok = "forget",
|
||||
on_ok = function() answered = "ok" end,
|
||||
on_cancel = function() answered = "cancel" end,
|
||||
}
|
||||
local dialogScreen = ui.screen(ui.box{ui.box{pad = 12, buried}, dialog})
|
||||
|
||||
-- The card is centered on both axes, and the layer fills the panel.
|
||||
assert(rect(dialog) == "0,0 320x480", rect(dialog))
|
||||
local card = dialog.children[1]
|
||||
assert(card.rect.w == 272, "card width " .. card.rect.w) -- 0.85 of 320
|
||||
assert(card.rect.x == 24, "card x " .. card.rect.x) -- centered horizontally
|
||||
assert(card.rect.y == math.floor((480 - card.rect.h) / 2), "card y " .. card.rect.y)
|
||||
|
||||
-- A tap on the button that the dialog covers must not reach it.
|
||||
dialogScreen:down(60, 24)
|
||||
dialogScreen:up(60, 24)
|
||||
assert(underneath == 0, "the dialog was tapped through")
|
||||
|
||||
-- The dialog's own buttons still work.
|
||||
local ok = card.children[#card.children].children[2]
|
||||
local point = {x = ok.rect.x + 4, y = ok.rect.y + 4}
|
||||
dialogScreen:down(point.x, point.y)
|
||||
dialogScreen:up(point.x, point.y)
|
||||
assert(answered == "ok", "ok button did not fire, got " .. tostring(answered))
|
||||
|
||||
-- setText repaints only on a real change, so a caller may push a value every tick.
|
||||
local label = ui.text("12:00:00")
|
||||
local clockScreen = ui.screen(ui.box{label})
|
||||
|
||||
Reference in New Issue
Block a user