xurfer/src/lovr/lib/iui/input/mouse.lua

160 lines
3.9 KiB
Lua
Raw Normal View History

2026-06-06 12:35:31 +02:00
local currentPath = (...):match('(.-)[^%./]+$')
local parentPath = currentPath:match('(.-)[^%./]+%.$')
--- @class IUILib
local iui = require(parentPath .. "iui")
--- @class (exact) IUIMouseVelocityEntry
--- @field dx number
--- @field dy number
--- @field timestamp number
--- @field dt number
--- @class (exact) IUIMouseState
--- @field x number
--- @field y number
--- @field dx number
--- @field dy number
--- @field scrollX number
--- @field scrollY number
--- @field down IUISet<number>
--- @field pressed IUISet<number>
--- @field released IUISet<number>
--- @class (exact) IUIMouseRootContext
--- @field velocityBuffer IUIMouseVelocityEntry[]
--- @field storage IUIMouseState
--- @field disabledStorage IUIMouseState
local ctx --- @type IUIMouseRootContext
local function limitMouseVelocityEvents()
local limit = iui.backend.system.getTimestamp() - 0.1
while #ctx.velocityBuffer > 0 and ctx.velocityBuffer[1].timestamp < limit do
table.remove(ctx.velocityBuffer, 1)
end
end
local function addMouseVelocityEvent(dx, dy)
local timestamp = iui.backend.system.getTimestamp()
--- @type IUIMouseVelocityEntry
local entry = { dx = dx, dy = dy, timestamp = timestamp, dt = iui.dt }
table.insert(ctx.velocityBuffer, entry)
limitMouseVelocityEvents()
end
--- @return IUIMouseState
local function makeMouseState()
--- @type IUIMouseState
return {
x = -100,
y = -100,
dx = 0,
dy = 0,
scrollX = 0,
scrollY = 0,
down = iui.set.new(),
pressed = iui.set.new(),
released = iui.set.new()
}
end
local mouse = {
--- @param event IUIMouseEvent
--- @param button number
--- @param x number
--- @param y number
--- @param dx number
--- @param dy number
__call = function(_, event, button, x, y, dx, dy)
if ctx == nil then return end
local storage = ctx.storage
if event == "move" then
storage.x = x
storage.y = y
storage.dx = storage.dx + dx
storage.dy = storage.dy + dy
addMouseVelocityEvent(dx, dy)
elseif event == "down" then
storage.x = x
storage.y = y
storage.down:put(button)
storage.pressed:put(button)
elseif event == "up" then
storage.x = x
storage.y = y
storage.down:remove(button)
storage.released:put(button)
elseif event == "scroll" then
storage.scrollX = storage.scrollX + dx
storage.scrollY = storage.scrollY + dy
end
end,
}
setmetatable(mouse, mouse)
--- @return IUIMouseRootContext
function mouse.newRootContext()
--- @type IUIMouseRootContext
return {
velocityBuffer = {},
storage = makeMouseState(),
disabledStorage = makeMouseState()
}
end
--- @param rootContext IUIMouseRootContext
function mouse.setRootContext(rootContext)
ctx = rootContext
mouse.__index = ctx.storage
end
function mouse.endFrame()
local storage = ctx.storage
storage.dx = 0
storage.dy = 0
storage.scrollX = 0
storage.scrollY = 0
storage.pressed:removeAll()
storage.released:removeAll()
end
function mouse.resetVelocity()
ctx.velocityBuffer = {}
end
--- @return number vx, number vy
function mouse.getVelocity()
local vx, vy = 0, 0
local weightSum = 0
local now = iui.backend.system.getTimestamp()
for _, entry in ipairs(ctx.velocityBuffer) do
local weight = math.exp((entry.timestamp - now) * 20)
weightSum = weightSum + weight
vx = vx + (entry.dx / entry.dt) * weight
vy = vy + (entry.dy / entry.dt) * weight
end
if weightSum ~= 0 then
vx, vy = vx / weightSum, vy / weightSum
end
return -vx, -vy
end
--- @param active boolean
function mouse.setActive(active)
mouse.__index = active and ctx.storage or ctx.disabledStorage
end
return mouse --[[@as IUIMouse]]