From 4b75f21e52147c62538a28b2fae59b5b4a0f2394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Sun, 18 Jul 2021 19:30:43 +0200 Subject: [PATCH] Remove backend system and ctruLua support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since I only use the LÖVE backend anyway, this simplifies the code. Tidied some code. --- asset/asset.lua | 7 - backend/ctrulua.lua | 31 -- backend/love.lua | 30 -- ecs/ecs.can | 4 - init.lua | 52 ++- input/axis.lua | 162 +++++++++ input/backend/ctrulua.lua | 277 ---------------- input/button.lua | 156 +++++++++ input/init.lua | 15 +- input/input.lua | 615 ++--------------------------------- input/{backend => }/love.lua | 2 +- input/pointer.lua | 246 ++++++++++++++ scene/scene.lua | 12 - signal/backend/love.lua | 43 --- signal/init.lua | 15 +- signal/signal.can | 54 ++- timer/timer.lua | 9 - 17 files changed, 663 insertions(+), 1067 deletions(-) delete mode 100644 backend/ctrulua.lua delete mode 100644 backend/love.lua create mode 100644 input/axis.lua delete mode 100644 input/backend/ctrulua.lua create mode 100644 input/button.lua rename input/{backend => }/love.lua (99%) create mode 100644 input/pointer.lua delete mode 100644 signal/backend/love.lua diff --git a/asset/asset.lua b/asset/asset.lua index 2bc6163..570618c 100644 --- a/asset/asset.lua +++ b/asset/asset.lua @@ -16,7 +16,6 @@ local asset_mt = { -- @tparam assetName string the asset's full name -- @tparam ... number/string other arguments for the asset loader -- @return the asset - -- @impl ubiquitousse __call = function(self, assetName, ...) local cache = self.cache local hash = table.concat({assetName, ...}, ".") @@ -37,7 +36,6 @@ local asset_mt = { end, --- Preload a list of assets. - -- @impl ubiquitousse load = function(self, list) for _, asset in ipairs(list) do self(asset) @@ -46,7 +44,6 @@ local asset_mt = { --- Allow loaded assets to be garbage collected. -- Only useful if the caching mode is set to "manual" duritng creation. - -- @impl ubiquitousse clear = function(self) self.cache = {} end @@ -61,7 +58,6 @@ local asset = { -- @tparam directory string the directory in which the assets will be loaded -- @tparam loaders table loaders table: {prefix = function, ...} -- @tparam mode string[opt="auto"] caching mode - -- @impl ubiquitousse new = function(dir, loaders, mode) local cache = {} if mode == nil or mode == "auto" then @@ -69,15 +65,12 @@ local asset = { end return setmetatable({ --- A prefix for asset names - -- @impl ubiquitousse prefix = dir..".", --- The asset cache. Each cached asset is indexed with a string key "type.assetName". - -- @impl ubiquitousse cache = cache, --- The loaders table. - -- @impl ubiquitousse loaders = loaders }, asset_mt) end diff --git a/backend/ctrulua.lua b/backend/ctrulua.lua deleted file mode 100644 index f24aaa5..0000000 --- a/backend/ctrulua.lua +++ /dev/null @@ -1,31 +0,0 @@ -local uqt = require((...):match("^(.-ubiquitousse)%.")) -local ctr = require("ctr") -local gfx = require("ctr.gfx") - -local madeForCtr = "v1.0" -local madeForUqt = "0.0.1" - --- Check versions -local txt = "" - -if ctr.version ~= madeForCtr then - txt = txt .. ("Ubiquitousse ctrµLua backend was made for ctrµLua %s but %s is used!\n") - :format(madeForCtr, uqt.version) -end - -if uqt.version ~= madeForUqt then - txt = txt .. ("Ubiquitousse ctrµLua backend was made for Ubiquitousse %s but %s is used!\n") - :format(madeForUqt, uqt.version) -end - --- Show warnings -if txt ~= "" then - txt = txt .. "Things may not work as expected.\n" - print(txt) - for _=0,300 do - gfx.start(gfx.TOP) - gfx.wrappedText(0, 0, txt, gfx.TOP_WIDTH) - gfx.stop() - gfx.render() - end -end diff --git a/backend/love.lua b/backend/love.lua deleted file mode 100644 index ef320cc..0000000 --- a/backend/love.lua +++ /dev/null @@ -1,30 +0,0 @@ -local uqt = require((...):match("^(.-ubiquitousse)%.")) - -local madeForLove = { 11, "x", "x" } -local madeForUqt = "0.0.1" - --- Check versions -local txt = "" - -local actualLove = { love.getVersion() } -for i, v in ipairs(madeForLove) do - if v ~= "x" then - if actualLove[i] ~= v then - txt = txt .. ("Ubiquitousse Löve backend was made for LÖVE %s.%s.%s but %s.%s.%s is used!\n") - :format(madeForLove[1], madeForLove[2], madeForLove[3], actualLove[1], actualLove[2], actualLove[3]) - break - end - end -end - -if uqt.version ~= madeForUqt then - txt = txt .. ("Ubiquitousse Löve backend was made for Ubiquitousse %s but %s is used!\n") - :format(madeForUqt, uqt.version) -end - --- Show warnings -if txt ~= "" then - txt = txt .. "Things may not work as expected.\n" - print(txt) - love.window.showMessageBox("Compatibility warning", txt, "warning") -end diff --git a/ecs/ecs.can b/ecs/ecs.can index 4bd2625..663d47b 100644 --- a/ecs/ecs.can +++ b/ecs/ecs.can @@ -422,7 +422,6 @@ end ecs = { --- Create and returns a world system based on a list of systems. -- The systems will be instancied for this world. - -- @impl ubiquitousse world = (...) let world = setmetatable({ filter = ecs.all(), @@ -436,7 +435,6 @@ ecs = { end, --- Returns a filter that returns true if, for every argument, a field with the same name exists in the entity. - -- @impl ubiquitousse all = (...) if ... then let l = {...} @@ -454,7 +452,6 @@ ecs = { end, --- Returns a filter that returns true if one of the arguments if the name of a field in the entity. - -- @impl ubiquitousse any = (...) if ... then let l = {...} @@ -472,7 +469,6 @@ ecs = { end, --- If uqt.scene is available, returns a new scene that will consist of a ECS world with the specified systems and entities. - -- @impl ubiquitousse scene = (name, systems={}, entities={}) assert(scene, "ubiquitousse.scene unavailable") let s = scene.new(name) diff --git a/init.lua b/init.lua index 6f5a7d1..87c8fde 100644 --- a/init.lua +++ b/init.lua @@ -9,13 +9,8 @@ -- However, some modules may provide more feature when other modules are available. -- These dependencies are written at the top of every main module file. -- --- Ubiquitousse's goal is to run everywhere with the least porting effort possible, so Ubiquitousse tries to only use features that --- are almost sure to be available everywhere. --- --- Some Ubiquitousse modules require functions that are not in the Lua standard library, and must therefore be implemented in a backend, --- such as ubiquitousse.love. When required, modules will try to autodetect the engine it is running on, and load a correct backend. --- --- Most Ubiquitousse module backends require a few things to be fully implemented: +-- Ubiquitousse's goal is to run everywhere with the least porting effort possible, so while the current version mainly focus LÖVE, it +-- should be easily modifiable to work with something else. Ubiquitousse should only require: -- * The backend needs to have access to some kind of main loop, or at least a function called very often (may or may not be the -- same as the redraw screen callback). -- * Some way of measuring time (preferably with millisecond-precision). @@ -23,9 +18,12 @@ -- * Lua 5.1, 5.2, 5.3 or LuaJit. -- * Other requirement for specific modules should be described in the module's documentation. -- +-- Functions that depends on LÖVE or anything that's not in the Lua standard libraries (and therefore the one you may want to port to +-- another framework) are indicated by a "-- @impl love" annotation. +-- -- Units used in the API documentation: -- * All distances are expressed in pixels (px) --- * All durations are expressed in milliseconds (ms) +-- * All durations are expressed in seconds (ms) -- These units are only used to make writing documentation easier; you can use other units if you want, as long as you're consistent. -- -- Style: @@ -35,18 +33,6 @@ -- * CamelCase for class names. -- * lowerCamelCase is expected for everything else. -- --- Implementation levels: --- * backend: nothing defined in Ubiquitousse, must be implemented in backend --- * mixed: partly implemented in Ubiquitousse but must be complemeted in backend. --- * ubiquitousse: fully-working version in Ubiquitousse, may or may not be redefined in backend --- The implementation level is indicated using the "@impl level" annotation. --- --- For backend writers: --- If a function defined here already contains some code, this means this code is mandatory and you must put/call --- it in your implementation (except if the backend provides a more efficient implementation). --- Also, a backend file shouldn't redefine the ubiquitousse table itself but only redefine the backend-dependant fields. --- Lua 5.3: The API doesn't make the difference between numbers and integers, so convert to integers when needed. --- -- For game writer: -- Ubiquitousse works with Lua 5.1 to 5.3, including LuaJit, but doesn't provide any version checking or compatibility layer -- between the different versions, so it's up to you to handle that in your game (or ignore the problem and sticks to your @@ -63,10 +49,23 @@ local ubiquitousse ubiquitousse = { --- Ubiquitousse version. - -- @impl ubiquitousse - version = "0.0.1" + version = "0.1.0" } +-- Check LÖVE version +local madeForLove = { 11, "x", "x" } + +local actualLove = { love.getVersion() } +for i, v in ipairs(madeForLove) do + if v ~= "x" and actualLove[i] ~= v then + local txt = ("Ubiquitousse was made for LÖVE %s.%s.%s but %s.%s.%s is used!\nThings may not work as expected.") + :format(madeForLove[1], madeForLove[2], madeForLove[3], actualLove[1], actualLove[2], actualLove[3]) + print(txt) + love.window.showMessageBox("Compatibility warning", txt, "warning") + break + end +end + -- We're going to require modules requiring Ubiquitousse, so to avoid stack overflows we already register the ubiquitousse package package.loaded[p] = ubiquitousse @@ -80,13 +79,4 @@ for _, m in ipairs{"signal", "asset", "ecs", "input", "scene", "timer", "util"} end end --- Backend engine autodetect and load -if love then - require(p..".backend.love") -elseif package.loaded["ctr"] then - require(p..".backend.ctrulua") -elseif package.loaded["libretro"] then - error("NYI") -end - return ubiquitousse diff --git a/input/axis.lua b/input/axis.lua new file mode 100644 index 0000000..da3c2b6 --- /dev/null +++ b/input/axis.lua @@ -0,0 +1,162 @@ +local input = require((...):gsub("axis$", "input")) +local button_mt = require((...):gsub("axis$", "button")) + +--- AxisInput methods +local axis_mt +axis_mt = { + -- Axis inputs -- + -- Axis input is a container for axes detector. An axis input will return the value of the axis detector the most far away from their center (0). + -- Axis input provide a threshold setting; every axis which has a distance to the center below the threshold (none by default) will be ignored. + -- @tparam AxisDetectors ... all the axis detectors or axis identifiers + -- @tretrun AxisInput the object + _new = function(...) + local r = setmetatable({ + hijackStack = {}, -- hijackers stack, last element is the object currently hijacking this input + hijacking = nil, -- object currently hijacking this input + detectors = {}, -- detectors list + val = 0, -- current value between -1 and 1 + dval = 0, -- change between -2 and 2 + raw = 0, -- raw value between -max and +max + max = 1, -- maximum for raw values + threshold = 0, -- ie., the deadzone + triggeringThreshold = 0.5 -- digital button threshold + }, axis_mt) + table.insert(r.hijackStack, r) + r.hijacking = r + r:bind(...) + r.positive = input.button(function() return r:value() > r.triggeringThreshold end) + r.negative = input.button(function() return r:value() < -r.triggeringThreshold end) + return r + end, + + --- Returns a new AxisInput with the same properties. + -- @treturn AxisInput the cloned object + clone = function(self) + return input.axis(unpack(self.detectors)) + :threshold(self.threshold) + :triggeringThreshold(self.triggeringThreshold) + end, + + --- Bind new AxisDetector(s) to this input. + -- @tparam AxisDetectors ... axis detectors or axis identifiers to add + -- @treturn AxisInput this AxisInput object + bind = function(self, ...) + for _,d in ipairs({...}) do + table.insert(self.detectors, input.axisDetector(d)) + end + return self + end, + --- Unbind AxisDetector(s). + -- @tparam AxisDetectors ... axis detectors or axis identifiers to remove + -- @treturn AxisInput this AxisInput object + unbind = button_mt.unbind, + --- Unbind all AxisDetector(s). + -- @treturn AxisInput this AxisInput object + clear = button_mt.clear, + + --- Hijacks the input. + -- This function returns a new input object which mirrors the current object, except it will hijack every new input. + -- This means any value change will only be visible to the new object; the axis will always appear to be at 0 for the initial object. + -- An input can be hijacked several times; the one which hijacked it last will be the active one. + -- @treturn AxisInput the new input object which is hijacking the input + hijack = function(self) + local hijacked + hijacked = setmetatable({ + positive = input.button(function() return hijacked:value() > self.triggeringThreshold end), + negative = input.button(function() return hijacked:value() < -self.triggeringThreshold end) + }, { __index = self, __newindex = self }) + table.insert(self.hijackStack, hijacked) + self.hijacking = hijacked + return hijacked + end, + --- Release the input that was hijacked by this object. + -- Input will be given back to the previous object. + -- @treturn AxisInput this AxisInput object + free = button_mt.free, + + --- Sets the default detection threshold (deadzone). + -- 0 by default. + -- @tparam number new the new detection threshold + -- @treturn AxisInput this AxisInput object + threshold = function(self, new) + self.threshold = tonumber(new) + return self + end, + + --- Returns the value of the input (between -1 and 1). + -- @tparam[opt=default threshold] number threshold value to use + -- @treturn number the input value + value = function(self, curThreshold) + if self.hijacking == self then + self:update() + local val = self.val + return math.abs(val) > math.abs(curThreshold or self.threshold) and val or 0 + else + return 0 + end + end, + --- Returns the change in value of the input since last update (between -2 and 2). + -- @treturn number the value delta + delta = function(self) + if self.hijacking == self then + self:update() + return self.dval + else + return 0 + end + end, + --- Returns the raw value of the input (between -max and +max). + -- @tparam[opt=default threshold*max] number raw threshold value to use + -- @treturn number the input raw value + raw = function(self, rawThreshold) + if self.hijacking == self then + self:update() + local raw = self.raw + return math.abs(raw) > math.abs(rawThreshold or self.threshold*self.max) and raw or 0 + else + return 0 + end + end, + --- Return the raw max of the input. + -- @treturn number the input raw max + max = function(self) + self:update() + return self.max + end, + + --- Sets the default triggering threshold, i.e. how the minimal axis value for which the associated buttons will be considered down. + -- 0.5 by default. + -- @tparam number new the new triggering threshold + -- @treturn AxisInput this AxisInput object + triggeringThreshold = function(self, new) + self.triggeringThreshold = tonumber(new) + return self + end, + + --- The associated button pressed when the axis reaches a positive value. + positive = nil, + --- The associated button pressed when the axis reaches a negative value. + negative = nil, + + --- Update axis state. + -- Automatically called, don't call unless you know what you're doing. + update = function(self) + if not input.updated[self] then + local val, raw, max = 0, 0, 1 + for _, d in ipairs(self.detectors) do + local v, r, m = d() -- v[-1,1], r[-m,+m] + if math.abs(v) > math.abs(val) then + val, raw, max = v, r or v, m or 1 + end + end + self.dval = val - self.val + self.val, self.raw, self.max = val, raw, max + input.updated[self] = true + end + end, + + --- LÖVE note: other callbacks that are defined in backend/love.lua and need to be called in the associated LÖVE callbacks. +} +axis_mt.__index = axis_mt + +return axis_mt diff --git a/input/backend/ctrulua.lua b/input/backend/ctrulua.lua deleted file mode 100644 index cccb8d9..0000000 --- a/input/backend/ctrulua.lua +++ /dev/null @@ -1,277 +0,0 @@ -local input = require((...):match("^(.-%.)backend").."input") - -local loaded, signal = pcall(require, (...):match("^(.-)input").."signal") -if not loaded then signal = nil end - -local gfx = require("ctr.gfx") -local hid = require("ctr.hid") - -local keys = {} -local touchX, touchY, dTouchX, dTouchY - -local oUpdate = input.update -input.update = function(dt) - hid.read() - - keys = hid.keys() - - local nTouchX, nTouchY = hid.touch() - dTouchX, dTouchY = nTouchX - touchX, nTouchY - touchY - touchX, touchY = nTouchX, nTouchY - - oUpdate(dt) -end - -input.buttonDetector = function(...) - local ret = {} - for _,id in ipairs({...}) do - -- Keys - if id:match("^key%.") then - local key = id:match("^key%.(.+)$") - table.insert(ret, function() - return keys.held[key] - end) - else - error("Unknown button identifier: "..id) - end - end - return table.unpack(ret) -end - -input.axisDetector = function(...) - local ret = {} - for _,id in ipairs({...}) do - -- Binary axis - if id:match(".+%,.+") then - local d1, d2 = input.buttonDetector(id:match("^(.+)%,(.+)$")) - table.insert(ret, function() - local b1, b2 = d1(), d2() - if b1 and b2 then return 0 - elseif b1 then return -1 - elseif b2 then return 1 - else return 0 end - end) - -- Touch movement - elseif id:match("^touch%.move%.") then - local axis, threshold = id:match("^touch%.move%.(.+)%%(.+)$") - if not axis then axis = id:match("^touch%.move%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local val, raw, max - if axis == "x" then - raw, max = dTouchX, gfx.BOTTOM_WIDTH - elseif axis == "y" then - raw, max = dTouchY, gfx.BOTTOM_HEIGHT - end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - -- Touch position - elseif id:match("^touch%.position%.") then - local axis, threshold = id:match("^touch%.position%.(.+)%%(.+)$") - if not axis then axis = id:match("^touch%.position%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local val, raw, max - if axis == "x" then - max = gfx.BOTTOM_WIDTH / 2 -- /2 because x=0,y=0 is the center of the screen (an axis value is in [-1,1]) - raw = touchX - max - elseif axis == "y" then - max = gfx.BOTTOM_HEIGHT / 2 - raw = touchY - max - end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - -- Circle pad axis - elseif id:match("^circle%.") then - local axis, threshold = id:match("^circle%.(.+)%%(.+)$") - if not axis then axis = id:match("^circle%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local x, y = hid.circle() - local val, raw, max = 0, 0, 156 - if axis == "x" then raw = x - elseif axis == "y" then raw = y end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - -- C-Stick axis - elseif id:match("^cstick%.") then - local axis, threshold = id:match("^cstick%.(.+)%%(.+)$") - if not axis then axis = id:match("^cstick%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local x, y = hid.cstick() - local val, raw, max = 0, 0, 146 - if axis == "x" then raw = x - elseif axis == "y" then raw = y end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - -- Accelerometer axis - elseif id:match("^accel%.") then - local axis, threshold = id:match("^accel%.(.+)%%(.+)$") - if not axis then axis = id:match("^accel%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local x, y, z = hid.accel() - local val, raw, max = 0, 0, 32768 -- no idea actually, but it's a s16 - if axis == "x" then raw = x - elseif axis == "y" then raw = y - elseif axis == "z" then raw = z end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - -- Gyroscope axis - elseif id:match("^gyro%.") then - local axis, threshold = id:match("^gyro%.(.+)%%(.+)$") - if not axis then axis = id:match("^gyro%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, function() - local roll, pitch, yaw = hid.gyro() - local val, raw, max = 0, 0, 32768 -- no idea actually, but it's a s16 - if axis == "roll" then raw = roll - elseif axis == "pitch" then raw = pitch - elseif axis == "yaw" then raw = yaw end - val = raw / max - return math.abs(val) > math.abs(threshold) and val or 0, raw, max - end) - else - error("Unknown axis identifier: "..id) - end - end - return table.unpack(ret) -end - -input.buttonsInUse = function(threshold) - local r = {} - for key, held in pairs(keys.held) do - if held then table.insert(r, "key."..key) end - end - return r -end - -input.axesInUse = function(threshold) - local r = {} - threshold = threshold or 0.5 - - if math.abs(touchX) / gfx.BOTTOM_WIDTH > threshold then table.insert(r, "touch.position.x%"..threshold) end - if math.abs(touchY) / gfx.BOTTOM_HEIGHT > threshold then table.insert(r, "touch.position.y%"..threshold) end - - if math.abs(dTouchX) / gfx.BOTTOM_WIDTH > threshold then table.insert(r, "touch.move.x%"..threshold) end - if math.abs(dTouchY) / gfx.BOTTOM_HEIGHT > threshold then table.insert(r, "touch.move.y%"..threshold) end - - local circleX, circleY = hid.circle() - if math.abs(circleX) / 156 > threshold then table.insert(r, "circle.x%"..threshold) end - if math.abs(circleY) / 156 > threshold then table.insert(r, "circle.y%"..threshold) end - - if ctr.apt.isNew3DS() then - local cstickX, cstickY = hid.cstick() - if math.abs(cstickY) / 146 > threshold then table.insert(r, "cstick.y%"..threshold) end - if math.abs(cstickX) / 146 > threshold then table.insert(r, "cstick.x%"..threshold) end - end - - local accelX, accelY, accelZ = hid.accel() - if math.abs(accelX) / 32768 > threshold then table.insert(r, "accel.x%"..threshold) end - if math.abs(accelY) / 32768 > threshold then table.insert(r, "accel.y%"..threshold) end - if math.abs(accelZ) / 32768 > threshold then table.insert(r, "accel.z%"..threshold) end - - -- no gyro, because it is always in use - - return r -end - -input.buttonName = function(...) - local ret = {} - for _,id in ipairs({...}) do - -- Key - if id:match("^key%.") then - local key = id:match("^key%.(.+)$") - table.insert(ret, key:sub(1,1):upper()..key:sub(2).." key") - else - table.insert(ret, id) - end - end - return table.unpack(ret) -end - -input.axisName = function(...) - local ret = {} - for _,id in ipairs({...}) do - -- Binary axis - if id:match(".+%,.+") then - local b1, b2 = input.buttonName(id:match("^(.+)%,(.+)$")) - table.insert(ret, b1.." / "..b2) - -- Touch movement - elseif id:match("^touch%.move%.") then - local axis, threshold = id:match("^touch%.move%.(.+)%%(.+)$") - if not axis then axis = id:match("^touch%.move%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, ("Touch %s movement (threshold %s%%)"):format(axis, math.abs(threshold*100))) - -- Touch position - elseif id:match("^touch%.position%.") then - local axis, threshold = id:match("^touch%.position%.(.+)%%(.+)$") - if not axis then axis = id:match("^touch%.position%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, ("Touch %s position (threshold %s%%)"):format(axis, math.abs(threshold*100))) - -- Circle pad axis - elseif id:match("^circle%.") then - local axis, threshold = id:match("^circle%.(.+)%%(.+)$") - if not axis then axis = id:match("^circle%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - if axis == "x" then - table.insert(ret, ("Circle pad horizontal axis (deadzone %s%%)"):format(math.abs(threshold*100))) - elseif axis == "y" then - table.insert(ret, ("Circle pad vertical axis (deadzone %s%%)"):format(math.abs(threshold*100))) - else - table.insert(ret, ("Circle pad %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) - end - -- C-Stick axis - elseif id:match("^cstick%.") then - local axis, threshold = id:match("^cstick%.(.+)%%(.+)$") - if not axis then axis = id:match("^cstick%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - if axis == "x" then - table.insert(ret, ("C-Stick horizontal axis (deadzone %s%%)"):format(math.abs(threshold*100))) - elseif axis == "y" then - table.insert(ret, ("C-Stick vertical axis (deadzone %s%%)"):format(math.abs(threshold*100))) - else - table.insert(ret, ("C-Stick %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) - end - -- Accelerometer axis - elseif id:match("^accel%.") then - local axis, threshold = id:match("^accel%.(.+)%%(.+)$") - if not axis then axis = id:match("^accel%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, ("Accelerometer %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) - -- Gyroscope axis - elseif id:match("^gyro%.") then - local axis, threshold = id:match("^gyro%.(.+)%%(.+)$") - if not axis then axis = id:match("^gyro%.(.+)$") end -- no threshold (=0) - threshold = tonumber(threshold) or 0 - table.insert(ret, ("Gyroscope %s axis (deadzone %s%%)"):format(axis, math.abs(threshold*100))) - else - table.insert(ret, id) - end - end - return table.unpack(ret) -end - --- Size -input.screenWidth, input.screenHeight = gfx.TOP_WIDTH, gfx.TOP_HEIGHT - --- Defaults -input.default.pointer:bind( - { "absolute", "key.left,key.right", "key.up,key.down" }, - { "absolute", "circle.x", "circle.y" } -) -input.default.confirm:bind("key.a") -input.default.cancel:bind("key.b") - ---- Register signals -if signal then - signal.event:replace("update", oUpdate, input.update) -end - -return input diff --git a/input/button.lua b/input/button.lua new file mode 100644 index 0000000..fc700d9 --- /dev/null +++ b/input/button.lua @@ -0,0 +1,156 @@ +local input = require((...):gsub("button$", "input")) + +--- ButtonInput methods +local button_mt +button_mt = { + -- Buttons inputs -- + -- Button input is a container for buttons detector. A button will be pressed when one of its detectors returns true. + -- Inputs also knows if the button was just pressed or released. + -- @tparam ButtonDetectors ... all the buttons detectors or buttons identifiers + -- @tretrun ButtonInput the object + _new = function(...) + local r = setmetatable({ + hijackStack = {}, -- hijackers stack, last element is the object currently hijacking this input + hijacking = nil, -- object currently hijacking this input + detectors = {}, -- detectors list + state = "none" -- current state (none, pressed, down, released) + }, button_mt) + table.insert(r.hijackStack, r) + r.hijacking = r + r:bind(...) + return r + end, + + --- Returns a new ButtonInput with the same properties. + -- @treturn ButtonInput the cloned object + clone = function(self) + return input.button(unpack(self.detectors)) + end, + + --- Bind new ButtonDetector(s) to this input. + -- @tparam ButtonDetectors ... buttons detectors or buttons identifiers to add + -- @treturn ButtonInput this ButtonInput object + bind = function(self, ...) + for _, d in ipairs({...}) do + table.insert(self.detectors, input.buttonDetector(d)) + end + return self + end, + --- Unbind ButtonDetector(s). + -- @tparam ButtonDetectors ... buttons detectors or buttons identifiers to remove + -- @treturn ButtonInput this ButtonInput object + unbind = function(self, ...) + for _, d in ipairs({...}) do + for i=#self.detectors, 1, -1 do + if self.detectors[i] == d then + table.remove(self.detectors, i) + break + end + end + end + return self + end, + --- Unbind all ButtonDetector(s). + -- @treturn ButtonInput this ButtonInput object + clear = function(self) + self.detectors = {} + return self + end, + + --- Hijacks the input. + -- This function returns a new input object which mirrors the current object, except it will hijack every new input. + -- This means any new button press/down/release will only be visible to the new object; the button will always appear unpressed for the initial object. + -- This is useful for contextual input, for example if you want to display a menu without pausing the game: the menu + -- can hijack relevant inputs while it is open, so they don't trigger any action in the rest of the game. + -- An input can be hijacked several times; the one which hijacked it last will be the active one. + -- @treturn ButtonInput the new input object which is hijacking the input + hijack = function(self) + local hijacked = setmetatable({}, { __index = self, __newindex = self }) + table.insert(self.hijackStack, hijacked) + self.hijacking = hijacked + return hijacked + end, + --- Release the input that was hijacked by this object. + -- Input will be given back to the previous object. + -- @treturn ButtonInput this ButtonInput object + free = function(self) + local hijackStack = self.hijackStack + for i, v in ipairs(hijackStack) do + if v == self then + table.remove(hijackStack, i) + self.hijacking = hijackStack[#hijackStack] + return self + end + end + error("This object is currently not hijacking this input") + end, + + --- Returns true if the input was just pressed. + -- @treturn boolean true if the input was pressed, false otherwise + pressed = function(self) + if self.hijacking == self then + self:update() + return self.state == "pressed" + else + return false + end + end, + --- Returns true if the input was just released. + -- @treturn boolean true if the input was released, false otherwise + released = function(self) + if self.hijacking == self then + self:update() + return self.state == "released" + else + return false + end + end, + --- Returns true if the input is down. + -- @treturn boolean true if the input is currently down, false otherwise + down = function(self) + if self.hijacking == self then + self:update() + local state = self.state + return state == "down" or state == "pressed" + else + return false + end + end, + --- Returns true if the input is up. + -- @treturn boolean true if the input is currently up, false otherwise + up = function(self) + return not self:down() + end, + + --- Update button state. + -- Automatically called, don't call unless you know what you're doing. + update = function(self) + if not input.updated[self] then + local down = false + for _, d in ipairs(self.detectors) do + if d() then + down = true + break + end + end + local state = self.state + if down then + if state == "none" or state == "released" then + self.state = "pressed" + else + self.state = "down" + end + else + if state == "down" or state == "pressed" then + self.state = "released" + else + self.state = "none" + end + end + input.updated[self] = true + end + end +} +button_mt.__index = button_mt + +return button_mt diff --git a/input/init.lua b/input/init.lua index a4bf493..a4dfd57 100644 --- a/input/init.lua +++ b/input/init.lua @@ -1,14 +1 @@ -local input - -local p = ... -if love then - input = require(p..".backend.love") -elseif package.loaded["ctr"] then - input = require(p..".backend.ctrulua") -elseif package.loaded["libretro"] then - error("NYI") -else - error("no backend for ubiquitousse.input") -end - -return input +return require((...)..".input") diff --git a/input/input.lua b/input/input.lua index ad84a96..19a6e24 100644 --- a/input/input.lua +++ b/input/input.lua @@ -10,495 +10,24 @@ if not loaded then signal = nil end -- TODO: other, optional, default/generic inputs, and a way to know if they are binded. -- TODO: multiplayer input helpers? something like getting the same input for different players, or default inputs for different players -local input -local sqrt = math.sqrt -local unpack = table.unpack or unpack -local dt = 0 +-- FIXME https://love2d.org/forums/viewtopic.php?p=241434#p241434 ---- Used to store inputs which were updated this frame --- { Input: true, ... } --- This table is for internal use and shouldn't be used from an external script. -local updated = {} - ---- ButtonInput methods --- @impl ubiquitousse -local button_mt = { - --- Returns a new ButtonInput with the same properties. - -- @treturn ButtonInput the cloned object - clone = function(self) - return input.button(unpack(self.detectors)) - end, - - --- Bind new ButtonDetector(s) to this input. - -- @tparam ButtonDetectors ... buttons detectors or buttons identifiers to add - -- @treturn ButtonInput this ButtonInput object - bind = function(self, ...) - for _, d in ipairs({...}) do - table.insert(self.detectors, input.buttonDetector(d)) - end - return self - end, - --- Unbind ButtonDetector(s). - -- @tparam ButtonDetectors ... buttons detectors or buttons identifiers to remove - -- @treturn ButtonInput this ButtonInput object - unbind = function(self, ...) - for _, d in ipairs({...}) do - for i=#self.detectors, 1, -1 do - if self.detectors[i] == d then - table.remove(self.detectors, i) - break - end - end - end - return self - end, - --- Unbind all ButtonDetector(s). - -- @treturn ButtonInput this ButtonInput object - clear = function(self) - self.detectors = {} - return self - end, - - --- Hijacks the input. - -- This function returns a new input object which mirrors the current object, except it will hijack every new input. - -- This means any new button press/down/release will only be visible to the new object; the button will always appear unpressed for the initial object. - -- This is useful for contextual input, for example if you want to display a menu without pausing the game: the menu - -- can hijack relevant inputs while it is open, so they don't trigger any action in the rest of the game. - -- An input can be hijacked several times; the one which hijacked it last will be the active one. - -- @treturn ButtonInput the new input object which is hijacking the input - hijack = function(self) - local hijacked = setmetatable({}, { __index = self, __newindex = self }) - table.insert(self.hijackStack, hijacked) - self.hijacking = hijacked - return hijacked - end, - --- Release the input that was hijacked by this object. - -- Input will be given back to the previous object. - -- @treturn ButtonInput this ButtonInput object - free = function(self) - local hijackStack = self.hijackStack - for i, v in ipairs(hijackStack) do - if v == self then - table.remove(hijackStack, i) - self.hijacking = hijackStack[#hijackStack] - return self - end - end - error("This object is currently not hijacking this input") - end, - - --- Returns true if the input was just pressed. - -- @treturn boolean true if the input was pressed, false otherwise - pressed = function(self) - if self.hijacking == self then - self:update() - return self.state == "pressed" - else - return false - end - end, - --- Returns true if the input was just released. - -- @treturn boolean true if the input was released, false otherwise - released = function(self) - if self.hijacking == self then - self:update() - return self.state == "released" - else - return false - end - end, - --- Returns true if the input is down. - -- @treturn boolean true if the input is currently down, false otherwise - down = function(self) - if self.hijacking == self then - self:update() - local state = self.state - return state == "down" or state == "pressed" - else - return false - end - end, - --- Returns true if the input is up. - -- @treturn boolean true if the input is currently up, false otherwise - up = function(self) - return not self:down() - end, - - --- Update button state. - -- Automatically called, don't call unless you know what you're doing. - -- @impl ubiquitousse - update = function(self) - if not updated[self] then - local down = false - for _, d in ipairs(self.detectors) do - if d() then - down = true - break - end - end - local state = self.state - if down then - if state == "none" or state == "released" then - self.state = "pressed" - else - self.state = "down" - end - else - if state == "down" or state == "pressed" then - self.state = "released" - else - self.state = "none" - end - end - updated[self] = true - end - end -} -button_mt.__index = button_mt - ---- AxisInput methods --- @impl ubiquitousse -local axis_mt = { - --- Returns a new AxisInput with the same properties. - -- @treturn AxisInput the cloned object - clone = function(self) - return input.axis(unpack(self.detectors)) - :threshold(self.threshold) - :triggeringThreshold(self.triggeringThreshold) - end, - - --- Bind new AxisDetector(s) to this input. - -- @tparam AxisDetectors ... axis detectors or axis identifiers to add - -- @treturn AxisInput this AxisInput object - bind = function(self, ...) - for _,d in ipairs({...}) do - table.insert(self.detectors, input.axisDetector(d)) - end - return self - end, - --- Unbind AxisDetector(s). - -- @tparam AxisDetectors ... axis detectors or axis identifiers to remove - -- @treturn AxisInput this AxisInput object - unbind = button_mt.unbind, - --- Unbind all AxisDetector(s). - -- @treturn AxisInput this AxisInput object - clear = button_mt.clear, - - --- Hijacks the input. - -- This function returns a new input object which mirrors the current object, except it will hijack every new input. - -- This means any value change will only be visible to the new object; the axis will always appear to be at 0 for the initial object. - -- An input can be hijacked several times; the one which hijacked it last will be the active one. - -- @treturn AxisInput the new input object which is hijacking the input - hijack = function(self) - local hijacked - hijacked = setmetatable({ - positive = input.button(function() return hijacked:value() > self.triggeringThreshold end), - negative = input.button(function() return hijacked:value() < -self.triggeringThreshold end) - }, { __index = self, __newindex = self }) - table.insert(self.hijackStack, hijacked) - self.hijacking = hijacked - return hijacked - end, - --- Release the input that was hijacked by this object. - -- Input will be given back to the previous object. - -- @treturn AxisInput this AxisInput object - free = button_mt.free, - - --- Sets the default detection threshold (deadzone). - -- 0 by default. - -- @tparam number new the new detection threshold - -- @treturn AxisInput this AxisInput object - threshold = function(self, new) - self.threshold = tonumber(new) - return self - end, - - --- Returns the value of the input (between -1 and 1). - -- @tparam[opt=default threshold] number threshold value to use - -- @treturn number the input value - value = function(self, curThreshold) - if self.hijacking == self then - self:update() - local val = self.val - return math.abs(val) > math.abs(curThreshold or self.threshold) and val or 0 - else - return 0 - end - end, - --- Returns the change in value of the input since last update (between -2 and 2). - -- @treturn number the value delta - delta = function(self) - if self.hijacking == self then - self:update() - return self.dval - else - return 0 - end - end, - --- Returns the raw value of the input (between -max and +max). - -- @tparam[opt=default threshold*max] number raw threshold value to use - -- @treturn number the input raw value - raw = function(self, rawThreshold) - if self.hijacking == self then - self:update() - local raw = self.raw - return math.abs(raw) > math.abs(rawThreshold or self.threshold*self.max) and raw or 0 - else - return 0 - end - end, - --- Return the raw max of the input. - -- @treturn number the input raw max - max = function(self) - self:update() - return self.max - end, - - --- Sets the default triggering threshold, i.e. how the minimal axis value for which the associated buttons will be considered down. - -- 0.5 by default. - -- @tparam number new the new triggering threshold - -- @treturn AxisInput this AxisInput object - triggeringThreshold = function(self, new) - self.triggeringThreshold = tonumber(new) - return self - end, - - --- The associated button pressed when the axis reaches a positive value. - positive = nil, - --- The associated button pressed when the axis reaches a negative value. - negative = nil, - - --- Update axis state. - -- Automatically called, don't call unless you know what you're doing. - -- @impl ubiquitousse - update = function(self) - if not updated[self] then - local val, raw, max = 0, 0, 1 - for _, d in ipairs(self.detectors) do - local v, r, m = d() -- v[-1,1], r[-m,+m] - if math.abs(v) > math.abs(val) then - val, raw, max = v, r or v, m or 1 - end - end - self.dval = val - self.val - self.val, self.raw, self.max = val, raw, max - updated[self] = true - end - end, - - --- LÖVE note: other callbacks that are defined in backend/love.lua and need to be called in the associated LÖVE callbacks. -} -axis_mt.__index = axis_mt - ---- PointerInput methods --- @impl ubiquitousse -local pointer_mt = { - --- Returns a new PointerInput with the same properties. - -- @treturn PointerInput the cloned object - clone = function(self) - return input.pointer(unpack(self.detectors)) - :dimensions(self.width, self.height) - :offset(self.offsetX, self.offsetY) - :speed(self.xSpeed, self.ySpeed) - end, - - --- Bind new axis couples to this input. - -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to add and in which mode - -- @treturn PointerInput this PointerInput object - bind = function(self, ...) - for _, p in ipairs({...}) do - if type(p) == "table" then - local h, v = p[2], p[3] - if getmetatable(h) ~= axis_mt then - h = input.axis(h) - end - if getmetatable(v) ~= axis_mt then - v = input.axis(v) - end - table.insert(self.detectors, { p[1], h, v }) - else - error("Pointer detector must be a table") - end - end - return self - end, - --- Unbind axis couple(s). - -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to remove - -- @treturn PointerInput this PointerInput object - unbind = button_mt.unbind, - --- Unbind all axis couple(s). - -- @treturn PointerInput this PointerInput object - clear = button_mt.clear, - - --- Hijacks the input. - -- This function returns a new input object which mirrors the current object, except it will hijack every new input. - -- This means any value change will only be visible to the new object; the pointer will always appear to be at offsetX,offsetY for the initial object. - -- An input can be hijacked several times; the one which hijacked it last will be the active one. - -- @treturn PointerInput the new input object which is hijacking the input - hijack = function(self) - local hijacked - hijacked = { - horizontal = input.axis(function() - local h = hijacked:x() - local width = hijacked.width - return h/width, h, width - end), - vertical = input.axis(function() - local v = hijacked:y() - local height = hijacked.height - return v/height, v, height - end) - } - hijacked.right, hijacked.left = hijacked.horizontal.positive, hijacked.horizontal.negative - hijacked.up, hijacked.down = hijacked.vertical.negative, hijacked.vertical.positive - setmetatable(hijacked, { __index = self, __newindex = self }) - table.insert(self.hijackStack, hijacked) - self.hijacking = hijacked - return hijacked - end, - --- Free the input that was hijacked by this object. - -- Input will be given back to the previous object. - -- @treturn PointerInput this PointerInput object - free = button_mt.free, - - --- Set the moving area half-dimensions. - -- Call without argument to use half the window dimensions. - -- It's the half dimensions because axes values goes from -1 to 1, so theses dimensions only - -- covers values from x=0,y=0 to x=1,y=1. The full moving area will be 4*newWidth*newHeight. - -- @tparam number newWidth new width - -- @tparam number newHeight new height - -- @treturn PointerInput this PointerInput object - dimensions = function(self, newWidth, newHeight) - self.width, self.height = newWidth, newHeight - return self - end, - --- Set the moving area coordinates offset. - -- The offset is a value automatically added to the x and y values when using the x() and y() methods. - -- Call without argument to automatically offset so 0,0 <= x(),y() <= width,height, i.e. offset to width,height. - -- @tparam number newOffX new X offset - -- @tparam number newOffY new Y offset - -- @treturn PointerInput this PointerInput object - offset = function(self, newOffX, newOffY) - self.offsetX, self.offsetY = newOffX, newOffY - return self - end, - --- Set maximal speed (pixels-per-milisecond) - -- Only used in relative mode. - -- Calls without argument to use the raw data and don't apply a speed modifier. - -- @tparam number newXSpeed new X speed - -- @tparam number newYSpeed new Y speed - -- @treturn PointerInput this PointerInput object - speed = function(self, newXSpeed, newYSpeed) - self.xSpeed, self.ySpeed = newXSpeed, newYSpeed or newXSpeed - return self - end, - - --- Returns the current X value of the pointer. - -- @treturn number X value - x = function(self) - if self.hijacking == self then - self:update() - return self.valX + (self.offsetX or self.width or input.getDrawWidth()/2) - else - return self.offsetX or self.width or input.getDrawWidth()/2 - end - end, - --- Returns the current Y value of the pointer. - -- @treturn number Y value - y = function(self) - if self.hijacking == self then - self:update() - return self.valY + (self.offsetY or self.height or input.getDrawHeight()/2) - else - return self.offsetY or self.height or input.getDrawHeight()/2 - end - end, - - --- Returns the X and Y value of the pointer, clamped. - -- They are clamped to stay in the ellipse touching all 4 sides of the dimension rectangle, i.e. the - -- (x,y) vector's magnitude reached its maximum either in (0,height) or (width,0). - -- Typically, this is used with square dimensions for player movements: when moving diagonally, the magnitude - -- will be the same as when moving horiontally or vertically, thus avoiding faster diagonal movement, A.K.A. "straferunning". - -- If you're not conviced by my overly complicated explanation: just use this to retrieve x and y for movement and everything - -- will be fine. - -- @treturn number X value - -- @treturn number Y value - clamped = function(self) - local width, height = self.width, self.height - if self.hijacking == self then - self:update() - local x, y = self.valX, self.valY - local cx, cy = x, y - local normalizedMagnitude = (x*x)/(width*width) + (y*y)/(height*height) -- go back to a unit circle - if normalizedMagnitude > 1 then - local magnitude = sqrt(x*x + y*y) - cx, cy = cx / magnitude * width, cy / magnitude * height - end - return cx + (self.offsetX or width or input.getDrawWidth()/2), cy + (self.offsetY or height or input.getDrawHeight()/2) - else - return self.offsetX or width or input.getDrawWidth()/2, self.offsetY or height or input.getDrawHeight()/2 - end - end, - - --- The associated horizontal axis. - horizontal = nil, - --- The associated vertical axis. - vertical = nil, - - --- The associated button pressed when the pointer goes to the right. - right = nil, - --- The associated button pressed when the pointer goes to the left. - left = nil, - --- The associated button pressed when the pointer points up. - up = nil, - --- The associated button pressed when the pointer points down. - down = nil, - - --- Update pointer state. - -- Automatically called, don't call unless you know what you're doing. - -- @impl ubiquitousse - update = function(self) - if not updated[self] then - local x, y = self.valX, self.valY - local xSpeed, ySpeed = self.xSpeed, self.ySpeed - local width, height = self.width or input.getDrawWidth()/2, self.height or input.getDrawHeight()/2 - local newX, newY = x, y - local maxMovX, maxMovY = 0, 0 -- the maxium axis movement in a direction (used to determine which axes have the priority) (absolute value) - for _, pointer in ipairs(self.detectors) do - local mode, xAxis, yAxis = unpack(pointer) - if mode == "relative" then - local movX, movY = math.abs(xAxis:value()), math.abs(yAxis:value()) - if movX > maxMovX then - newX = x + (xSpeed and (xAxis:value() * xSpeed * dt) or xAxis:raw()) - maxMovX = movX - end - if movY > maxMovY then - newY = y + (ySpeed and (yAxis:value() * ySpeed * dt) or yAxis:raw()) - maxMovY = movY - end - elseif mode == "absolute" then - local movX, movY = math.abs(xAxis:delta()), math.abs(yAxis:delta()) - if movX > maxMovX then - newX = xAxis:value() * width - maxMovX = movX - end - if movY > maxMovY then - newY = yAxis:value() * height - maxMovY = movY - end - end - end - self.valX, self.valY = math.min(math.abs(newX), width) * (newX < 0 and -1 or 1), math.min(math.abs(newY), height) * (newY < 0 and -1 or 1) - updated[self] = true - end - end -} -pointer_mt.__index = pointer_mt +local button_mt +local axis_mt +local pointer_mt --- Input stuff -- Inspired by Tactile by Andrew Minnich (https://github.com/tesselode/tactile), under the MIT license. -- Ubiquitousse considers two basic input methods, called buttons (binary input) and axes (analog input). +local input input = { + --- Used to store inputs which were updated this frame + -- { Input: true, ... } + -- This table is for internal use and shouldn't be used from an external script. + updated = {}, + + dt = 0, + --------------------------------- --- Detectors (input sources) --- --------------------------------- @@ -518,12 +47,11 @@ input = { -- The function may error if the identifier is incorrect. -- @tparam string button identifier, depends on the platform Ubiquitousse is running on -- @treturn the new button detector - -- @impl backend + -- @impl love basicButtonDetector = function(str) end, --- Make a new button detector from a detector function, string, or list of buttons. -- @tparam string, function button identifier - -- @impl ubiquitousse buttonDetector = function(obj) if type(obj) == "function" then return obj @@ -557,12 +85,11 @@ input = { -- The function may error if the identifier is incorrect. -- @tparam string axis identifier, depends on the platform Ubiquitousse is running on -- @treturn the new axis detector - -- @impl backend + -- @impl love basicAxisDetector = function(str) end, --- Make a new axis detector from a detector function, string, or a couple of buttons. -- @tparam string, function or table axis identifier - -- @impl ubiquitousse axisDetector = function(obj) if type(obj) == "function" then return obj @@ -581,91 +108,6 @@ input = { error(("Not a valid axis detector: %s"):format(obj)) end, - ------------------------------------------ - --- Inputs (the thing you want to use) --- - ------------------------------------------ - - -- Buttons inputs -- - -- Button input is a container for buttons detector. A button will be pressed when one of its detectors returns true. - -- Inputs also knows if the button was just pressed or released. - -- @tparam ButtonDetectors ... all the buttons detectors or buttons identifiers - -- @tretrun ButtonInput the object - -- @impl ubiquitousse - button = function(...) - local r = setmetatable({ - hijackStack = {}, -- hijackers stack, last element is the object currently hijacking this input - hijacking = nil, -- object currently hijacking this input - detectors = {}, -- detectors list - state = "none" -- current state (none, pressed, down, released) - }, button_mt) - table.insert(r.hijackStack, r) - r.hijacking = r - r:bind(...) - return r - end, - - -- Axis inputs -- - -- Axis input is a container for axes detector. An axis input will return the value of the axis detector the most far away from their center (0). - -- Axis input provide a threshold setting; every axis which has a distance to the center below the threshold (none by default) will be ignored. - -- @tparam AxisDetectors ... all the axis detectors or axis identifiers - -- @tretrun AxisInput the object - -- @impl ubiquitousse - axis = function(...) - local r = setmetatable({ - hijackStack = {}, -- hijackers stack, last element is the object currently hijacking this input - hijacking = nil, -- object currently hijacking this input - detectors = {}, -- detectors list - val = 0, -- current value between -1 and 1 - dval = 0, -- change between -2 and 2 - raw = 0, -- raw value between -max and +max - max = 1, -- maximum for raw values - threshold = 0, -- ie., the deadzone - triggeringThreshold = 0.5 -- digital button threshold - }, axis_mt) - table.insert(r.hijackStack, r) - r.hijacking = r - r:bind(...) - r.positive = input.button(function() return r:value() > r.triggeringThreshold end) - r.negative = input.button(function() return r:value() < -r.triggeringThreshold end) - return r - end, - - -- Pointer inputs -- - -- Pointer inputs are container for two axes input, in order to represent a two-dimensionnal pointing device, e.g. a mouse or a stick. - -- Each pointer detector is a table with 3 fields: mode(string), XAxis(axis), YAxis(axis). mode can either be "relative" or "absolute". - -- In relative mode, the pointer will return the movement since last update (for example to move a mouse pointer with a stick). - -- In absolute mode, the pointer will return the pointer position directly deduced of the current axes position. - -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to add and in which mode - -- @tretrun PointerInput the object - -- @impl ubiquitousse - pointer = function(...) - local r = setmetatable({ - hijackStack = {}, -- hijackers stack, first element is the object currently hijacking this input - hijacking = nil, -- object currently hijacking this input - detectors = {}, -- pointers list (composite detectors) - valX = 0, valY = 0, -- pointer position - width = 1, height = 1, -- half-dimensions of the movement area - offsetX = 0, offsetY = 0, -- offsets - xSpeed = 1, ySpeed = 1, -- speed (pixels/milisecond); for relative mode - }, pointer_mt) - table.insert(r.hijackStack, r) - r.hijacking = r - r:bind(...) - r.horizontal = input.axis(function() - local h = r:x() - local width = r.width - return h/width, h, width - end) - r.vertical = input.axis(function() - local v = r:y() - local height = r.height - return v/height, v, height - end) - r.right, r.left = r.horizontal.positive, r.horizontal.negative - r.up, r.down = r.vertical.negative, r.vertical.positive - return r - end, - ------------------------------ --- Input detection helpers -- ------------------------------ @@ -675,13 +117,13 @@ input = { -- This may also returns "axis threshold" buttons if an axis passes the threshold. -- @tparam[opt=0.5] number threshold the threshold to detect axes as button -- @treturn string,... buttons identifiers list - -- @impl backend + -- @impl love buttonUsed = function(threshold) end, --- Returns a list of the axes currently in use, identified by their string axis identifier -- @tparam[opt=0.5] number threshold the threshold to detect axes -- @treturn string,... axes identifiers list - -- @impl backend + -- @impl love axisUsed = function(threshold) end, --- Returns a nice name for the button identifier. @@ -689,7 +131,7 @@ input = { -- May returns the raw identifier if you're lazy. -- @tparam string... button identifier string(s) -- @treturn string... the displayable names - -- @impl backend + -- @impl love buttonName = function(...) end, --- Returns a nice name for the axis identifier. @@ -697,7 +139,7 @@ input = { -- May returns the raw identifier if you're lazy. -- @tparam string... axis identifier string(s) -- @treturn string... the displayable names - -- @impl backend + -- @impl love axisName = function(...) end, ------------------- @@ -710,7 +152,7 @@ input = { -- any platform without having to configure the keys. -- If some key function in your game match one of theses defaults, using it instead of creating a new -- input would be a good idea. - -- @impl mixed + -- @impl love default = { pointer = nil, -- Pointer: used to move and select. Example binds: arrow keys, WASD, stick. confirm = nil, -- Button: used to confirm something. Example binds: Enter, A button. @@ -719,7 +161,7 @@ input = { --- Get draw area dimensions. -- Used for pointers. - -- @impl backend + -- @impl love getDrawWidth = function() return 1 end, getDrawHeight = function() return 1 end, @@ -727,10 +169,9 @@ input = { -- Should be called at every game update. If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event. -- The backend can hook into this function to to its input-related updates. -- @tparam numder dt the delta-time - -- @impl ubiquitousse update = function(newDt) - dt = newDt - updated = {} + input.dt = newDt + input.updated = {} end --- If you use LÖVE, note that in order to provide every feature (especially key detection), several callbacks functions will @@ -739,6 +180,16 @@ input = { -- callbacks, minux the "love."). } +package.loaded[...] = input +button_mt = require((...):gsub("input$", "button")) +axis_mt = require((...):gsub("input$", "axis")) +pointer_mt = require((...):gsub("input$", "pointer")) + +-- Constructors +input.button = button_mt._new +input.axis = axis_mt._new +input.pointer = pointer_mt._new + -- Create default inputs input.default.pointer = input.pointer() input.default.confirm = input.button() @@ -749,4 +200,6 @@ if signal then signal.event:bind("update", input.update) end +require((...):gsub("input$", "love")) + return input diff --git a/input/backend/love.lua b/input/love.lua similarity index 99% rename from input/backend/love.lua rename to input/love.lua index 89650cb..b6e66d9 100644 --- a/input/backend/love.lua +++ b/input/love.lua @@ -1,4 +1,4 @@ -local input = require((...):match("^(.-%.)backend").."input") +local input = require((...):gsub("love$", "input")) local loaded, signal = pcall(require, (...):match("^(.-)input").."signal") if not loaded then signal = nil end diff --git a/input/pointer.lua b/input/pointer.lua new file mode 100644 index 0000000..d5e268a --- /dev/null +++ b/input/pointer.lua @@ -0,0 +1,246 @@ +local input = require((...):gsub("pointer$", "input")) +local button_mt = require((...):gsub("pointer$", "button")) +local axis_mt = require((...):gsub("pointer$", "axis")) + +local sqrt = math.sqrt + +--- PointerInput methods +local pointer_mt +pointer_mt = { + -- Pointer inputs -- + -- Pointer inputs are container for two axes input, in order to represent a two-dimensionnal pointing device, e.g. a mouse or a stick. + -- Each pointer detector is a table with 3 fields: mode(string), XAxis(axis), YAxis(axis). mode can either be "relative" or "absolute". + -- In relative mode, the pointer will return the movement since last update (for example to move a mouse pointer with a stick). + -- In absolute mode, the pointer will return the pointer position directly deduced of the current axes position. + -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to add and in which mode + -- @tretrun PointerInput the object + _new = function(...) + local r = setmetatable({ + hijackStack = {}, -- hijackers stack, first element is the object currently hijacking this input + hijacking = nil, -- object currently hijacking this input + detectors = {}, -- pointers list (composite detectors) + valX = 0, valY = 0, -- pointer position + width = 1, height = 1, -- half-dimensions of the movement area + offsetX = 0, offsetY = 0, -- offsets + xSpeed = 1, ySpeed = 1, -- speed (pixels/milisecond); for relative mode + }, pointer_mt) + table.insert(r.hijackStack, r) + r.hijacking = r + r:bind(...) + r.horizontal = input.axis(function() + local h = r:x() + local width = r.width + return h/width, h, width + end) + r.vertical = input.axis(function() + local v = r:y() + local height = r.height + return v/height, v, height + end) + r.right, r.left = r.horizontal.positive, r.horizontal.negative + r.up, r.down = r.vertical.negative, r.vertical.positive + return r + end, + + --- Returns a new PointerInput with the same properties. + -- @treturn PointerInput the cloned object + clone = function(self) + return input.pointer(unpack(self.detectors)) + :dimensions(self.width, self.height) + :offset(self.offsetX, self.offsetY) + :speed(self.xSpeed, self.ySpeed) + end, + + --- Bind new axis couples to this input. + -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to add and in which mode + -- @treturn PointerInput this PointerInput object + bind = function(self, ...) + for _, p in ipairs({...}) do + if type(p) == "table" then + local h, v = p[2], p[3] + if getmetatable(h) ~= axis_mt then + h = input.axis(h) + end + if getmetatable(v) ~= axis_mt then + v = input.axis(v) + end + table.insert(self.detectors, { p[1], h, v }) + else + error("Pointer detector must be a table") + end + end + return self + end, + --- Unbind axis couple(s). + -- @tparam table{mode,XAxis,YAxis} ... couples of axis detectors, axis identifiers or axis input to remove + -- @treturn PointerInput this PointerInput object + unbind = button_mt.unbind, + --- Unbind all axis couple(s). + -- @treturn PointerInput this PointerInput object + clear = button_mt.clear, + + --- Hijacks the input. + -- This function returns a new input object which mirrors the current object, except it will hijack every new input. + -- This means any value change will only be visible to the new object; the pointer will always appear to be at offsetX,offsetY for the initial object. + -- An input can be hijacked several times; the one which hijacked it last will be the active one. + -- @treturn PointerInput the new input object which is hijacking the input + hijack = function(self) + local hijacked + hijacked = { + horizontal = input.axis(function() + local h = hijacked:x() + local width = hijacked.width + return h/width, h, width + end), + vertical = input.axis(function() + local v = hijacked:y() + local height = hijacked.height + return v/height, v, height + end) + } + hijacked.right, hijacked.left = hijacked.horizontal.positive, hijacked.horizontal.negative + hijacked.up, hijacked.down = hijacked.vertical.negative, hijacked.vertical.positive + setmetatable(hijacked, { __index = self, __newindex = self }) + table.insert(self.hijackStack, hijacked) + self.hijacking = hijacked + return hijacked + end, + --- Free the input that was hijacked by this object. + -- Input will be given back to the previous object. + -- @treturn PointerInput this PointerInput object + free = button_mt.free, + + --- Set the moving area half-dimensions. + -- Call without argument to use half the window dimensions. + -- It's the half dimensions because axes values goes from -1 to 1, so theses dimensions only + -- covers values from x=0,y=0 to x=1,y=1. The full moving area will be 4*newWidth*newHeight. + -- @tparam number newWidth new width + -- @tparam number newHeight new height + -- @treturn PointerInput this PointerInput object + dimensions = function(self, newWidth, newHeight) + self.width, self.height = newWidth, newHeight + return self + end, + --- Set the moving area coordinates offset. + -- The offset is a value automatically added to the x and y values when using the x() and y() methods. + -- Call without argument to automatically offset so 0,0 <= x(),y() <= width,height, i.e. offset to width,height. + -- @tparam number newOffX new X offset + -- @tparam number newOffY new Y offset + -- @treturn PointerInput this PointerInput object + offset = function(self, newOffX, newOffY) + self.offsetX, self.offsetY = newOffX, newOffY + return self + end, + --- Set maximal speed (pixels-per-milisecond) + -- Only used in relative mode. + -- Calls without argument to use the raw data and don't apply a speed modifier. + -- @tparam number newXSpeed new X speed + -- @tparam number newYSpeed new Y speed + -- @treturn PointerInput this PointerInput object + speed = function(self, newXSpeed, newYSpeed) + self.xSpeed, self.ySpeed = newXSpeed, newYSpeed or newXSpeed + return self + end, + + --- Returns the current X value of the pointer. + -- @treturn number X value + x = function(self) + if self.hijacking == self then + self:update() + return self.valX + (self.offsetX or self.width or input.getDrawWidth()/2) + else + return self.offsetX or self.width or input.getDrawWidth()/2 + end + end, + --- Returns the current Y value of the pointer. + -- @treturn number Y value + y = function(self) + if self.hijacking == self then + self:update() + return self.valY + (self.offsetY or self.height or input.getDrawHeight()/2) + else + return self.offsetY or self.height or input.getDrawHeight()/2 + end + end, + + --- Returns the X and Y value of the pointer, clamped. + -- They are clamped to stay in the ellipse touching all 4 sides of the dimension rectangle, i.e. the + -- (x,y) vector's magnitude reached its maximum either in (0,height) or (width,0). + -- Typically, this is used with square dimensions for player movements: when moving diagonally, the magnitude + -- will be the same as when moving horiontally or vertically, thus avoiding faster diagonal movement, A.K.A. "straferunning". + -- If you're not conviced by my overly complicated explanation: just use this to retrieve x and y for movement and everything + -- will be fine. + -- @treturn number X value + -- @treturn number Y value + clamped = function(self) + local width, height = self.width, self.height + if self.hijacking == self then + self:update() + local x, y = self.valX, self.valY + local cx, cy = x, y + local normalizedMagnitude = (x*x)/(width*width) + (y*y)/(height*height) -- go back to a unit circle + if normalizedMagnitude > 1 then + local magnitude = sqrt(x*x + y*y) + cx, cy = cx / magnitude * width, cy / magnitude * height + end + return cx + (self.offsetX or width or input.getDrawWidth()/2), cy + (self.offsetY or height or input.getDrawHeight()/2) + else + return self.offsetX or width or input.getDrawWidth()/2, self.offsetY or height or input.getDrawHeight()/2 + end + end, + + --- The associated horizontal axis. + horizontal = nil, + --- The associated vertical axis. + vertical = nil, + + --- The associated button pressed when the pointer goes to the right. + right = nil, + --- The associated button pressed when the pointer goes to the left. + left = nil, + --- The associated button pressed when the pointer points up. + up = nil, + --- The associated button pressed when the pointer points down. + down = nil, + + --- Update pointer state. + -- Automatically called, don't call unless you know what you're doing. + update = function(self) + if not input.updated[self] then + local x, y = self.valX, self.valY + local xSpeed, ySpeed = self.xSpeed, self.ySpeed + local width, height = self.width or input.getDrawWidth()/2, self.height or input.getDrawHeight()/2 + local newX, newY = x, y + local maxMovX, maxMovY = 0, 0 -- the maxium axis movement in a direction (used to determine which axes have the priority) (absolute value) + for _, pointer in ipairs(self.detectors) do + local mode, xAxis, yAxis = unpack(pointer) + if mode == "relative" then + local movX, movY = math.abs(xAxis:value()), math.abs(yAxis:value()) + if movX > maxMovX then + newX = x + (xSpeed and (xAxis:value() * xSpeed * input.dt) or xAxis:raw()) + maxMovX = movX + end + if movY > maxMovY then + newY = y + (ySpeed and (yAxis:value() * ySpeed * input.dt) or yAxis:raw()) + maxMovY = movY + end + elseif mode == "absolute" then + local movX, movY = math.abs(xAxis:delta()), math.abs(yAxis:delta()) + if movX > maxMovX then + newX = xAxis:value() * width + maxMovX = movX + end + if movY > maxMovY then + newY = yAxis:value() * height + maxMovY = movY + end + end + end + self.valX, self.valY = math.min(math.abs(newX), width) * (newX < 0 and -1 or 1), math.min(math.abs(newY), height) * (newY < 0 and -1 or 1) + input.updated[self] = true + end + end +} +pointer_mt.__index = pointer_mt + +return pointer_mt diff --git a/scene/scene.lua b/scene/scene.lua index 4a5fa24..dde5c10 100644 --- a/scene/scene.lua +++ b/scene/scene.lua @@ -22,28 +22,22 @@ if not loaded then timer = nil end local scene scene = { --- The current scene table. - -- @impl ubiquitousse current = nil, --- Shortcut for scene.current.timer. - -- @impl ubiquitousse timer = nil, --- Shortcut for scene.current.signal. - -- @impl ubiquitousse signal = nil, --- The scene stack: list of scene, from the farest one to the nearest. - -- @impl ubiquitousse stack = {}, --- A prefix for scene modules names. -- Will search in the "scene" directory by default. Redefine it to fit your own ridiculous filesystem. - -- @impl ubiquitousse prefix = "scene.", --- Creates and returns a new Scene object. -- @tparam[opt="unamed"] string name the new scene name - -- @impl ubiquitousse new = function(name) return { name = name or "unamed", -- The scene name. @@ -68,7 +62,6 @@ scene = { -- Then the stack is changed to replace the old scene with the new one. -- @tparam string/table scenePath the new scene module name, or the scene table directly -- @param ... arguments to pass to the scene's enter function - -- @impl ubiquitousse switch = function(scenePath, ...) local previous = scene.current scene.current = type(scenePath) == "string" and require(scene.prefix..scenePath) or scenePath @@ -89,7 +82,6 @@ scene = { -- will be reused. -- @tparam string/table scenePath the new scene module name, or the scene table directly -- @param ... arguments to pass to the scene's enter function - -- @impl ubiquitousse push = function(scenePath, ...) local previous = scene.current scene.current = type(scenePath) == "string" and require(scene.prefix..scenePath) or scenePath @@ -104,7 +96,6 @@ scene = { --- Pop the current scene from the scene stack. -- The previous scene will be set as the current scene, then the current scene exit function will be called, -- then the previous scene resume function will be called, and then the current scene will be removed from the stack. - -- @impl ubiquitousse pop = function() local previous = scene.current scene.current = scene.stack[#scene.stack-1] @@ -119,7 +110,6 @@ scene = { end, --- Pop all scenes. - -- @impl ubiquitousse popAll = function() while scene.current do scene.pop() @@ -130,7 +120,6 @@ scene = { -- Should be called at every game update. If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event. -- @tparam number dt the delta-time (milisecond) -- @param ... arguments to pass to the scene's update function after dt - -- @impl ubiquitousse update = function(dt, ...) if scene.current then if timer then scene.current.timer:update(dt) end @@ -141,7 +130,6 @@ scene = { --- Draw the current scene. -- Should be called every time the game is draw. If ubiquitousse.signal is available, will be bound to the "draw" signal in signal.event. -- @param ... arguments to pass to the scene's draw function - -- @impl ubiquitousse draw = function(...) if scene.current then scene.current:draw(...) end end diff --git a/signal/backend/love.lua b/signal/backend/love.lua deleted file mode 100644 index 87045d3..0000000 --- a/signal/backend/love.lua +++ /dev/null @@ -1,43 +0,0 @@ -local signal = require((...):match("^(.-%.)backend").."signal") - -function signal.registerEvents() - local callbacks = { -- everything except run, errorhandler, threaderror - "displayrotated", "draw", "load", "lowmemory", "quit", "update", - "directorydropped", "filedropped", "focus", "mousefocus", "resize", "visible", - "keypressed", "keyreleased", "textedited", "textinput", - "mousemoved", "mousepressed", "mousereleased", "wheelmoved", - "gamepadaxis", "gamepadpressed", "gamepadreleased", - "joystickadded", "joystickaxis", "joystickhat", "joystickpressed", "joystickreleased", "joystickremoved", - "touchmoved", "touchpressed", "touchreleased" - } - local event = signal.event - for _, callback in ipairs(callbacks) do - if callback == "update" then - if love[callback] then - local old = love[callback] - love[callback] = function(dt) - old(dt) - event:emit(callback, dt) - end - else - love[callback] = function(dt) - event:emit(callback, dt) - end - end - else - if love[callback] then - local old = love[callback] - love[callback] = function(...) - old(...) - event:emit(callback, ...) - end - else - love[callback] = function(...) - event:emit(callback, ...) - end - end - end - end -end - -return signal diff --git a/signal/init.lua b/signal/init.lua index 827b528..bd21a3c 100644 --- a/signal/init.lua +++ b/signal/init.lua @@ -1,14 +1 @@ -local signal - -local p = ... -if love then - signal = require(p..".backend.love") -elseif package.loaded["ctr"] then - error("NYI") -elseif package.loaded["libretro"] then - error("NYI") -else - error("no backend for ubiquitousse.signal") -end - -return signal \ No newline at end of file +return require((...)..".signal") diff --git a/signal/signal.can b/signal/signal.can index c732e32..472ecea 100644 --- a/signal/signal.can +++ b/signal/signal.can @@ -2,11 +2,9 @@ let registry_mt = { --- Map of signals to list of listeners. - -- @impl ubiquitousse signals = {}, --- Bind one or several functions to a signal name. - -- @impl ubiquitousse bind = :(name, fn, ...) if not @signals[name] then @signals[name] = {} @@ -18,7 +16,6 @@ let registry_mt = { end, --- Unbind one or several functions to a signal name. - -- @impl ubiquitousse unbind = :(name, fn, ...) if not @signals[name] then return @@ -34,13 +31,11 @@ let registry_mt = { end, --- Remove every bound function to a signal name. - -- @impl ubiquitousse unbindAll = :(name) @signals[name] = nil end, --- Replace a bound function with another function. - -- @impl ubiquitousse replace = :(name, sourceFn, destFn) if not @signals[name] then @signals[name] = {} @@ -54,13 +49,11 @@ let registry_mt = { end, --- Remove every bound function to every signal. - -- @impl ubiquitousse clear = :() @signals = {} end, --- Emit a signal, i.e. call every function bound to it, with the given arguments. - -- @impl ubiquitousse emit = :(name, ...) if @signals[name] then for _, fn in ipairs(@signals[name]) do @@ -74,13 +67,11 @@ registry_mt.__index = registry_mt let signal = { --- Creates and return a new SignalRegistry. -- A SignalRegistry is a separate ubiquitousse.signal instance: its signals will be independant from other registries. - -- @impl ubiquitousse new = () return setmetatable({ signals = {} }, registry_mt) end, - + --- Global SignalRegistry. - -- @impl ubiquitousse signals = {}, bind = (...) return registry_mt.bind(signal, ...) @@ -102,13 +93,50 @@ let signal = { -- * update(dt), should be called on every game update -- * draw, should be called on every game draw -- * for LÖVE, there are callbacks for every LÖVE callback function that need to be called on their corresponding LÖVE callback - -- @impl mixed event = nil, --- Call this function to hook signal.event signals to the current backend. -- For LÖVE, this means overriding every existing LÖVE callback. If a callback is already defined, the new one will call the old function along with the signal:emit. - -- @impl backend - registerEvents = () end + -- @impl love + registerEvents = () + local callbacks = { -- everything except run, errorhandler, threaderror + "displayrotated", "draw", "load", "lowmemory", "quit", "update", + "directorydropped", "filedropped", "focus", "mousefocus", "resize", "visible", + "keypressed", "keyreleased", "textedited", "textinput", + "mousemoved", "mousepressed", "mousereleased", "wheelmoved", + "gamepadaxis", "gamepadpressed", "gamepadreleased", + "joystickadded", "joystickaxis", "joystickhat", "joystickpressed", "joystickreleased", "joystickremoved", + "touchmoved", "touchpressed", "touchreleased" + } + local event = signal.event + for _, callback in ipairs(callbacks) do + if callback == "update" then + if love[callback] then + local old = love[callback] + love[callback] = function(dt) + old(dt) + event:emit(callback, dt) + end + else + love[callback] = function(dt) + event:emit(callback, dt) + end + end + else + if love[callback] then + local old = love[callback] + love[callback] = function(...) + old(...) + event:emit(callback, ...) + end + else + love[callback] = function(...) + event:emit(callback, ...) + end + end + end + end + end } signal.event = signal.new() diff --git a/timer/timer.lua b/timer/timer.lua index 963fba2..ea3ec2a 100644 --- a/timer/timer.lua +++ b/timer/timer.lua @@ -133,7 +133,6 @@ local timer_mt = { --- Update the timer. -- Should be called at every game update. -- @tparam number dt the delta-time (time spent since last time the function was called) (miliseconds) - -- @impl ubiquitousse update = function(self, dt) local t = self.t if not t.dead then @@ -199,7 +198,6 @@ local timer_mt = { -- You shouldn't need to worry about this if your timer belongs to a registry. -- If you don't use registries, you probably should purge dead timers to free up some memory (dead timers don't do anything otherwise). -- @treturn bool true if the timer can be discarded, false if it's still active. - -- @impl ubiquitousse dead = function(self) return self.t.dead end @@ -211,7 +209,6 @@ local registry_mt = { --- Update all the timers in the registry. -- Should be called at every game update; called by ubiquitousse.update. -- @tparam number dt the delta-time (time spent since last time the function was called) (miliseconds) - -- @impl ubiquitousse update = function(self, dt) -- process timers for _, timer in ipairs(self.timers) do @@ -228,7 +225,6 @@ local registry_mt = { --- Create a new timer and add it to the registry. -- Same as timer_module.run, but add it to the registry. - -- @impl ubiquitousse run = function(self, func) local r = timer_module.run(func) table.insert(self.timers, r) @@ -237,7 +233,6 @@ local registry_mt = { --- Create a new tween timer and add it to the registry. -- Same as timer_module.tween, but add it to the registry. - -- @impl ubiquitousse tween = function(self, duration, tbl, to, method) local r = timer_module.tween(duration, tbl, to, method) table.insert(self.timers, r) @@ -245,7 +240,6 @@ local registry_mt = { end, --- Cancels all the running timers in this registry. - -- @impl ubiquitousse clear = function(self) self.timers = {} end @@ -258,7 +252,6 @@ timer_module = { -- A timer registry provides an easy way to handle your timers; it will keep track of them, -- updating and removing them as needed. If you use the scene system, a scene-specific -- timer registry is available at ubiquitousse.scene.current.timer. - -- @impl ubiquitousse new = function() return setmetatable({ --- Used to store all the functions delayed with ubiquitousse.time.delay @@ -277,7 +270,6 @@ timer_module = { -- don't want to handle your timers manually. -- @tparam[opt] function func the function to schedule -- @treturn timer the object - -- @impl ubiquitousse run = function(func) local r = setmetatable({ t = { @@ -316,7 +308,6 @@ timer_module = { -- @tparam table to the new values -- @tparam[opt="linear"] string/function method tweening method (string name or the actual function(time, start, change, duration)) -- @treturn timer the object. A duration is already defined, and the :chain methods takes the same arguments as tween (and creates a tween). - -- @impl ubiquitousse tween = function(duration, tbl, to, method) method = method or "linear" method = type(method) == "string" and ease[method] or method