tween API

This commit is contained in:
Face
2025-08-10 13:52:27 +03:00
parent dbd1e7e6ac
commit 6fdf422f7e
5 changed files with 923 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ TODO:
Issues:
1. **< br />** counts as 1 element in **WebsiteContainer**, therefore despite being (0,0) in size, it counts as double in spacing
2. **Tween** API doesn't modify CSS, it operates independently at Godot level.
Notes:
- **< input />** is sort-of inline in normal web. We render it as a block element (new-line).

View File

@@ -797,6 +797,272 @@ var HTML_CONTENT_ADD_REMOVE = """<head>
</body>
""".to_utf8_buffer()
var HTML_CONTENT_TWEEN = """<head>
<title>Tween Animation Demo</title>
<icon src="https://cdn-icons-png.flaticon.com/512/1828/1828774.png">
<meta name="theme-color" content="#1a202c">
<meta name="description" content="Demonstrating Lua Tween animations">
<style>
body { bg-[#0f1629] text-[#ffffff] p-6 font-sans }
h1 { text-[#60a5fa] text-4xl font-bold text-center mb-6 }
h2 { text-[#a78bfa] text-2xl font-bold mt-8 mb-4 }
.container { bg-[#1e293b] p-6 rounded-lg shadow-lg mb-6 }
.demo-box {
bg-[#3b82f6]
w-[100px]
h-[100px]
rounded-lg
flex
items-center
justify-center
text-[#ffffff]
font-bold
cursor-pointer
mb-4
}
.control-panel {
flex
flex-row
gap-2
flex-wrap
justify-center
mt-4
}
button {
bg-[#059669]
text-[#ffffff]
px-4
py-2
rounded
hover:bg-[#047857]
active:bg-[#065f46]
cursor-pointer
}
.reset-btn { bg-[#dc2626] hover:bg-[#b91c1c] active:bg-[#991b1b] }
.warning { text-[#fbbf24] bg-[#451a03] p-4 rounded border-l-4 border-[#f59e0b] }
</style>
<script>
-- Get references to all demo elements
local fadeBox = gurt.select('#fade-box')
local colorBox = gurt.select('#color-box')
local scaleBox = gurt.select('#scale-box')
local rotateBox = gurt.select('#rotate-box')
local moveBox = gurt.select('#move-box')
local comboBox = gurt.select('#combo-box')
gurt.log('Tween animation demo started!')
-- Fade Animation
gurt.select('#fade-btn'):on('click', function()
fadeBox:createTween():to('opacity', 0):duration(1.0):easing('out'):transition('linear'):play()
end)
gurt.select('#fade-restore-btn'):on('click', function()
fadeBox:createTween():to('opacity', 1):duration(0.5):easing('in'):transition('linear'):play()
end)
-- Color Animation
gurt.select('#color-btn'):on('click', function()
colorBox:createTween():to('backgroundColor', '#ff0000'):duration(1.0):easing('inout'):transition('quad'):play()
end)
gurt.select('#color-restore-btn'):on('click', function()
colorBox:createTween():to('backgroundColor', '#3b82f6'):duration(1.0):easing('inout'):transition('quad'):play()
end)
-- Scale Animation
gurt.select('#scale-btn'):on('click', function()
scaleBox:createTween():to('scale', 1.5):duration(1.0):easing('out'):transition('cubic'):play()
end)
gurt.select('#scale-restore-btn'):on('click', function()
scaleBox:createTween():to('scale', 1.0):duration(0.8):easing('in'):transition('cubic'):play()
end)
-- Rotation Animation
gurt.select('#rotate-btn'):on('click', function()
rotateBox:createTween():to('rotation', 360):duration(2.0):easing('inout'):transition('sine'):play()
end)
gurt.select('#rotate-restore-btn'):on('click', function()
rotateBox:createTween():to('rotation', 0):duration(1.0):easing('out'):transition('sine'):play()
end)
-- Movement Animation
gurt.select('#move-btn'):on('click', function()
moveBox:createTween():to('x', 200):duration(1.5):easing('out'):transition('elastic'):play()
end)
gurt.select('#move-restore-btn'):on('click', function()
moveBox:createTween():to('x', 0):duration(1.0):easing('in'):transition('elastic'):play()
end)
-- Complex Parallel Animation
gurt.select('#combo-btn'):on('click', function()
local tween = comboBox:createTween()
tween:parallel()
tween:to('scale', 1.3):duration(1.0):easing('out'):transition('back')
tween:to('rotation', 180):duration(1.0):easing('out'):transition('sine')
tween:to('backgroundColor', '#10b981'):duration(1.0):easing('out'):transition('linear')
tween:play()
end)
-- Complex Chain Animation
gurt.select('#chain-btn'):on('click', function()
local tween = comboBox:createTween()
tween:chain()
tween:to('x', 150):duration(0.8):easing('out'):transition('bounce')
tween:to('y', -50):duration(0.8):easing('out'):transition('bounce')
tween:to('x', 0):duration(0.8):easing('out'):transition('bounce')
tween:to('y', 0):duration(0.8):easing('out'):transition('bounce')
tween:play()
end)
-- Reset all animations
gurt.select('#reset-all-btn'):on('click', function()
-- Reset all properties to default
fadeBox:createTween():to('opacity', 1):duration(0.3):play()
colorBox:createTween():to('backgroundColor', '#3b82f6'):duration(0.3):play()
scaleBox:createTween():to('scale', 1.0):duration(0.3):play()
rotateBox:createTween():to('rotation', 0):duration(0.3):play()
moveBox:createTween():to('x', 0):duration(0.3):play()
local combo_tween = comboBox:createTween()
combo_tween:parallel()
combo_tween:to('scale', 1.0):duration(0.3)
combo_tween:to('rotation', 0):duration(0.3)
combo_tween:to('backgroundColor', '#3b82f6'):duration(0.3)
combo_tween:to('x', 0):duration(0.3)
combo_tween:to('y', 0):duration(0.3)
combo_tween:play()
end)
-- Callback example
gurt.select('#callback-btn'):on('click', function()
fadeBox:createTween():to('opacity', 0.3):duration(1.0):easing('inout'):callback(function()
gurt.log('Fade animation completed!')
end):play()
end)
-- Loops example
gurt.select('#steps-btn'):on('click', function()
scaleBox:createTween():loops(3)
:to('scale', 2.0):duration(0.5)
:to('scale', 1.0):duration(0.5)
:callback(function()
print('Animation completed!')
end):play()
end)
</script>
</head>
<body>
<h1>🎬 Lua Tween Animation Demo</h1>
<div style="warning">
<p><strong>Note:</strong> This demo showcases the Lua Tween API for smooth animations. Each box demonstrates different animation properties and easing functions.</p>
</div>
<h2>Fade Animation</h2>
<div style="container">
<div id="fade-box" style="demo-box">Fade Me</div>
<div style="control-panel">
<button id="fade-btn">Fade Out</button>
<button id="fade-restore-btn">Fade In</button>
<button id="callback-btn">Fade with Callback</button>
</div>
</div>
<h2>Color Animation</h2>
<div style="container">
<div id="color-box" style="demo-box">Change Color</div>
<div style="control-panel">
<button id="color-btn">To Red</button>
<button id="color-restore-btn">To Blue</button>
</div>
</div>
<h2>Scale Animation</h2>
<div style="container">
<div id="scale-box" style="demo-box">Scale Me</div>
<div style="control-panel">
<button id="scale-btn">Scale Up</button>
<button id="scale-restore-btn">Scale Down</button>
<button id="steps-btn">Pulse (Loop)</button>
</div>
</div>
<h2>Rotation Animation</h2>
<div style="container">
<div id="rotate-box" style="demo-box">Spin Me</div>
<div style="control-panel">
<button id="rotate-btn">Rotate 360°</button>
<button id="rotate-restore-btn">Reset Rotation</button>
</div>
</div>
<h2>Movement Animation</h2>
<div style="container">
<div id="move-box" style="demo-box">Move Me</div>
<div style="control-panel">
<button id="move-btn">Move Right</button>
<button id="move-restore-btn">Move Back</button>
</div>
</div>
<h2>Complex Animations</h2>
<div style="container">
<div id="combo-box" style="demo-box">Complex</div>
<div style="control-panel">
<button id="combo-btn">Parallel Animation</button>
<button id="chain-btn">Chain Animation</button>
</div>
</div>
<div style="control-panel mt-8">
<button id="reset-all-btn" style="reset-btn">🔄 Reset All Animations</button>
</div>
<div style="container mt-8">
<h2>Available Tween Methods:</h2>
<ul style="list-disc pl-6 text-[#d1d5db]">
<li><strong>element:createTween()</strong> - Create a new tween for the element</li>
<li><strong>tween:to(property, target_value, duration, easing)</strong> - Animate to a target value</li>
<li><strong>tween:from(property, start_value, duration, easing)</strong> - Animate from a start value</li>
<li><strong>tween:parallel()</strong> - Set tween to run animations in parallel</li>
<li><strong>tween:chain()</strong> - Set tween to run animations in sequence</li>
<li><strong>tween:steps(count)</strong> - Set number of loops</li>
<li><strong>tween:callback(function)</strong> - Execute function when tween completes</li>
<li><strong>tween:play()</strong> - Start the tween animation</li>
<li><strong>tween:stop()</strong> - Stop the tween animation</li>
<li><strong>tween:pause()</strong> - Pause the tween animation</li>
<li><strong>tween:resume()</strong> - Resume the tween animation</li>
</ul>
<h2 style="mt-4">Supported Properties:</h2>
<ul style="list-disc pl-6 text-[#d1d5db]">
<li><strong>opacity</strong> - Element transparency (0-1)</li>
<li><strong>backgroundColor</strong> - Background color (hex/color names)</li>
<li><strong>color/textColor</strong> - Text color (hex/color names)</li>
<li><strong>width/height</strong> - Element dimensions (pixels)</li>
<li><strong>x/y</strong> - Position coordinates (pixels)</li>
<li><strong>scale</strong> - Element scale factor</li>
<li><strong>rotation</strong> - Rotation angle (degrees)</li>
</ul>
<h2 style="mt-4">Easing Functions:</h2>
<ul style="list-disc pl-6 text-[#d1d5db]">
<li><strong>easeIn</strong> - Slow start, fast end</li>
<li><strong>easeOut</strong> - Fast start, slow end</li>
<li><strong>easeInOut</strong> - Slow start and end, fast middle</li>
<li><strong>easeOutIn</strong> - Fast start and end, slow middle</li>
</ul>
</div>
</body>
""".to_utf8_buffer()
var HTML_CONTENT_DOM_MANIPULATION = """
<head>
<title>DOM Utilities Test</title>
@@ -2256,3 +2522,7 @@ var HTML_CONTENT = """<head>
</div>
</body>
""".to_utf8_buffer()
# Set the active HTML content to use the tween demo
func _ready():
HTML_CONTENT = HTML_CONTENT_TWEEN

View File

@@ -507,6 +507,9 @@ static func add_element_methods(vm: LuauVM, lua_api: LuaAPI) -> void:
vm.lua_pushcallable(LuaDOMUtils._element_set_attribute_wrapper, "element.setAttribute")
vm.lua_setfield(-2, "setAttribute")
vm.lua_pushcallable(LuaDOMUtils._element_create_tween_wrapper, "element.createTween")
vm.lua_setfield(-2, "createTween")
_add_classlist_support(vm, lua_api)
vm.lua_newtable()
@@ -1016,6 +1019,14 @@ static func _element_newindex_wrapper(vm: LuauVM) -> int:
vm.lua_rawset(1)
return 0
static func _element_create_tween_wrapper(vm: LuauVM) -> int:
var lua_api = vm.get_meta("lua_api") as LuaAPI
if not lua_api:
vm.lua_pushnil()
return 1
return LuaTweenUtils.create_element_tween(vm, lua_api)
static func _unsubscribe_wrapper(vm: LuauVM) -> int:
# Get subscription ID from the subscription table
vm.luaL_checktype(1, vm.LUA_TTABLE)

View File

@@ -0,0 +1,640 @@
class_name LuaTweenUtils
extends RefCounted
class LuaTweenInstance:
var tween: Tween
var target_element: HTMLParser.HTMLElement
var target_dom_node: Node
var lua_api: LuaAPI
var dom_parser: HTMLParser
var tween_chain: Array = []
var is_parallel: bool = false
var callback_function: Callable
var default_duration: float = 1.0
var default_easing: String = "easeInOut"
var default_transition: String = "transLinear"
var steps_count: int = 1
var current_tween_config: Dictionary = {}
func _init(element: HTMLParser.HTMLElement, dom_node: Node, api: LuaAPI, parser: HTMLParser):
target_element = element
target_dom_node = dom_node
lua_api = api
dom_parser = parser
# Don't create tween here - will be created on main thread when needed
tween = null
is_parallel = false
static func create_element_tween(vm: LuauVM, lua_api: LuaAPI) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
var element = lua_api.dom_parser.find_by_id(element_id)
if not element:
vm.lua_pushnil()
return 1
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
if not dom_node:
vm.lua_pushnil()
return 1
var tween_instance = LuaTweenInstance.new(element, dom_node, lua_api, lua_api.dom_parser)
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func add_tween_methods(vm: LuauVM):
# Animation methods
vm.lua_pushcallable(_tween_to_wrapper, "tween.to")
vm.lua_setfield(-2, "to")
vm.lua_pushcallable(_tween_from_wrapper, "tween.from")
vm.lua_setfield(-2, "from")
# Configuration methods
vm.lua_pushcallable(_tween_duration_wrapper, "tween.duration")
vm.lua_setfield(-2, "duration")
vm.lua_pushcallable(_tween_easing_wrapper, "tween.easing")
vm.lua_setfield(-2, "easing")
vm.lua_pushcallable(_tween_transition_wrapper, "tween.transition")
vm.lua_setfield(-2, "transition")
# Control methods
vm.lua_pushcallable(_tween_play_wrapper, "tween.play")
vm.lua_setfield(-2, "play")
vm.lua_pushcallable(_tween_parallel_wrapper, "tween.parallel")
vm.lua_setfield(-2, "parallel")
vm.lua_pushcallable(_tween_chain_wrapper, "tween.chain")
vm.lua_setfield(-2, "chain")
vm.lua_pushcallable(_tween_loops_wrapper, "tween.loops")
vm.lua_setfield(-2, "loops")
vm.lua_pushcallable(_tween_callback_wrapper, "tween.callback")
vm.lua_setfield(-2, "callback")
# Playback control
vm.lua_pushcallable(_tween_stop_wrapper, "tween.stop")
vm.lua_setfield(-2, "stop")
vm.lua_pushcallable(_tween_pause_wrapper, "tween.pause")
vm.lua_setfield(-2, "pause")
vm.lua_pushcallable(_tween_resume_wrapper, "tween.resume")
vm.lua_setfield(-2, "resume")
# Kill method
vm.lua_pushcallable(_tween_kill_wrapper, "tween.kill")
vm.lua_setfield(-2, "kill")
static func _tween_to_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var property: String = vm.luaL_checkstring(2)
var target_value = vm.lua_tovariant(3)
var tween_op = {
"type": "to",
"property": property,
"target_value": target_value,
"duration": tween_instance.default_duration,
"easing": tween_instance.default_easing,
"transition": tween_instance.default_transition
}
tween_instance.tween_chain.append(tween_op)
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_from_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var property: String = vm.luaL_checkstring(2)
var start_value = vm.lua_tovariant(3)
tween_instance.current_tween_config = {
"type": "from",
"property": property,
"start_value": start_value,
"duration": tween_instance.default_duration,
"easing": tween_instance.default_easing,
"transition": tween_instance.default_transition
}
tween_instance.tween_chain.append(tween_instance.current_tween_config)
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
# Re-add all tween methods to the new table
add_tween_methods(vm)
return 1
static func _tween_duration_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var duration: float = vm.luaL_checknumber(2)
if tween_instance.tween_chain.size() > 0:
tween_instance.tween_chain[-1]["duration"] = duration
else:
tween_instance.default_duration = duration
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_easing_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var easing: String = vm.luaL_checkstring(2)
if tween_instance.tween_chain.size() > 0:
tween_instance.tween_chain[-1]["easing"] = easing
else:
tween_instance.default_easing = easing
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_transition_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var transition: String = vm.luaL_checkstring(2)
if tween_instance.tween_chain.size() > 0:
tween_instance.tween_chain[-1]["transition"] = transition
else:
tween_instance.default_transition = transition
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_play_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
if not is_instance_valid(tween_instance.target_dom_node):
return 0
var operations_to_execute = tween_instance.tween_chain.duplicate()
var is_parallel = tween_instance.is_parallel
var steps_count = tween_instance.steps_count
var callback_func = tween_instance.callback_function
# Clear chain after copying
tween_instance.tween_chain.clear()
_execute_tween_on_main_thread.call_deferred(tween_instance.target_dom_node, operations_to_execute, is_parallel, callback_func, steps_count)
return 0
static func _tween_parallel_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
tween_instance.is_parallel = true
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_chain_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
tween_instance.is_parallel = false
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_loops_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
var loops: int = vm.luaL_checkint(2)
# Store loops count for execution
tween_instance.steps_count = loops
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_callback_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
vm.luaL_checktype(2, vm.LUA_TFUNCTION)
var callback_ref = vm.lua_ref(2) # Reference function at index 2
tween_instance.callback_function = LuaTweenUtils.execute_lua_callback.bind(vm, callback_ref)
vm.lua_newtable()
vm.lua_pushobject(tween_instance)
vm.lua_setfield(-2, "_tween_instance")
add_tween_methods(vm)
return 1
static func _tween_stop_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
if tween_instance.tween and is_instance_valid(tween_instance.tween):
tween_instance.tween.stop()
return 0
static func _tween_pause_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
if tween_instance.tween and is_instance_valid(tween_instance.tween):
tween_instance.tween.pause()
return 0
static func _tween_resume_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
if tween_instance.tween and is_instance_valid(tween_instance.tween):
tween_instance.tween.play()
return 0
static func _tween_kill_wrapper(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
vm.lua_getfield(1, "_tween_instance")
var tween_instance = vm.lua_toobject(-1) as LuaTweenInstance
vm.lua_pop(1)
if not tween_instance:
return 0
if tween_instance.tween and is_instance_valid(tween_instance.tween):
tween_instance.tween.kill()
return 0
static func parse_easing(easing_name: String) -> Tween.EaseType:
match easing_name.to_lower():
"in", "easein":
return Tween.EASE_IN
"out", "easeout":
return Tween.EASE_OUT
"inout", "easeinout":
return Tween.EASE_IN_OUT
"outin", "easeoutin":
return Tween.EASE_OUT_IN
_:
return Tween.EASE_IN_OUT
static func parse_transition(transition_name: String) -> Tween.TransitionType:
match transition_name.to_lower():
"linear", "translinear":
return Tween.TRANS_LINEAR
"quad", "transquad":
return Tween.TRANS_QUAD
"cubic", "transcubic":
return Tween.TRANS_CUBIC
"quart", "transquart":
return Tween.TRANS_QUART
"quint", "transquint":
return Tween.TRANS_QUINT
"sine", "transsine":
return Tween.TRANS_SINE
"expo", "transexpo":
return Tween.TRANS_EXPO
"circ", "transcirc":
return Tween.TRANS_CIRC
"elastic", "transelastic":
return Tween.TRANS_ELASTIC
"back", "transback":
return Tween.TRANS_BACK
"bounce", "transbounce":
return Tween.TRANS_BOUNCE
_:
return Tween.TRANS_LINEAR
# Main thread execution of tween operations
static func _execute_tween_on_main_thread(dom_node: Node, operations: Array, is_parallel: bool, callback_func: Callable = Callable(), steps: int = 1):
if not is_instance_valid(dom_node):
return
var tween = dom_node.create_tween()
if not tween:
return
tween.set_parallel(is_parallel)
if steps > 1:
tween.set_loops(steps)
if callback_func.is_valid():
tween.finished.connect(callback_func, CONNECT_ONE_SHOT)
for i in range(operations.size()):
var tween_op = operations[i]
execute_single_tween_operation(dom_node, tween, tween_op)
static func execute_single_tween_operation(dom_node: Node, tween: Tween, tween_op: Dictionary):
var property = tween_op.property
var duration = tween_op.duration
var easing = parse_easing(tween_op.get("easing", "easeInOut"))
var transition = parse_transition(tween_op.get("transition", "transLinear"))
match tween_op.type:
"to":
var target_value = tween_op.target_value
animate_property_direct(dom_node, tween, property, target_value, duration, easing, transition)
"from":
var start_value = tween_op.start_value
var current_value = get_property_value_direct(dom_node, property)
set_property_value_direct(dom_node, property, start_value)
animate_property_direct(dom_node, tween, property, current_value, duration, easing, transition)
static func animate_property_direct(dom_node: Node, tween: Tween, property: String, target_value, duration: float, easing: Tween.EaseType, transition: Tween.TransitionType = Tween.TRANS_LINEAR):
match property:
"opacity":
var alpha_value = float(target_value)
if dom_node is Control:
var tweener = tween.tween_property(dom_node, "modulate:a", alpha_value, duration)
tweener.set_ease(easing).set_trans(transition)
"backgroundColor":
var color_value = ColorUtils.parse_color(target_value)
if dom_node is PanelContainer:
var style_box = dom_node.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
tween.tween_property(style_box, "bg_color", color_value, duration).set_ease(easing).set_trans(transition)
return
elif dom_node is MarginContainer:
var panel_child = null
for child in dom_node.get_children():
if child is PanelContainer:
panel_child = child
break
if panel_child:
var style_box = panel_child.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
tween.tween_property(style_box, "bg_color", color_value, duration).set_ease(easing).set_trans(transition)
return
tween.tween_property(dom_node, "modulate", color_value, duration).set_ease(easing).set_trans(transition)
# Try Control as fallback
elif dom_node is Control:
tween.tween_property(dom_node, "modulate", color_value, duration).set_ease(easing).set_trans(transition)
"color", "textColor":
var color_value = ColorUtils.parse_color(target_value)
if dom_node is RichTextLabel:
tween.tween_property(dom_node, "modulate", color_value, duration).set_ease(easing).set_trans(transition)
elif dom_node.has_method("get_node_or_null"):
var rtl = dom_node.get_node_or_null("RichTextLabel")
if rtl:
tween.tween_property(rtl, "modulate", color_value, duration).set_ease(easing).set_trans(transition)
"width":
var width_value = float(target_value)
if dom_node is Control:
tween.tween_property(dom_node, "custom_minimum_size:x", width_value, duration).set_ease(easing).set_trans(transition)
"height":
var height_value = float(target_value)
if dom_node is Control:
tween.tween_property(dom_node, "custom_minimum_size:y", height_value, duration).set_ease(easing).set_trans(transition)
"x", "position.x":
var x_value = float(target_value)
if dom_node is Control:
tween.tween_property(dom_node, "position:x", x_value, duration).set_ease(easing).set_trans(transition)
"y", "position.y":
var y_value = float(target_value)
if dom_node is Control:
tween.tween_property(dom_node, "position:y", y_value, duration).set_ease(easing).set_trans(transition)
"scale":
var scale_value = float(target_value)
if dom_node is Control:
var scale_vec = Vector2(scale_value, scale_value)
tween.tween_property(dom_node, "scale", scale_vec, duration).set_ease(easing).set_trans(transition)
"rotation":
var rotation_value = deg_to_rad(float(target_value))
if dom_node is Control:
tween.tween_property(dom_node, "rotation", rotation_value, duration).set_ease(easing).set_trans(transition)
static func set_property_value_direct(dom_node: Node, property: String, value):
match property:
"opacity":
if dom_node is Control:
dom_node.modulate.a = float(value)
"backgroundColor":
var color_value = ColorUtils.parse_color(value)
if dom_node is PanelContainer:
var style_box = dom_node.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
style_box.bg_color = color_value
elif dom_node is MarginContainer:
var panel_child = null
for child in dom_node.get_children():
if child is PanelContainer:
panel_child = child
break
if panel_child:
var style_box = panel_child.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
style_box.bg_color = color_value
else:
dom_node.modulate = color_value
elif dom_node is Control:
dom_node.modulate = color_value
"color", "textColor":
var color_value = ColorUtils.parse_color(value)
if dom_node is RichTextLabel:
dom_node.modulate = color_value
elif dom_node.has_method("get_node_or_null"):
var rtl = dom_node.get_node_or_null("RichTextLabel")
if rtl:
rtl.modulate = color_value
"width":
var width_value = float(value)
if dom_node is Control:
dom_node.custom_minimum_size.x = width_value
"height":
var height_value = float(value)
if dom_node is Control:
dom_node.custom_minimum_size.y = height_value
"x", "position.x":
if dom_node is Control:
dom_node.position.x = float(value)
"y", "position.y":
if dom_node is Control:
dom_node.position.y = float(value)
"scale":
var scale_value = float(value)
if dom_node is Control:
var scale_vec = Vector2(scale_value, scale_value)
dom_node.scale = scale_vec
"rotation":
var rotation_value = deg_to_rad(float(value))
if dom_node is Control:
dom_node.rotation = rotation_value
static func get_property_value_direct(dom_node: Node, property: String):
match property:
"opacity":
if dom_node is Control:
return dom_node.modulate.a
"backgroundColor":
if dom_node is PanelContainer:
var style_box = dom_node.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
return style_box.bg_color
elif dom_node is MarginContainer:
var panel_child = null
for child in dom_node.get_children():
if child is PanelContainer:
panel_child = child
break
if panel_child:
var style_box = panel_child.get_theme_stylebox("panel")
if style_box and style_box is StyleBoxFlat:
return style_box.bg_color
else:
return dom_node.modulate
elif dom_node is Control:
return dom_node.modulate
"color", "textColor":
if dom_node is RichTextLabel:
return dom_node.modulate
elif dom_node.has_method("get_node_or_null"):
var rtl = dom_node.get_node_or_null("RichTextLabel")
if rtl:
return rtl.modulate
"width":
if dom_node is Control:
return dom_node.custom_minimum_size.x
"height":
if dom_node is Control:
return dom_node.custom_minimum_size.y
"x", "position.x":
if dom_node is Control:
return dom_node.position.x
"y", "position.y":
if dom_node is Control:
return dom_node.position.y
"scale":
if dom_node is Control:
return dom_node.scale.x
"rotation":
if dom_node is Control:
return rad_to_deg(dom_node.rotation)
return 0
static func execute_lua_callback(vm: LuauVM, callback_ref: int):
if not vm or not is_instance_valid(vm):
return
vm.lua_getref(callback_ref)
if vm.lua_isfunction(-1):
var result = vm.lua_pcall(0, 0, 0)
if result != vm.LUA_OK:
vm.lua_pop(1)
else:
vm.lua_pop(1)
vm.lua_unref(callback_ref)

View File

@@ -0,0 +1 @@
uid://dj2h7hrqd0v0u