feat: scrolling

This commit is contained in:
2026-08-06 09:05:02 -04:00
parent 26f3fdb6e6
commit e5ed980294
9 changed files with 527 additions and 37 deletions
+36 -3
View File
@@ -27,8 +27,10 @@ local ui = {}
---@field on_enter? UiHandler
---@field on_exit? UiHandler
---@field on_click? UiHandler
---@field paint? fun(id: NodeId, x: integer, y: integer, w: integer, h: integer)
---@field paint? fun(id: NodeId, x: integer, y: integer, w: integer, h: integer, clipX: integer, clipY: integer, clipW: integer, clipH: integer)
---@field press_style? boolean False for a widget that paints its own press feedback.
---@field scrollX? boolean Children measure unbounded across, and the box pans horizontally.
---@field scrollY? boolean Children measure unbounded down, and the box pans vertically.
---@class UiConfirmSpec
---@field title string
@@ -135,10 +137,13 @@ local function clearState()
pressStyles = {}
end
tree.setPainter(function(id, x, y, w, h)
-- The clip is the slice of the node being repainted now, which for a composited repaint is
-- one band. A painter that ignores it still draws correctly; one that culls to it stops
-- redrawing its whole contents once per band it spans.
tree.setPainter(function(id, x, y, w, h, clipX, clipY, clipW, clipH)
local painter = painters[id]
if painter then
painter(id, x, y, w, h)
painter(id, x, y, w, h, clipX, clipY, clipW, clipH)
end
end)
@@ -308,6 +313,29 @@ function ui.invalidate(id)
tree.invalidate(id)
end
---Pans a scrollable node, clamped to its content. The node is marked dirty, so the next
---ui.draw() repaints it -- there is nothing for the app to draw and no handle to refresh,
---which is the whole point of scrolling in the tree rather than in a painter.
---@param id NodeId
---@param x integer
---@param y integer
function ui.setScroll(id, x, y)
tree.setScroll(id, x, y)
end
---@param id NodeId
---@return integer x, integer y
function ui.getScroll(id)
return tree.getScroll(id)
end
---How far each axis can pan. Zero on an axis whose content already fits.
---@param id NodeId
---@return integer maxX, integer maxY
function ui.getScrollRange(id)
return tree.getScrollRange(id)
end
---@param spec UiConfirmSpec
---@return NodeId
function ui.confirm(spec)
@@ -393,10 +421,15 @@ function ui.rebuild()
collectgarbage()
end
-- One GC step a frame, because the collector's default pace is the painter's problem: a
-- band buffer needs a contiguous block, and letting the heap double before a cycle lets
-- garbage take the block the band was going to get. Stepping keeps the sawtooth shallow
-- enough that beginBuffer() keeps succeeding instead of falling back to the panel.
function ui.draw()
if root then
tree.draw(root)
end
collectgarbage "step"
end
local function inside(id, x, y)