114 lines
3.6 KiB
Lua
114 lines
3.6 KiB
Lua
local ui = require "ui"
|
|
|
|
-- Rows are ordinary nodes inside a scrolling box, so this app draws nothing itself: the
|
|
-- tree clips the subtree to the box, the painter culls to the band, and a drag is one
|
|
-- integer per frame. Both axes pan -- the rows are wider than the pane -- so a diagonal
|
|
-- drag moves the content diagonally.
|
|
local ROWS = 60
|
|
local ROW_H = 64
|
|
local ROW_W = 420 -- wider than the pane, so there is something to pan across
|
|
local GAP = 8
|
|
|
|
-- Fraction of a flick's speed still left after a second. Applied as DECAY^(dt/1000) rather
|
|
-- than per frame, so the glide takes the same time whatever the frame rate.
|
|
local DECAY = 0.02
|
|
-- Below this the glide is no longer visible and would only keep the pane repainting.
|
|
local STOP_PX_PER_SEC = 8
|
|
|
|
---@class ScrollApp : SlateApp, TouchHandlers
|
|
local M = {}
|
|
local list
|
|
local scrollX, scrollY = 0, 0
|
|
-- Drag deltas accumulate here and are applied in draw(), because that is the only place
|
|
-- with a real elapsed time to turn them into a speed. Touch handlers just record.
|
|
local dragX, dragY = 0, 0
|
|
local velocityX, velocityY = 0, 0
|
|
local lastX, lastY
|
|
local dragging = false
|
|
|
|
function M.init()
|
|
log.info "scroll demo ready"
|
|
end
|
|
|
|
function M.onTouchDown(x, y)
|
|
dragging = true
|
|
lastX, lastY = x, y
|
|
dragX, dragY = 0, 0
|
|
velocityX, velocityY = 0, 0 -- a finger down catches the glide, like every touch UI
|
|
end
|
|
|
|
function M.onTouchMove(x, y)
|
|
if not dragging then
|
|
return
|
|
end
|
|
-- Content follows the finger, so dragging down moves the list up.
|
|
dragX = dragX + (lastX - x)
|
|
dragY = dragY + (lastY - y)
|
|
lastX, lastY = x, y
|
|
end
|
|
|
|
function M.onTouchUp()
|
|
dragging = false
|
|
end
|
|
|
|
---@param deltaMs integer
|
|
function M.draw(deltaMs)
|
|
if deltaMs <= 0 then
|
|
return
|
|
end
|
|
local seconds = deltaMs / 1000
|
|
|
|
if dragging then
|
|
scrollX, scrollY = scrollX + dragX, scrollY + dragY
|
|
-- Speed measured over the frame the movement actually arrived in. A finger that stopped
|
|
-- moving reports zero, which is what makes a hold-then-lift not flick.
|
|
velocityX, velocityY = dragX / seconds, dragY / seconds
|
|
dragX, dragY = 0, 0
|
|
elseif velocityX ~= 0 or velocityY ~= 0 then
|
|
scrollX, scrollY = scrollX + velocityX * seconds, scrollY + velocityY * seconds
|
|
local keep = DECAY ^ seconds
|
|
velocityX, velocityY = velocityX * keep, velocityY * keep
|
|
if math.abs(velocityX) < STOP_PX_PER_SEC then
|
|
velocityX = 0
|
|
end
|
|
if math.abs(velocityY) < STOP_PX_PER_SEC then
|
|
velocityY = 0
|
|
end
|
|
else
|
|
return -- nothing moving, so nothing to repaint
|
|
end
|
|
|
|
ui.setScroll(list, math.floor(scrollX), math.floor(scrollY))
|
|
-- Read back what the clamp allowed: without this the offset keeps running past the end
|
|
-- while the content sits still, and the list ignores the first part of the drag back.
|
|
local clampedX, clampedY = ui.getScroll(list)
|
|
if clampedX ~= math.floor(scrollX) then
|
|
scrollX, velocityX = clampedX, 0
|
|
end
|
|
if clampedY ~= math.floor(scrollY) then
|
|
scrollY, velocityY = clampedY, 0
|
|
end
|
|
end
|
|
|
|
function M.node()
|
|
-- Sized to the frame, not "fill": fill resolves against the whole panel, and the pane the
|
|
-- app was given is shorter by the status bar. The difference is the scroll range.
|
|
local frameW, frameH = ui.frame()
|
|
local rows = { gap = GAP, pad = GAP, scrollX = true, scrollY = true, w = frameW, h = frameH }
|
|
for i = 1, ROWS do
|
|
rows[#rows + 1] = ui.box {
|
|
w = ROW_W,
|
|
h = ROW_H - GAP,
|
|
justify = "center",
|
|
align = "center",
|
|
background = (i % 2 == 1) and ui.theme.face or ui.theme.background,
|
|
border = ui.theme.muted,
|
|
ui.label("Row " .. i, { font = screen.FONT_LARGE }),
|
|
}
|
|
end
|
|
list = ui.box(rows)
|
|
return list
|
|
end
|
|
|
|
return M
|