feat(ui): add ui.toast, a message that slides up from the bottom edge
The card sits in a scroll container of its own height over an empty strip of the same, so the slide is a setScroll() delta on the laid-out tree rather than a rebuild a frame, and the container scissors the part not yet arrived.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
-- A message that slides up from the bottom edge, holds, then slides back down.
|
||||
--
|
||||
-- The card sits inside a scroll container of its own height whose content is twice that:
|
||||
-- an empty strip above the card. Sliding is therefore ui.setScroll() on the tree already
|
||||
-- laid out -- a delta on stored coordinates -- rather than a rebuild a frame, and the
|
||||
-- container scissors the half of the card that has not arrived yet.
|
||||
--
|
||||
-- Whatever mounts the tree includes toast.node() and calls toast.draw(deltaMs); show()
|
||||
-- rebuilds so the node exists, and hide() rebuilds to drop it.
|
||||
|
||||
local ui = require "ui"
|
||||
|
||||
local M = {}
|
||||
|
||||
local HEIGHT = 44
|
||||
local MARGIN = 12
|
||||
local SLIDE_MS = 180
|
||||
|
||||
local text, box
|
||||
-- nil, or "in" / "hold" / "out" with the milliseconds spent in it.
|
||||
local phase, elapsed, holdMs
|
||||
|
||||
---@param message string
|
||||
---@param durationMs? integer How long the card holds before it slides away.
|
||||
function M.show(message, durationMs)
|
||||
text, holdMs = message, durationMs or 1500
|
||||
phase, elapsed = "in", 0
|
||||
ui.rebuild()
|
||||
end
|
||||
|
||||
function M.hide()
|
||||
if phase then
|
||||
phase, text, box = nil, nil, nil
|
||||
ui.rebuild()
|
||||
end
|
||||
end
|
||||
|
||||
---@return NodeId|nil
|
||||
function M.node()
|
||||
if not phase then
|
||||
return nil
|
||||
end
|
||||
box = ui.box {
|
||||
at = { x = MARGIN, y = screen.getHeight() - HEIGHT - MARGIN },
|
||||
w = screen.getWidth() - 2 * MARGIN,
|
||||
h = HEIGHT,
|
||||
scrollY = true,
|
||||
ui.box {
|
||||
w = "fill",
|
||||
ui.spacer { w = "fill", h = HEIGHT },
|
||||
ui.box {
|
||||
w = "fill",
|
||||
h = HEIGHT,
|
||||
align = "center",
|
||||
justify = "center",
|
||||
background = ui.theme.face,
|
||||
border = ui.theme.muted,
|
||||
ui.text(text),
|
||||
},
|
||||
},
|
||||
}
|
||||
return box
|
||||
end
|
||||
|
||||
local function progress()
|
||||
if phase == "in" then
|
||||
return elapsed / SLIDE_MS
|
||||
elseif phase == "out" then
|
||||
return 1 - elapsed / SLIDE_MS
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
---Advances the slide. Safe to call every frame whether or not a toast is up.
|
||||
---@param deltaMs integer
|
||||
function M.draw(deltaMs)
|
||||
if not phase then
|
||||
return
|
||||
end
|
||||
elapsed = elapsed + deltaMs
|
||||
if phase == "in" and elapsed >= SLIDE_MS then
|
||||
phase, elapsed = "hold", 0
|
||||
elseif phase == "hold" and elapsed >= holdMs then
|
||||
phase, elapsed = "out", 0
|
||||
elseif phase == "out" and elapsed >= SLIDE_MS then
|
||||
M.hide()
|
||||
return
|
||||
end
|
||||
if box then
|
||||
ui.setScroll(box, 0, math.floor(HEIGHT * progress()))
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user