diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml new file mode 100644 index 0000000..52d47ba --- /dev/null +++ b/.github/workflows/ci_workflow.yml @@ -0,0 +1,23 @@ +name: Run tests + +on: [push] + +jobs: + build_and_run: + name: Build and run tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + lfs: true + - uses: actions/setup-java@v1 + with: + java-version: '17' + + - name: Build && Run + run: | + deployer_url="https://raw.githubusercontent.com/Insality/defold-deployer/4/deployer.sh" + curl -s ${deployer_url} | bash -s lbd --headless --settings ./test/test.ini + + - name: Upload test report + run: bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 394e59f..0dbbab7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ Thumbs.db manifest.public.der manifest.private.der + +deployer_version_settings.txt + +.deployer_cache +dist +deployer_build_stats.csv \ No newline at end of file diff --git a/.luacov b/.luacov new file mode 100644 index 0000000..79df7a7 --- /dev/null +++ b/.luacov @@ -0,0 +1,73 @@ +local reporter = require("luacov.reporter.defold") + +--- Default values for configuration options. +-- For project specific configuration create '.luacov' file in your project +-- folder. It should be a Lua script setting various options as globals +-- or returning table of options. +-- @class module +-- @name deftest.coverage.configuration +return { + + --- Reporter class to use when creating a report. Default: DefaultReporter from reporter.lua + reporter = reporter, + + --- Filename to store collected stats. Default: "luacov.stats.out". + statsfile = "luacov.stats.out", + + --- Filename to store report. Default: "luacov.report.out". + reportfile = "luacov.report.out", + + --- Enable saving coverage data after every `savestepsize` lines? + -- Setting this flag to `true` in config is equivalent to running LuaCov + -- using `luacov.tick` module. Default: false. + tick = false, + + --- Stats file updating frequency for `luacov.tick`. + -- The lower this value - the more frequently results will be written out to the stats file. + -- You may want to reduce this value (to, for example, 2) to avoid losing coverage data in + -- case your program may terminate without triggering luacov exit hooks that are supposed + -- to save the data. Default: 100. + savestepsize = 100, + + --- Run reporter on completion? Default: true. + runreport = true, + + --- Delete stats file after reporting? Default: false. + deletestats = true, + + --- Process Lua code loaded from raw strings? + -- That is, when the 'source' field in the debug info + -- does not start with '@'. Default: true. + codefromstrings = true, + + --- Lua patterns for files to include when reporting. + -- All will be included if nothing is listed. + -- Do not include the '.lua' extension. Path separator is always '/'. + -- Overruled by `exclude`. + -- @usage + -- include = { + -- "mymodule$", -- the main module + -- "mymodule%/.+$", -- and everything namespaced underneath it + -- } + include = {}, + + --- Lua patterns for files to exclude when reporting. + -- Nothing will be excluded if nothing is listed. + -- Do not include the '.lua' extension. Path separator is always '/'. + -- Overrules `include`. + exclude = { "^test%/.+$" }, + + --- Table mapping names of modules to be included to their filenames. + -- Has no effect if empty. + -- Real filenames mentioned here will be used for reporting + -- even if the modules have been installed elsewhere. + -- Module name can contain '*' wildcard to match groups of modules, + -- in this case corresponding path will be used as a prefix directory + -- where modules from the group are located. + -- @usage + -- modules = { + -- ["some_rock"] = "src/some_rock.lua", + -- ["some_rock.*"] = "src" + -- } + modules = {}, +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f77ddf1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "Lua.diagnostics.globals": [ + "tweener", + "describe", + "it", + "sys", + "init", + "timer", + "socket", + "go", + "before", + "gui", + "vmath" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index ae3f4e3..8d4dc27 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ ![](media/logo.png) -[![Github-sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/insality) [![Ko-Fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/insality) [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/insality) - [![GitHub release (latest by date)](https://img.shields.io/github/v/tag/insality/defold-tweener?style=for-the-badge&label=Release)](https://github.com/Insality/defold-tweener/tags) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/insality/defold-tweener/ci-workflow.yml?branch=master&style=for-the-badge)](https://github.com/Insality/defold-tweener/actions) +[![codecov](https://img.shields.io/codecov/c/github/Insality/defold-tweener?style=for-the-badge)](https://codecov.io/gh/Insality/defold-tweener) + +[![Github-sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/insality) [![Ko-Fi](https://img.shields.io/badge/Ko--fi-F16061?style=for-the-badge&logo=ko-fi&logoColor=white)](https://ko-fi.com/insality) [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/insality) # Tweener @@ -22,10 +24,10 @@ Open your `game.project` file and add the following line to the dependencies field under the project section: -**[Tweener v2](https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip)** +**[Tweener](https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip)** ``` -https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip +https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip ``` ### Library Size @@ -34,8 +36,8 @@ https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip | Platform | Library Size | | ---------------- | ------------ | -| HTML5 | **2.38 KB** | -| Desktop / Mobile | **5.55 KB** | +| HTML5 | **3.28 KB** | +| Desktop / Mobile | **6.21 KB** | ### Global Update Frequency @@ -54,7 +56,7 @@ update_frequency = 60 ### Quick API Reference ```lua -local tween_function = tweener.linear or go.EASING_LINEAR or gui.EASING_LINEAR +local tween_function = tweener.linear or go.EASING_LINEAR or gui.EASING_LINEAR or {0, 0.2, 0.4, 0.8, 0.9, 1} tweener.tween(tween_function, from, to, time, callback, [dt]) tweener.ease(tween_function, from, to, time, time_elapsed) @@ -81,7 +83,7 @@ tweener.tween(tween_function, from, to, time, callback, [dt]) This function initiates a tween operation immediately. Here's how to use it: - **Parameters:** - - `tween_function`: The tween function. + - `tween_function`: The tween function. You can use one of the predefined easing functions or provide a custom one. - `from`: The starting value of the tween. - `to`: The ending value of the tween. - `time`: The duration of the tween, in seconds. @@ -94,9 +96,17 @@ This function initiates a tween operation immediately. Here's how to use it: - **Usage Example:** ```lua +tweener.tween(tweener.linear, 0, 100, 1.5, function(value, is_final_call) + print("Tween value: " .. value) +end) + tweener.tween(go.EASING_OUTSINE, 0, 100, 1.5, function(value, is_final_call) print("Tween value: " .. value) end) + +tweener.tween({0, 0.2, 0.4, 0.8, 0.9, 1}, 0, 100, 1.5, function(value, is_final_call) + print("Tween value: " .. value) +end) ``` @@ -120,6 +130,12 @@ This function calculates the value of the tween at a specific point in time, bas ```lua local value = tweener.ease(tweener.inquad, 0, 100, 1.5, 0.75) print("The tween value at halfway point is: ", value) + +local value = tweener.ease(gui.EASING_OUTSINE, 0, 100, 1.5, 0.75) +print("The tween value at halfway point is: ", value) + +local value = tweener.ease({0, 0.2, 0.4, 0.8, 0.9, 1}, 0, 100, 1.5, 0.75) +print("The tween value at halfway point is: ", value) ``` These functions provide a flexible and powerful way to add animations and transitions to your projects, making them feel more dynamic and engaging. Enjoy animating with the **Tweener** module! *(Thanks, Mister ChatGPT)* 🙃 @@ -237,6 +253,14 @@ If you have any issues, questions or suggestions please [create an issue](https: +### **V3** +
+ Changelog + + - Added custom easings support +
+ + ## ❤️ Support project ❤️ Your donation helps me stay engaged in creating valuable projects for **Defold**. If you appreciate what I'm doing, please consider supporting me! diff --git a/example/example.gui_script b/example/example.gui_script index 13c2497..bfef646 100644 --- a/example/example.gui_script +++ b/example/example.gui_script @@ -22,7 +22,8 @@ function init(self) local box_pos = gui.get_position(box) local current_time = socket.gettime() - tweener.tween(tweener.outsine, 75, 885, 2.4, function(value, is_final_call) + local custom_easing = {0, 0.4, 0.5, 0.6, 0.5, 0.4, 0.5, 0.6, 1} + tweener.tween(custom_easing, 75, 885, 2.4, function(value, is_final_call) box_pos.x = value gui.set_position(box, box_pos) diff --git a/game.project b/game.project index e932829..0b40c98 100644 --- a/game.project +++ b/game.project @@ -16,9 +16,11 @@ title = Defold Tweener version = 1 publisher = Insality developer = Maksim Tuprikov, Insality +dependencies#0 = https://github.com/britzl/deftest/archive/refs/tags/2.8.0.zip [library] include_dirs = tweener [tweener] update_frequency = 60 + diff --git a/media/logo.png b/media/logo.png index a0b93c5..855f143 100644 Binary files a/media/logo.png and b/media/logo.png differ diff --git a/settings_deployer b/settings_deployer new file mode 100644 index 0000000..07634c7 --- /dev/null +++ b/settings_deployer @@ -0,0 +1,17 @@ +# Path to bob folder. It will find and save new bob.jar files inside +bob_folder=./ + +# You can point bob version for project in format "filename:sha" +bob_sha="181:fd1ad4c17bfdcd890ea7176f2672c35102384419" + +# Select Defold channel. Values: stable, beta, alpha +bob_channel="stable" + +# If true, it will check and download latest bob version. It will ignore bob_sha param +use_latest_bob=false + +# Select Defold build server +build_server="https://build.defold.com" + +# Is need to build html report +is_build_html_report=true \ No newline at end of file diff --git a/test/test.collection b/test/test.collection new file mode 100644 index 0000000..fe2bf3f --- /dev/null +++ b/test/test.collection @@ -0,0 +1,39 @@ +name: "default" +scale_along_z: 0 +embedded_instances { + id: "test" + data: "components {\n" + " id: \"test\"\n" + " component: \"/test/test.gui\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/test/test.gui b/test/test.gui new file mode 100644 index 0000000..d0f87d1 --- /dev/null +++ b/test/test.gui @@ -0,0 +1,10 @@ +script: "/test/test.gui_script" +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/test/test.gui_script b/test/test.gui_script new file mode 100644 index 0000000..ac3f84f --- /dev/null +++ b/test/test.gui_script @@ -0,0 +1,8 @@ +local deftest = require("deftest.deftest") + +function init(self) + deftest.add(require("test.test_tweener")) + + local is_report = sys.get_config("test.report") == "1" + deftest.run({ coverage = { enabled = is_report } }) +end diff --git a/test/test.ini b/test/test.ini new file mode 100644 index 0000000..eeec6a9 --- /dev/null +++ b/test/test.ini @@ -0,0 +1,9 @@ +[bootstrap] +main_collection = /test/test.collectionc + +[display] +height = 256 +width = 256 + +[test] +report = 1 \ No newline at end of file diff --git a/test/test_tweener.lua b/test/test_tweener.lua new file mode 100644 index 0000000..828bcb2 --- /dev/null +++ b/test/test_tweener.lua @@ -0,0 +1,36 @@ +return function() + ---@type tweener + ---@diagnostic disable-next-line: missing-fields + local tweener = {} + + describe("Defold Tweener", function() + before(function() + tweener = require("tweener.tweener") + end) + + it("Tweener Ease", function() + assert(tweener.ease(go.EASING_LINEAR, 0, 100, 1, 0) == 0) + assert(tweener.ease(go.EASING_LINEAR, 0, 100, 1, 0.5) == 50) + assert(tweener.ease(go.EASING_LINEAR, 0, 100, 1, 1) == 100) + + assert(tweener.ease(go.EASING_INOUTQUAD, 0, 100, 1, 0) == 0) + assert(tweener.ease(go.EASING_INOUTQUAD, 0, 100, 1, 0.5) == 50) + assert(tweener.ease(go.EASING_INOUTQUAD, 0, 100, 1, 1) == 100) + end) + + it("Tweener Custom Ease", function() + local easing = {0, 1, 2, 1, 0} + + assert(tweener.ease(easing, 0, 100, 1, 0) == 0) + assert(tweener.ease(easing, 0, 100, 1, 0.25) == 100) + assert(tweener.ease(easing, 0, 100, 1, 0.5) == 200) + assert(tweener.ease(easing, 0, 100, 1, 0.75) == 100) + assert(tweener.ease(easing, 0, 100, 1, 1) == 0) + + -- Edge + assert(tweener.ease(easing, 0, 100, 1, -1) == 0) + assert(tweener.ease(easing, 0, 100, 1, 0) == 0) + assert(tweener.ease(easing, 0, 100, 1, 2) == 0) + end) + end) +end diff --git a/tweener/annotations_tweener.lua b/tweener/annotations_tweener.lua new file mode 100644 index 0000000..14296a2 --- /dev/null +++ b/tweener/annotations_tweener.lua @@ -0,0 +1,7 @@ +---Download Defold annotations from here: https://github.com/astrochili/defold-annotations/releases/ + +---@alias easing_function (fun(current: number, from: number, to: number, time: number): number)|constant|number[] + +---@class tweener +---@field tween fun(easing_function: easing_function, from: number, to: number, time: number, callback: fun(value: number, is_end: boolean), update_delta_time: number|nil): hash +---@field ease fun(easing_function: easing_function, from: number, to: number, time: number, time_elapsed: number): number \ No newline at end of file diff --git a/tweener/tweener.lua b/tweener/tweener.lua index 88c4217..bc01a1d 100644 --- a/tweener/tweener.lua +++ b/tweener/tweener.lua @@ -1,9 +1,48 @@ ----@alias easing_function (fun(current: number, from: number, to: number, time: number): number)|constant - local UPDATE_FREQUENCY = sys.get_config_int("tweener.UPDATE_FREQUENCY", 60) local M = {} +---@class hash +---@class constant +---@class vector + +local TYPE_TABLE = "table" +local TYPE_USERDATA = "userdata" + + +---@param easing number[]|vector @The array of easing values, Example: {0, 0.5, 1, 2, 1}. Must have at least two elements. +---@return easing_function @The easing function +local function get_custom_easing(easing) + local sample_count = #easing + if sample_count < 2 then + error("Easing table must have at least two elements.") + end + + return function(t, b, c, d) + if d == 0 then + error("Division by zero: 'd' must not be zero.") + end + + local time_progress = t / d + if time_progress >= 1 then + return (c + b) * easing[sample_count] + end + if time_progress <= 0 then + return b * easing[1] + end + + local sample_index = sample_count - 1 + local index1 = math.floor(time_progress * sample_index) + local index2 = math.min(index1 + 1, sample_index) + + local diff = (time_progress - index1 * (1 / sample_index)) * sample_index + local progress = easing[index1 + 1] * (1.0 - diff) + easing[index2 + 1] * diff + + return c * progress + b + end +end + + ---@param easing_function easing_function ---@param from number ---@param to number @@ -13,7 +52,13 @@ local M = {} ---@return hash @The created timer id function M.tween(easing_function, from, to, time, callback, update_delta_time) update_delta_time = update_delta_time or (1 / UPDATE_FREQUENCY) + + -- Acquire the easing function easing_function = M.DEFOLD_EASINGS[easing_function] or easing_function + local easing_type = type(easing_function) + if easing_type == TYPE_USERDATA or easing_type == TYPE_TABLE then + easing_function = get_custom_easing(easing_function --[[@as vector]]) + end local time_elapsed = 0 local latest_time = socket.gettime() @@ -48,6 +93,10 @@ function M.ease(easing_function, from, to, time, time_elapsed) end easing_function = M.DEFOLD_EASINGS[easing_function] or easing_function + local easing_type = type(easing_function) + if easing_type == TYPE_USERDATA or easing_type == TYPE_TABLE then + easing_function = get_custom_easing(easing_function --[[@as vector]]) + end return easing_function(time_elapsed, from, to - from, time) end