-- 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