Berry animation meter (#24193)
* Remove tab from json * Berry animation meter and other optimizations
This commit is contained in:
parent
5f7cb57ffb
commit
8feff1148a
@ -1,67 +0,0 @@
|
||||
# Template animation for Cylon like eye
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
param period type time default 5s
|
||||
param acending type bool default true
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving from right to left
|
||||
animation shutter_rl_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_lr_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
0xFC0000 # Red
|
||||
0xFF8000 # Orange
|
||||
0xFFFF00 # Yellow
|
||||
0x00FF00 # Green
|
||||
0x00FFFF # Cyan
|
||||
0x0080FF # Blue
|
||||
0x8000FF # Violet
|
||||
0xCCCCCC # White
|
||||
0xFC0000 # Red - need to add the first color at last position to ensure roll-over
|
||||
]
|
||||
|
||||
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
|
||||
run main
|
||||
@ -1,77 +0,0 @@
|
||||
# Template animation with flags
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
param period default 2s
|
||||
param ascending type bool default true # define to true to enable 'ascending' part
|
||||
param descending type bool default true # define to true to enable 'descending' part
|
||||
|
||||
# since 'strip_length()' is a value provider, it must be assigned to a variable before being used
|
||||
set strip_len = strip_length()
|
||||
|
||||
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
|
||||
|
||||
# define two rotating palettes, shifted by one color
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
|
||||
|
||||
# shutter moving in ascending
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving in descending
|
||||
animation shutter_rl_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# this is the overall sequence composed of two sub-sequences
|
||||
# the first in ascending mode, the second in descending
|
||||
sequence shutter_seq repeat forever {
|
||||
if ascending { # conditional execution: run only if 'ascending' is true
|
||||
repeat col1.palette_size times { # run the shutter animation
|
||||
restart shutter_size # resync all times for this animation, to avoid temporal drift
|
||||
play shutter_lr_animation for period # run the animation
|
||||
col1.next = 1 # then move to next color for both palettes
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
if descending { # conditional execution: run only if 'descending' is true
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
0xFC0000 # Red
|
||||
0xFF8000 # Orange
|
||||
0xFFFF00 # Yellow
|
||||
0x00FF00 # Green
|
||||
0x00FFFF # Cyan
|
||||
0x0080FF # Blue
|
||||
0x8000FF # Violet
|
||||
0xCCCCCC # White
|
||||
]
|
||||
|
||||
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
|
||||
run main
|
||||
@ -1,164 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: chap_5_21_template_shutter_bidir.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Template animation for Cylon like eye
|
||||
# Template animation class: shutter_bidir
|
||||
class shutter_bidir_animation : animation.engine_proxy
|
||||
static var PARAMS = animation.enc_params({
|
||||
"colors": {"type": "palette"},
|
||||
"period": {"type": "time", "default": 5000},
|
||||
"acending": {"type": "bool", "default": true}
|
||||
})
|
||||
|
||||
# Template setup method - overrides EngineProxy placeholder
|
||||
def setup_template()
|
||||
var engine = self # using 'self' as a proxy to engine object (instead of 'self.engine')
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var shutter_size_ = (def (engine)
|
||||
var provider = animation.sawtooth(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = strip_len_
|
||||
provider.duration = animation.create_closure_value(engine, def (engine) return self.period end)
|
||||
return provider
|
||||
end)(engine)
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
# shutter moving from left to right
|
||||
var shutter_lr_animation_ = animation.beacon_animation(engine)
|
||||
shutter_lr_animation_.color = col2_
|
||||
shutter_lr_animation_.back_color = col1_
|
||||
shutter_lr_animation_.pos = 0
|
||||
shutter_lr_animation_.beacon_size = shutter_size_
|
||||
shutter_lr_animation_.slew_size = 0
|
||||
shutter_lr_animation_.priority = 5
|
||||
# shutter moving from right to left
|
||||
var shutter_rl_animation_ = animation.beacon_animation(engine)
|
||||
shutter_rl_animation_.color = col1_
|
||||
shutter_rl_animation_.back_color = col2_
|
||||
shutter_rl_animation_.pos = 0
|
||||
shutter_rl_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
|
||||
shutter_rl_animation_.slew_size = 0
|
||||
shutter_rl_animation_.priority = 5
|
||||
var shutter_seq_ = animation.sequence_manager(engine, -1)
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_lr_animation_, def (engine) return self.period end)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_rl_animation_, def (engine) return self.period end)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
self.add(shutter_seq_)
|
||||
end
|
||||
end
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFC0000" # Red
|
||||
"FFFF8000" # Orange
|
||||
"FFFFFF00" # Yellow
|
||||
"FF00FF00" # Green
|
||||
"FF00FFFF" # Cyan
|
||||
"FF0080FF" # Blue
|
||||
"FF8000FF" # Violet
|
||||
"FFCCCCCC" # White
|
||||
"FFFC0000" # Red - need to add the first color at last position to ensure roll-over
|
||||
)
|
||||
var main_ = shutter_bidir_animation(engine)
|
||||
main_.colors = rainbow_with_white_
|
||||
main_.period = 1500
|
||||
engine.add(main_)
|
||||
engine.run()
|
||||
|
||||
# Compilation warnings:
|
||||
# Line 51: Template 'shutter_bidir' parameter 'acending' is declared but never used in the template body.
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Template animation for Cylon like eye
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
param period type time default 5s
|
||||
param acending type bool default true
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
# shutter moving from left to right
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving from right to left
|
||||
animation shutter_rl_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
sequence shutter_seq repeat forever {
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_lr_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
0xFC0000 # Red
|
||||
0xFF8000 # Orange
|
||||
0xFFFF00 # Yellow
|
||||
0x00FF00 # Green
|
||||
0x00FFFF # Cyan
|
||||
0x0080FF # Blue
|
||||
0x8000FF # Violet
|
||||
0xCCCCCC # White
|
||||
0xFC0000 # Red - need to add the first color at last position to ensure roll-over
|
||||
]
|
||||
|
||||
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
|
||||
run main
|
||||
|
||||
-#
|
||||
@ -1,182 +0,0 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: chap_5_22_template_shutter_bidir.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Template animation with flags
|
||||
# Template animation class: shutter_bidir
|
||||
class shutter_bidir_animation : animation.engine_proxy
|
||||
static var PARAMS = animation.enc_params({
|
||||
"colors": {"type": "palette"},
|
||||
"period": {"default": 2000},
|
||||
"ascending": {"type": "bool", "default": true},
|
||||
"descending": {"type": "bool", "default": true}
|
||||
})
|
||||
|
||||
# Template setup method - overrides EngineProxy placeholder
|
||||
def setup_template()
|
||||
var engine = self # using 'self' as a proxy to engine object (instead of 'self.engine')
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
|
||||
var shutter_size_ = (def (engine)
|
||||
var provider = animation.sawtooth(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = strip_len_
|
||||
provider.duration = animation.create_closure_value(engine, def (engine) return self.period end)
|
||||
return provider
|
||||
end)(engine)
|
||||
# define two rotating palettes, shifted by one color
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = animation.create_closure_value(engine, def (engine) return self.colors end)
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
|
||||
# shutter moving in ascending
|
||||
var shutter_lr_animation_ = animation.beacon_animation(engine)
|
||||
shutter_lr_animation_.color = col2_
|
||||
shutter_lr_animation_.back_color = col1_
|
||||
shutter_lr_animation_.pos = 0
|
||||
shutter_lr_animation_.beacon_size = shutter_size_
|
||||
shutter_lr_animation_.slew_size = 0
|
||||
shutter_lr_animation_.priority = 5
|
||||
# shutter moving in descending
|
||||
var shutter_rl_animation_ = animation.beacon_animation(engine)
|
||||
shutter_rl_animation_.color = col1_
|
||||
shutter_rl_animation_.back_color = col2_
|
||||
shutter_rl_animation_.pos = 0
|
||||
shutter_rl_animation_.beacon_size = animation.create_closure_value(engine, def (engine) return animation.resolve(strip_len_) - animation.resolve(shutter_size_) end)
|
||||
shutter_rl_animation_.slew_size = 0
|
||||
shutter_rl_animation_.priority = 5
|
||||
# this is the overall sequence composed of two sub-sequences
|
||||
# the first in ascending mode, the second in descending
|
||||
var shutter_seq_ = animation.sequence_manager(engine, -1)
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return bool(self.ascending) end)
|
||||
# conditional execution: run only if 'ascending' is true
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
|
||||
# run the shutter animation
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end) # resync all times for this animation, to avoid temporal drift
|
||||
.push_play_step(shutter_lr_animation_, def (engine) return self.period end) # run the animation
|
||||
.push_closure_step(def (engine) col1_.next = 1 end) # then move to next color for both palettes
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
)
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return bool(self.descending) end)
|
||||
# conditional execution: run only if 'descending' is true
|
||||
.push_repeat_subsequence(animation.sequence_manager(engine, def (engine) return col1_.palette_size end)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_rl_animation_, def (engine) return self.period end)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
)
|
||||
self.add(shutter_seq_)
|
||||
end
|
||||
end
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_with_white_ = bytes(
|
||||
"FFFC0000" # Red
|
||||
"FFFF8000" # Orange
|
||||
"FFFFFF00" # Yellow
|
||||
"FF00FF00" # Green
|
||||
"FF00FFFF" # Cyan
|
||||
"FF0080FF" # Blue
|
||||
"FF8000FF" # Violet
|
||||
"FFCCCCCC" # White
|
||||
)
|
||||
var main_ = shutter_bidir_animation(engine)
|
||||
main_.colors = rainbow_with_white_
|
||||
main_.period = 1500
|
||||
engine.add(main_)
|
||||
engine.run()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Template animation with flags
|
||||
|
||||
template animation shutter_bidir {
|
||||
param colors type palette
|
||||
param period default 2s
|
||||
param ascending type bool default true # define to true to enable 'ascending' part
|
||||
param descending type bool default true # define to true to enable 'descending' part
|
||||
|
||||
# since 'strip_length()' is a value provider, it must be assigned to a variable before being used
|
||||
set strip_len = strip_length()
|
||||
|
||||
# animated value for the size of the shutter, evolving linearly in time (sawtooth from 0% to 100%)
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = period)
|
||||
|
||||
# define two rotating palettes, shifted by one color
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1 # move 'col2' to the next color so it's shifte by one compared to 'col1'
|
||||
|
||||
# shutter moving in ascending
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# shutter moving in descending
|
||||
animation shutter_rl_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = strip_len - shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
# this is the overall sequence composed of two sub-sequences
|
||||
# the first in ascending mode, the second in descending
|
||||
sequence shutter_seq repeat forever {
|
||||
if ascending { # conditional execution: run only if 'ascending' is true
|
||||
repeat col1.palette_size times { # run the shutter animation
|
||||
restart shutter_size # resync all times for this animation, to avoid temporal drift
|
||||
play shutter_lr_animation for period # run the animation
|
||||
col1.next = 1 # then move to next color for both palettes
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
if descending { # conditional execution: run only if 'descending' is true
|
||||
repeat col1.palette_size times {
|
||||
restart shutter_size
|
||||
play shutter_rl_animation for period
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
0xFC0000 # Red
|
||||
0xFF8000 # Orange
|
||||
0xFFFF00 # Yellow
|
||||
0x00FF00 # Green
|
||||
0x00FFFF # Cyan
|
||||
0x0080FF # Blue
|
||||
0x8000FF # Violet
|
||||
0xCCCCCC # White
|
||||
]
|
||||
|
||||
animation main = shutter_bidir(colors = rainbow_with_white, period = 1.5s)
|
||||
run main
|
||||
|
||||
-#
|
||||
@ -61,42 +61,6 @@ SUCCESS
|
||||
SUCCESS
|
||||
```
|
||||
|
||||
## chap_5_21_template_shutter_bidir.anim
|
||||
|
||||
**Status:** ✅ Success
|
||||
|
||||
## Symbol Table
|
||||
|
||||
| Symbol | Type | Builtin | Dangerous | Takes Args |
|
||||
|----------------------|-----------------------|---------|-----------|------------|
|
||||
| `main` | animation | | | |
|
||||
| `rainbow_with_white` | palette | | | |
|
||||
| `shutter_bidir` | animation_constructor | | | ✓ |
|
||||
|
||||
### Compilation Output
|
||||
|
||||
```
|
||||
SUCCESS
|
||||
```
|
||||
|
||||
## chap_5_22_template_shutter_bidir.anim
|
||||
|
||||
**Status:** ✅ Success
|
||||
|
||||
## Symbol Table
|
||||
|
||||
| Symbol | Type | Builtin | Dangerous | Takes Args |
|
||||
|----------------------|-----------------------|---------|-----------|------------|
|
||||
| `main` | animation | | | |
|
||||
| `rainbow_with_white` | palette | | | |
|
||||
| `shutter_bidir` | animation_constructor | | | ✓ |
|
||||
|
||||
### Compilation Output
|
||||
|
||||
```
|
||||
SUCCESS
|
||||
```
|
||||
|
||||
## christmas_tree.anim
|
||||
|
||||
**Status:** ✅ Success
|
||||
@ -448,7 +412,7 @@ stack traceback:
|
||||
### Compilation Output
|
||||
|
||||
```
|
||||
dsl_compilation_error: Line 4: Transpilation failed: Line 4: Cannot redefine built-in symbol 'abs' (type: 4). Use a different name like 'abs_custom' or 'my_abs'
|
||||
dsl_compilation_error: Line 4: Transpilation failed: Line 4: Cannot redefine built-in symbol 'abs'. Use a different name like 'abs_custom' or 'my_abs'
|
||||
stack traceback:
|
||||
<unknown source>: in function `error`
|
||||
<unknown source>: in function `transpile`
|
||||
@ -1256,16 +1220,14 @@ SUCCESS
|
||||
|
||||
## Summary
|
||||
|
||||
- **Total files processed:** 51
|
||||
- **Successfully compiled:** 48
|
||||
- **Total files processed:** 49
|
||||
- **Successfully compiled:** 46
|
||||
- **Failed to compile:** 3
|
||||
|
||||
### Successful Files
|
||||
|
||||
- ✅ breathing_colors.anim
|
||||
- ✅ candy_cane.anim
|
||||
- ✅ chap_5_21_template_shutter_bidir.anim
|
||||
- ✅ chap_5_22_template_shutter_bidir.anim
|
||||
- ✅ christmas_tree.anim
|
||||
- ✅ comet_chase.anim
|
||||
- ✅ computed_values_demo.anim
|
||||
|
||||
@ -35,7 +35,7 @@ var rainbow_with_white_ = bytes(
|
||||
)
|
||||
# define a gradient across the whole strip
|
||||
var back_pattern_ = animation.palette_meter_animation(engine)
|
||||
back_pattern_.value_func = animation.create_closure_value(engine, def (engine) return animation.get_user_function('rand_meter')(engine) end)
|
||||
back_pattern_.level = animation.create_closure_value(engine, def (engine) return animation.get_user_function('rand_meter')(engine) end)
|
||||
engine.add(back_pattern_)
|
||||
engine.run()
|
||||
|
||||
@ -67,7 +67,7 @@ palette rainbow_with_white = [
|
||||
]
|
||||
|
||||
# define a gradient across the whole strip
|
||||
animation back_pattern = palette_meter_animation(value_func = rand_meter)
|
||||
animation back_pattern = palette_meter_animation(level = rand_meter())
|
||||
|
||||
run back_pattern
|
||||
|
||||
|
||||
@ -24,6 +24,6 @@ palette rainbow_with_white = [
|
||||
]
|
||||
|
||||
# define a gradient across the whole strip
|
||||
animation back_pattern = palette_meter_animation(value_func = rand_meter)
|
||||
animation back_pattern = palette_meter_animation(level = rand_meter())
|
||||
|
||||
run back_pattern
|
||||
|
||||
@ -1,32 +1,21 @@
|
||||
# Pattern of colors in the background based on palette, rotating over 5 s
|
||||
|
||||
berry """
|
||||
def rand_meter(time_ms, self)
|
||||
import math
|
||||
var r = math.rand() % 101
|
||||
return r
|
||||
end
|
||||
"""
|
||||
|
||||
extern function rand_meter
|
||||
# Vue-meter based on random data
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette rainbow_with_white = [
|
||||
0xFC0000 # Red
|
||||
0xFF8000 # Orange
|
||||
0xFFFF00 # Yellow
|
||||
0x00FF00 # Green
|
||||
0x00FFFF # Cyan
|
||||
0x0080FF # Blue
|
||||
0x8000FF # Violet
|
||||
0xCCCCCC # White
|
||||
0xFC0000 # Red - need to add the first color at last position to ensure roll-over
|
||||
palette vue_meter_palette = [
|
||||
( 0, 0x00FF00) # Green
|
||||
(143, 0x00FF00)
|
||||
|
||||
(164, 0xFFFF00) # Yellow
|
||||
(207, 0xFFFF00)
|
||||
|
||||
(228, 0xFF0000) # Red
|
||||
(255, 0xFF0000)
|
||||
]
|
||||
|
||||
# define a color attribute cycles color in space
|
||||
color rainbow_rich_color = rich_palette(palette=rainbow_with_white, cycle_period=0, transition_type=SINE)
|
||||
# define a color palette pattern for 'vue_meter_palette'
|
||||
color rainbow_rich_color = rich_palette(palette=vue_meter_palette, cycle_period=0, transition_type=LINEAR)
|
||||
|
||||
# define a gradient across the whole strip
|
||||
animation back_pattern = palette_meter_animation(color_source = rainbow_rich_color, value_func = rand_meter)
|
||||
# define a vue-meter based on all elements above
|
||||
animation back_pattern = palette_meter_animation(color_source = rainbow_rich_color, level = 85%)
|
||||
|
||||
run back_pattern
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
# Vue-meter based on random data
|
||||
|
||||
berry """
|
||||
# define a pseudo-random generator, returns value in range 0..255
|
||||
def rand_meter(engine)
|
||||
return (engine.time_ms * 2654435761) & 0xFF
|
||||
end
|
||||
"""
|
||||
|
||||
extern function rand_meter # declare the external function
|
||||
|
||||
# define a palette of rainbow colors including white with constant brightness
|
||||
palette vue_meter_palette = [
|
||||
( 0, 0x00FF00) # Green
|
||||
(143, 0x00FF00)
|
||||
|
||||
(164, 0xFFFF00) # Yellow
|
||||
(207, 0xFFFF00)
|
||||
|
||||
(228, 0xFF0000) # Red
|
||||
(255, 0xFF0000)
|
||||
]
|
||||
|
||||
# define a color palette pattern for 'vue_meter_palette'
|
||||
color rainbow_rich_color = rich_palette(palette=vue_meter_palette, cycle_period=0, transition_type=LINEAR)
|
||||
|
||||
# define a vue-meter based on all elements above
|
||||
animation back_pattern = palette_meter_animation(color_source = rainbow_rich_color, level = rand_meter())
|
||||
|
||||
run back_pattern
|
||||
@ -0,0 +1,17 @@
|
||||
# Sky
|
||||
|
||||
# Dark blue background
|
||||
color space_blue = 0x000066 # Note: opaque 0xFF alpha channel is implicitly added
|
||||
animation background = solid(color=space_blue)
|
||||
|
||||
# Add sparkle trail behind comets but on top of blue background
|
||||
animation stars = twinkle_animation(
|
||||
color=0xFFFFAA # Light blue sparkles
|
||||
density=8 # density (moderate sparkles)
|
||||
twinkle_speed=400ms # twinkle speed (quick sparkle)
|
||||
priority = 8
|
||||
)
|
||||
|
||||
# Start all animations
|
||||
run background
|
||||
run stars
|
||||
@ -23,7 +23,8 @@ ParameterizedObject (base class with parameter management and playable interface
|
||||
│ ├── CrenelPositionAnimation (crenel/square wave pattern)
|
||||
│ ├── BreatheAnimation (breathing effect)
|
||||
│ ├── PaletteGradientAnimation (gradient patterns with palette colors)
|
||||
│ │ └── PaletteMeterAnimation (meter/bar patterns)
|
||||
│ │ ├── PaletteMeterAnimation (meter/bar patterns)
|
||||
│ │ └── GradientMeterAnimation (VU meter with gradient colors and peak hold)
|
||||
│ ├── CometAnimation (moving comet with tail)
|
||||
│ ├── FireAnimation (realistic fire effect)
|
||||
│ ├── TwinkleAnimation (twinkling stars effect)
|
||||
@ -65,7 +66,7 @@ This unified base class enables:
|
||||
**Key Methods**:
|
||||
- `start(time_ms)` - Start the object at a specific time
|
||||
- `stop()` - Stop the object
|
||||
- `update(time_ms)` - Update object state based on current time
|
||||
- `update(time_ms)` - Update object state based on current time (no return value)
|
||||
|
||||
**Factory**: N/A (base class)
|
||||
|
||||
@ -75,7 +76,7 @@ Unified base class for all visual elements. Inherits from `ParameterizedObject`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `name` | string | "animation" | - | Optional name for the animation |
|
||||
| `id` | string | "animation" | - | Optional name for the animation |
|
||||
| `is_running` | bool | false | - | Whether the animation is active |
|
||||
| `priority` | int | 10 | 0-255 | Rendering priority (higher = on top) |
|
||||
| `duration` | int | 0 | min: 0 | Animation duration in ms (0 = infinite) |
|
||||
@ -158,7 +159,7 @@ Template animation parameters support all standard constraints:
|
||||
|
||||
**Implicit Parameters**:
|
||||
Template animations automatically inherit parameters from the `EngineProxy` class hierarchy without explicit declaration:
|
||||
- `name` (string, default: "animation") - Animation name
|
||||
- `id` (string, default: "animation") - Animation name
|
||||
- `priority` (int, default: 10) - Rendering priority
|
||||
- `duration` (int, default: 0) - Animation duration in milliseconds
|
||||
- `loop` (bool, default: false) - Whether animation loops
|
||||
@ -213,6 +214,8 @@ Base interface for all value providers. Inherits from `ParameterizedObject`.
|
||||
|
||||
**Timing Behavior**: For value providers, `start()` is typically not called because instances can be embedded in closures. Value providers consider the first call to `produce_value()` as the start of their internal time reference. The `start()` method only resets the time origin if the provider was already started previously (i.e., `self.start_time` is not nil).
|
||||
|
||||
**Update Method**: The `update(time_ms)` method does not return any value. Subclasses should check `self.is_running` to determine if the object is still active.
|
||||
|
||||
**Factory**: N/A (base interface)
|
||||
|
||||
### StaticValueProvider
|
||||
@ -611,7 +614,48 @@ Creates smooth color gradients that can be linear or radial. Inherits from `Anim
|
||||
|
||||
**Factories**: `animation.gradient_animation(engine)`, `animation.gradient_rainbow_linear(engine)`, `animation.gradient_rainbow_radial(engine)`, `animation.gradient_two_color_linear(engine)`
|
||||
|
||||
### GradientMeterAnimation
|
||||
|
||||
VU meter style animation that displays a gradient-colored bar from the start of the strip up to a configurable level. Includes optional peak hold indicator. Inherits from `PaletteGradientAnimation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `level` | int | 255 | 0-255 | Current meter level (0=empty, 255=full) |
|
||||
| `peak_hold` | int | 1000 | min: 0 | Peak hold time in ms (0=disabled) |
|
||||
| *(inherits all PaletteGradientAnimation parameters)* | | | | |
|
||||
|
||||
#### Visual Representation
|
||||
|
||||
```
|
||||
level=128 (50%), peak at 200
|
||||
[████████████████--------•-------]
|
||||
^ ^
|
||||
| peak indicator (single pixel)
|
||||
filled gradient area
|
||||
```
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Simple meter with rainbow gradient
|
||||
color rainbow = rich_palette()
|
||||
animation meter = gradient_meter_animation()
|
||||
meter.color_source = rainbow
|
||||
meter.level = 128
|
||||
|
||||
# Meter with peak hold (1 second)
|
||||
color fire_colors = rich_palette(palette=PALETTE_FIRE)
|
||||
animation vu_meter = gradient_meter_animation(peak_hold=1000)
|
||||
vu_meter.color_source = fire_colors
|
||||
|
||||
# Dynamic level from value provider
|
||||
set audio_level = triangle(min_value=0, max_value=255, period=2s)
|
||||
animation audio_meter = gradient_meter_animation(peak_hold=500)
|
||||
audio_meter.color_source = rainbow
|
||||
audio_meter.level = audio_level
|
||||
```
|
||||
|
||||
**Factory**: `animation.gradient_meter_animation(engine)`
|
||||
|
||||
### NoiseAnimation
|
||||
|
||||
|
||||
@ -38,18 +38,22 @@ class MyAnimation : animation.animation
|
||||
# Parameter validation is handled automatically by the framework
|
||||
end
|
||||
|
||||
def render(frame, time_ms)
|
||||
# Update animation state (no return value needed)
|
||||
def update(time_ms)
|
||||
super(self).update(time_ms)
|
||||
# Your update logic here
|
||||
end
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var param1 = self.my_param1
|
||||
var param2 = self.my_param2
|
||||
|
||||
# Use strip_length parameter instead of self.engine.strip_length for performance
|
||||
# Your rendering logic here
|
||||
# ...
|
||||
|
||||
@ -153,20 +157,16 @@ end
|
||||
The virtual parameter system automatically resolves ValueProviders when you access parameters:
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Virtual parameter access automatically resolves ValueProviders
|
||||
var color = self.color # Returns current color value, not the provider
|
||||
var position = self.pos # Returns current position value
|
||||
var size = self.size # Returns current size value
|
||||
|
||||
# Use strip_length parameter (computed once by engine_proxy) instead of self.engine.strip_length
|
||||
# Use resolved values in rendering logic
|
||||
for i: position..(position + size - 1)
|
||||
if i >= 0 && i < frame.width
|
||||
if i >= 0 && i < strip_length
|
||||
frame.set_pixel_color(i, color)
|
||||
end
|
||||
end
|
||||
@ -198,7 +198,7 @@ anim.pos = animation.triangle(0, 29, 3000)
|
||||
For performance-critical code, cache parameter values:
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Cache parameter values to avoid multiple virtual member access
|
||||
var current_color = self.color
|
||||
var current_pos = self.pos
|
||||
@ -206,7 +206,7 @@ def render(frame, time_ms)
|
||||
|
||||
# Use cached values in loops
|
||||
for i: current_pos..(current_pos + current_size - 1)
|
||||
if i >= 0 && i < frame.width
|
||||
if i >= 0 && i < strip_length
|
||||
frame.set_pixel_color(i, current_color)
|
||||
end
|
||||
end
|
||||
@ -389,24 +389,17 @@ end
|
||||
### Frame Buffer Operations
|
||||
|
||||
```berry
|
||||
def render(frame, time_ms)
|
||||
def render(frame, time_ms, strip_length)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Get frame dimensions
|
||||
var width = frame.width
|
||||
var height = frame.height # Usually 1 for LED strips
|
||||
|
||||
# Resolve dynamic parameters
|
||||
var color = self.resolve_value(self.color, "color", time_ms)
|
||||
var opacity = self.resolve_value(self.opacity, "opacity", time_ms)
|
||||
|
||||
# Render your effect
|
||||
for i: 0..(width-1)
|
||||
# Render your effect using strip_length parameter
|
||||
for i: 0..(strip_length-1)
|
||||
var pixel_color = calculate_pixel_color(i, time_ms)
|
||||
frame.set_pixel_color(i, pixel_color)
|
||||
end
|
||||
@ -487,19 +480,12 @@ class BeaconAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render the pulse to the provided frame buffer
|
||||
def render(frame, time_ms)
|
||||
def render(frame, time_ms, strip_length)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
var pixel_size = frame.width
|
||||
var pixel_size = strip_length
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
@ -636,7 +622,7 @@ def test_my_animation()
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(10)
|
||||
anim.start()
|
||||
var result = anim.render(frame, 1000)
|
||||
var result = anim.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
print("✓ All tests passed")
|
||||
|
||||
@ -1119,7 +1119,7 @@ Template animations automatically inherit parameters from the `engine_proxy` cla
|
||||
|
||||
```berry
|
||||
# These parameters are implicitly available in all template animations:
|
||||
param name type string default "animation"
|
||||
param id type string default "animation"
|
||||
param priority type int default 10
|
||||
param duration type int default 0
|
||||
param loop type bool default false
|
||||
|
||||
@ -163,7 +163,7 @@ _add_inherited_params_to_template(template_params_map)
|
||||
```
|
||||
|
||||
**Inherited Parameters (from Animation and ParameterizedObject):**
|
||||
- `name` (string, default: "animation")
|
||||
- `id` (string, default: "animation")
|
||||
- `priority` (int, default: 10)
|
||||
- `duration` (int, default: 0)
|
||||
- `loop` (bool, default: false)
|
||||
|
||||
@ -153,6 +153,8 @@ import "animations/twinkle" as twinkle_animation
|
||||
register_to_animation(twinkle_animation)
|
||||
import "animations/gradient" as gradient_animation
|
||||
register_to_animation(gradient_animation)
|
||||
import "animations/palette_meter" as palette_meter_animation
|
||||
register_to_animation(palette_meter_animation)
|
||||
import "animations/noise" as noise_animation
|
||||
register_to_animation(noise_animation)
|
||||
# import "animations/plasma" as plasma_animation
|
||||
|
||||
@ -34,13 +34,10 @@ class BeaconAnimation : animation.animation
|
||||
# Render the beacon to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var pixel_size = frame.width
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Use virtual parameter access - automatically resolves ValueProviders
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
@ -61,8 +58,8 @@ class BeaconAnimation : animation.animation
|
||||
if beacon_min < 0
|
||||
beacon_min = 0
|
||||
end
|
||||
if beacon_max >= pixel_size
|
||||
beacon_max = pixel_size
|
||||
if beacon_max >= strip_length
|
||||
beacon_max = strip_length
|
||||
end
|
||||
|
||||
# Draw the main beacon
|
||||
@ -83,8 +80,8 @@ class BeaconAnimation : animation.animation
|
||||
if left_slew_min < 0
|
||||
left_slew_min = 0
|
||||
end
|
||||
if left_slew_max >= pixel_size
|
||||
left_slew_max = pixel_size
|
||||
if left_slew_max >= strip_length
|
||||
left_slew_max = strip_length
|
||||
end
|
||||
|
||||
i = left_slew_min
|
||||
@ -103,8 +100,8 @@ class BeaconAnimation : animation.animation
|
||||
if right_slew_min < 0
|
||||
right_slew_min = 0
|
||||
end
|
||||
if right_slew_max >= pixel_size
|
||||
right_slew_max = pixel_size
|
||||
if right_slew_max >= strip_length
|
||||
right_slew_max = strip_length
|
||||
end
|
||||
|
||||
i = right_slew_min
|
||||
|
||||
@ -53,16 +53,7 @@ class CometAnimation : animation.animation
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Cache parameter values for performance (read once, use multiple times)
|
||||
var current_speed = self.speed
|
||||
var current_direction = self.direction
|
||||
@ -106,20 +97,15 @@ class CometAnimation : animation.animation
|
||||
self.direction = -current_direction
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the comet to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Get the integer position of the head (convert from 1/256th pixels to pixels)
|
||||
var head_pixel = self.head_position / 256
|
||||
|
||||
@ -129,7 +115,6 @@ class CometAnimation : animation.animation
|
||||
var direction = self.direction
|
||||
var wrap_around = self.wrap_around
|
||||
var fade_factor = self.fade_factor
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Extract color components from current color (ARGB format)
|
||||
var head_a = (current_color >> 24) & 0xFF
|
||||
|
||||
@ -37,18 +37,10 @@ class CrenelPositionAnimation : animation.animation
|
||||
# Render the crenel pattern to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var pixel_size = frame.width
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Access parameters via virtual members (automatically resolves ValueProviders)
|
||||
var back_color = self.back_color
|
||||
var pos = self.pos
|
||||
@ -87,7 +79,7 @@ class CrenelPositionAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render pulses
|
||||
while (pos < pixel_size) && (nb_pulse != 0)
|
||||
while (pos < strip_length) && (nb_pulse != 0)
|
||||
var i = 0
|
||||
if pos < 0
|
||||
i = -pos
|
||||
@ -95,7 +87,7 @@ class CrenelPositionAnimation : animation.animation
|
||||
# Invariant: pos + i >= 0
|
||||
|
||||
# Draw the pulse pixels
|
||||
while (i < pulse_size) && (pos + i < pixel_size)
|
||||
while (i < pulse_size) && (pos + i < strip_length)
|
||||
frame.set_pixel_color(pos + i, color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
@ -77,16 +77,7 @@ class FireAnimation : animation.animation
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Check if it's time to update the fire simulation
|
||||
# Update frequency is based on flicker_speed (Hz)
|
||||
var flicker_speed = self.flicker_speed # Cache parameter value
|
||||
@ -95,8 +86,6 @@ class FireAnimation : animation.animation
|
||||
self.last_update = time_ms
|
||||
self._update_fire_simulation(time_ms)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update the fire simulation
|
||||
@ -231,18 +220,10 @@ class FireAnimation : animation.animation
|
||||
# Render the fire to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Render each pixel with its current color
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
|
||||
@ -64,9 +64,7 @@ class GradientAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var movement_speed = self.movement_speed
|
||||
@ -83,8 +81,6 @@ class GradientAnimation : animation.animation
|
||||
|
||||
# Calculate gradient colors
|
||||
self._calculate_gradient(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate gradient colors for all pixels
|
||||
@ -197,15 +193,7 @@ class GradientAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render gradient to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < strip_length && i < frame.width
|
||||
if i < size(self.current_colors)
|
||||
|
||||
@ -182,9 +182,7 @@ class NoiseAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update time offset based on speed
|
||||
var current_speed = self.speed
|
||||
@ -199,8 +197,6 @@ class NoiseAnimation : animation.animation
|
||||
|
||||
# Calculate noise colors
|
||||
self._calculate_noise(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate noise colors for all pixels
|
||||
@ -230,15 +226,7 @@ class NoiseAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render noise to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
|
||||
148
lib/libesp32/berry_animation/src/animations/palette_meter.be
Normal file
148
lib/libesp32/berry_animation/src/animations/palette_meter.be
Normal file
@ -0,0 +1,148 @@
|
||||
# GradientMeterAnimation - VU meter style animation with palette gradient colors
|
||||
#
|
||||
# Displays a gradient-colored bar from the start of the strip up to a level (0-255).
|
||||
# Includes optional peak hold indicator that shows the maximum level for a configurable time.
|
||||
#
|
||||
# Visual representation:
|
||||
# level=128 (50%), peak at 200
|
||||
# [████████████████--------•-------]
|
||||
# ^ ^
|
||||
# | peak indicator (single pixel)
|
||||
# filled gradient area
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
#@ solidify:GradientMeterAnimation,weak
|
||||
class GradientMeterAnimation : animation.palette_gradient_animation
|
||||
# Instance variables for peak tracking
|
||||
var peak_level # Current peak level (0-255)
|
||||
var peak_time # Time when peak was set (ms)
|
||||
var _level # Cached value for 'self.level'
|
||||
|
||||
# Parameter definitions - extends PaletteGradientAnimation params
|
||||
static var PARAMS = animation.enc_params({
|
||||
# Inherited from PaletteGradientAnimation: color_source, shift_period, spatial_period, phase_shift
|
||||
# New meter-specific parameters
|
||||
"level": {"min": 0, "max": 255, "default": 255},
|
||||
"peak_hold": {"min": 0, "default": 1000} # 0 = disabled, >0 = hold time in ms
|
||||
})
|
||||
|
||||
# Initialize a new GradientMeterAnimation
|
||||
def init(engine)
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize peak tracking
|
||||
self.peak_level = 0
|
||||
self.peak_time = 0
|
||||
self._level = 0
|
||||
|
||||
# Override gradient defaults for meter use - static gradient
|
||||
self.shift_period = 0
|
||||
end
|
||||
|
||||
# Override update to handle peak tracking with absolute time
|
||||
def update(time_ms)
|
||||
var peak_hold = self.peak_hold
|
||||
|
||||
if peak_hold > 0
|
||||
var level = self.level
|
||||
self._level = level # cache value to be used in 'render()'
|
||||
var peak_level = self.peak_level
|
||||
# Update peak tracking using absolute time
|
||||
if level >= peak_level
|
||||
# New peak detected, or rearm current peak
|
||||
self.peak_level = level
|
||||
self.peak_time = time_ms
|
||||
elif peak_level > 0
|
||||
# Check if peak hold has expired
|
||||
var elapsed_since_peak = time_ms - self.peak_time
|
||||
if elapsed_since_peak > peak_hold
|
||||
# Peak hold expired, reset to current level
|
||||
self.peak_level = level
|
||||
self.peak_time = time_ms
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Call parent update (computes value_buffer with gradient values)
|
||||
super(self).update(time_ms)
|
||||
end
|
||||
|
||||
# Override render to only display filled pixels and peak indicator
|
||||
def render(frame, time_ms, strip_length)
|
||||
var color_source = self.get_param('color_source')
|
||||
if color_source == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var elapsed = time_ms - self.start_time
|
||||
var level = self._level # use cached value in 'update()'
|
||||
var peak_hold = self.peak_hold
|
||||
|
||||
# Calculate fill position (how many pixels to fill)
|
||||
var fill_pixels = tasmota.scale_uint(level, 0, 255, 0, strip_length)
|
||||
|
||||
# Calculate peak pixel position
|
||||
var peak_pixel = -1
|
||||
if peak_hold > 0 && self.peak_level > level
|
||||
peak_pixel = tasmota.scale_uint(self.peak_level, 0, 255, 0, strip_length) - 1
|
||||
end
|
||||
|
||||
|
||||
# Optimization for LUT patterns
|
||||
var lut
|
||||
if isinstance(color_source, animation.color_provider) && (lut := color_source.get_lut()) != nil
|
||||
var lut_factor = color_source.LUT_FACTOR # default = 1, we have only 128 cached values
|
||||
var lut_max = 256 >> lut_factor
|
||||
var i = 0
|
||||
var frame_ptr = frame.pixels._buffer()
|
||||
var lut_ptr = lut._buffer()
|
||||
var buffer = self.value_buffer._buffer()
|
||||
while (i < fill_pixels)
|
||||
var byte_value = buffer[i]
|
||||
var lut_index = byte_value >> lut_factor # Divide by 2 using bit shift
|
||||
if byte_value == 255
|
||||
lut_index = lut_max
|
||||
end
|
||||
|
||||
var lut_color_ptr = lut_ptr + (lut_index << 2) # calculate the pointer for LUT color
|
||||
frame_ptr[0] = lut_color_ptr[0]
|
||||
frame_ptr[1] = lut_color_ptr[1]
|
||||
frame_ptr[2] = lut_color_ptr[2]
|
||||
frame_ptr[3] = lut_color_ptr[3]
|
||||
|
||||
# advance to next
|
||||
i += 1
|
||||
frame_ptr += 4
|
||||
end
|
||||
else
|
||||
# Render only filled pixels and peak indicator (leave rest transparent)
|
||||
var i = 0
|
||||
while i < fill_pixels
|
||||
var byte_value = self.value_buffer[i]
|
||||
var color = color_source.get_color_for_value(byte_value, elapsed)
|
||||
frame.set_pixel_color(i, color)
|
||||
# Unfilled pixels stay transparent (not rendered)
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Do we need to show peak pixel?
|
||||
if peak_pixel >= fill_pixels
|
||||
var byte_value = self.value_buffer[peak_pixel]
|
||||
var color = color_source.get_color_for_value(byte_value, elapsed)
|
||||
frame.set_pixel_color(peak_pixel, color)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
var level = self.level
|
||||
var peak_hold = self.peak_hold
|
||||
return f"GradientMeterAnimation(level={level}, peak_hold={peak_hold}ms, peak={self.peak_level})"
|
||||
end
|
||||
end
|
||||
|
||||
return {'palette_meter_animation': GradientMeterAnimation}
|
||||
@ -9,6 +9,8 @@ import "./core/param_encoder" as encode_constraints
|
||||
#@ solidify:PaletteGradientAnimation,weak
|
||||
class PaletteGradientAnimation : animation.animation
|
||||
var value_buffer # Buffer to store values for each pixel (bytes object)
|
||||
var _spatial_period # Cached spatial_period for static pattern optimization
|
||||
var _phase_shift # Cached phase_shift for static pattern optimization
|
||||
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = animation.enc_params({
|
||||
@ -29,9 +31,6 @@ class PaletteGradientAnimation : animation.animation
|
||||
# Initialize non-parameter instance variables only
|
||||
self.value_buffer = bytes()
|
||||
|
||||
# Set default name
|
||||
self.name = "palette_gradient"
|
||||
|
||||
# Initialize value buffer with default frame width
|
||||
self._initialize_value_buffer()
|
||||
end
|
||||
@ -52,9 +51,23 @@ class PaletteGradientAnimation : animation.animation
|
||||
# Update the value buffer to generate gradient pattern
|
||||
def _update_value_buffer(time_ms, strip_length)
|
||||
# Cache parameter values for performance
|
||||
var shift_period = self.shift_period
|
||||
var spatial_period = self.spatial_period
|
||||
var phase_shift = self.phase_shift
|
||||
var shift_period = self.member("shift_period")
|
||||
var spatial_period = self.member("spatial_period")
|
||||
var phase_shift = self.member("phase_shift")
|
||||
|
||||
# Optimization: for static patterns (shift_period == 0), skip recomputation
|
||||
# if spatial_period, phase_shift, and strip_length haven't changed
|
||||
if shift_period == 0
|
||||
if self._spatial_period != nil &&
|
||||
self._spatial_period == spatial_period &&
|
||||
self._phase_shift == phase_shift &&
|
||||
size(self.value_buffer) == strip_length
|
||||
return # No changes, skip recomputation
|
||||
end
|
||||
# Update cached values
|
||||
self._spatial_period = spatial_period
|
||||
self._phase_shift = phase_shift
|
||||
end
|
||||
|
||||
# Determine effective spatial period (0 means full strip)
|
||||
var effective_spatial_period = spatial_period > 0 ? spatial_period : strip_length
|
||||
@ -97,16 +110,7 @@ class PaletteGradientAnimation : animation.animation
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
@ -119,31 +123,21 @@ class PaletteGradientAnimation : animation.animation
|
||||
|
||||
# Update the value buffer
|
||||
self._update_value_buffer(elapsed, strip_length)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the pattern to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to engine time)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Get current parameter values (cached for performance)
|
||||
var color_source = self.get_param('color_source') # use get_param to avoid resolving of color_provider
|
||||
if color_source == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
# Apply colors from the color source to each pixel based on its value
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Optimization for LUT patterns
|
||||
var lut
|
||||
if isinstance(color_source, animation.color_provider) && (lut := color_source.get_lut()) != nil
|
||||
@ -171,6 +165,8 @@ class PaletteGradientAnimation : animation.animation
|
||||
frame_ptr += 4
|
||||
end
|
||||
else # no LUT, do one color at a time
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
var i = 0
|
||||
while (i < strip_length)
|
||||
var byte_value = self.value_buffer[i]
|
||||
@ -202,54 +198,6 @@ class PaletteGradientAnimation : animation.animation
|
||||
end
|
||||
end
|
||||
|
||||
# Value meter pattern animation - creates meter/bar patterns based on a value function
|
||||
#@ solidify:PaletteMeterAnimation,weak
|
||||
class PaletteMeterAnimation : PaletteGradientAnimation
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = animation.enc_params({
|
||||
# Meter-specific parameters only
|
||||
"value_func": {"default": nil, "type": "function"}
|
||||
})
|
||||
|
||||
# Initialize a new meter pattern animation
|
||||
#
|
||||
# @param engine: AnimationEngine - Required animation engine reference
|
||||
def init(engine)
|
||||
# Call parent constructor
|
||||
super(self).init(engine)
|
||||
|
||||
# Set default name
|
||||
self.name = "palette_meter"
|
||||
end
|
||||
|
||||
# Override _update_value_buffer to generate meter pattern directly
|
||||
def _update_value_buffer(time_ms, strip_length)
|
||||
# Cache parameter values for performance
|
||||
var value_func = self.value_func
|
||||
if value_func == nil
|
||||
return
|
||||
end
|
||||
|
||||
# Cache engine reference to avoid dereferencing
|
||||
var engine = self.engine
|
||||
|
||||
# Get the current value
|
||||
var current_value = value_func(engine, time_ms, self)
|
||||
|
||||
# Calculate the meter position using scale_uint for better precision
|
||||
var meter_position = tasmota.scale_uint(current_value, 0, 255, 0, strip_length)
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Return 255 if pixel is within the meter, 0 otherwise
|
||||
self.value_buffer[i] = i < meter_position ? 255 : 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
'palette_gradient_animation': PaletteGradientAnimation,
|
||||
'palette_meter_animation': PaletteMeterAnimation
|
||||
'palette_gradient_animation': PaletteGradientAnimation
|
||||
}
|
||||
@ -29,9 +29,6 @@ class RichPaletteAnimation : animation.animation
|
||||
def init(engine)
|
||||
super(self).init(engine) # Initialize Animation base class
|
||||
|
||||
# Set default name (override inherited default)
|
||||
self.name = "rich_palette"
|
||||
|
||||
# Create internal RichPaletteColorProvider instance
|
||||
self.color_provider = animation.rich_palette(engine)
|
||||
|
||||
@ -71,7 +68,7 @@ class RichPaletteAnimation : animation.animation
|
||||
# String representation
|
||||
def tostring()
|
||||
try
|
||||
return f"RichPaletteAnimation({self.name}, cycle_period={self.cycle_period}, brightness={self.brightness})"
|
||||
return f"RichPaletteAnimation(cycle_period={self.cycle_period}, brightness={self.brightness})"
|
||||
except ..
|
||||
return "RichPaletteAnimation(uninitialized)"
|
||||
end
|
||||
|
||||
@ -10,8 +10,6 @@
|
||||
def solid(engine)
|
||||
# Create animation with engine-only constructor
|
||||
var anim = animation.animation(engine)
|
||||
anim.name = "solid"
|
||||
|
||||
return anim
|
||||
end
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ class TwinkleAnimation : animation.animation
|
||||
# NO instance variables for parameters - they are handled by the virtual parameter system
|
||||
|
||||
# Non-parameter instance variables only
|
||||
var twinkle_states # Array storing twinkle state for each pixel
|
||||
var current_colors # bytes() buffer storing ARGB colors (4 bytes per pixel)
|
||||
var last_update # Last update time for timing
|
||||
var random_seed # Seed for random number generation
|
||||
@ -33,32 +32,28 @@ class TwinkleAnimation : animation.animation
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.twinkle_states = []
|
||||
self.current_colors = bytes() # Use bytes() buffer for ARGB colors (4 bytes per pixel)
|
||||
self.last_update = 0
|
||||
|
||||
# Initialize random seed using engine time
|
||||
self.random_seed = self.engine.time_ms % 65536
|
||||
|
||||
# Initialize arrays based on strip length from engine
|
||||
# Initialize buffer based on strip length from engine
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
# Initialize arrays based on current strip length
|
||||
# Initialize buffer based on current strip length
|
||||
def _initialize_arrays()
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Resize arrays
|
||||
self.twinkle_states.resize(strip_length)
|
||||
|
||||
# Create new bytes() buffer for colors (4 bytes per pixel: ARGB)
|
||||
# Alpha channel serves as the active state: alpha=0 means off, alpha>0 means active
|
||||
self.current_colors.clear()
|
||||
self.current_colors.resize(strip_length * 4)
|
||||
|
||||
# Initialize all pixels to off state
|
||||
# Initialize all pixels to off state (transparent = alpha 0)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.twinkle_states[i] = 0 # 0 = off, >0 = brightness level
|
||||
self.current_colors.set(i * 4, 0x00000000, -4) # Transparent (alpha = 0)
|
||||
i += 1
|
||||
end
|
||||
@ -102,16 +97,7 @@ class TwinkleAnimation : animation.animation
|
||||
# Update animation state based on current time
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Call parent update method first
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
# Access parameters via virtual members
|
||||
var twinkle_speed = self.twinkle_speed
|
||||
|
||||
@ -122,8 +108,6 @@ class TwinkleAnimation : animation.animation
|
||||
self.last_update = time_ms
|
||||
self._update_twinkle_simulation(time_ms)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update the twinkle simulation with alpha-based fading
|
||||
@ -137,8 +121,8 @@ class TwinkleAnimation : animation.animation
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length || self.current_colors.size() != strip_length * 4
|
||||
# Ensure buffer is properly sized
|
||||
if self.current_colors.size() != strip_length * 4
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
@ -153,7 +137,6 @@ class TwinkleAnimation : animation.animation
|
||||
var fade_amount = tasmota.scale_uint(fade_speed, 0, 255, 1, 20)
|
||||
if alpha <= fade_amount
|
||||
# Star has faded completely - reset to transparent
|
||||
self.twinkle_states[i] = 0
|
||||
self.current_colors.set(i * 4, 0x00000000, -4)
|
||||
else
|
||||
# Reduce alpha while keeping RGB components unchanged
|
||||
@ -169,8 +152,11 @@ class TwinkleAnimation : animation.animation
|
||||
# For each pixel, check if it should twinkle based on density probability
|
||||
var j = 0
|
||||
while j < strip_length
|
||||
# Only consider pixels that are currently off (transparent)
|
||||
if self.twinkle_states[j] == 0
|
||||
# Only consider pixels that are currently off (alpha = 0)
|
||||
var current_color = self.current_colors.get(j * 4, -4)
|
||||
var alpha = (current_color >> 24) & 0xFF
|
||||
|
||||
if alpha == 0
|
||||
# Use density as probability out of 255
|
||||
if self._random_range(255) < density
|
||||
# Create new star at full brightness with random intensity alpha
|
||||
@ -185,7 +171,6 @@ class TwinkleAnimation : animation.animation
|
||||
var b = base_color & 0xFF
|
||||
|
||||
# Create new star with full-brightness color and variable alpha
|
||||
self.twinkle_states[j] = 1 # Mark as active (non-zero)
|
||||
self.current_colors.set(j * 4, (star_alpha << 24) | (r << 16) | (g << 8) | b, -4)
|
||||
end
|
||||
end
|
||||
@ -196,20 +181,12 @@ class TwinkleAnimation : animation.animation
|
||||
# Render the twinkle to the provided frame buffer
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Ensure arrays are properly sized
|
||||
if size(self.twinkle_states) != strip_length || self.current_colors.size() != strip_length * 4
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Ensure buffer is properly sized
|
||||
if self.current_colors.size() != strip_length * 4
|
||||
self._initialize_arrays()
|
||||
end
|
||||
|
||||
|
||||
@ -97,9 +97,7 @@ class WaveAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update time offset based on wave speed
|
||||
var current_wave_speed = self.wave_speed
|
||||
@ -114,8 +112,6 @@ class WaveAnimation : animation.animation
|
||||
|
||||
# Calculate wave colors
|
||||
self._calculate_wave(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate wave colors for all pixels
|
||||
@ -197,15 +193,7 @@ class WaveAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render wave to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width && i < self.current_colors.size()
|
||||
|
||||
@ -102,9 +102,7 @@ class BounceAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Initialize last_update_time on first update
|
||||
if self.last_update_time == 0
|
||||
@ -114,7 +112,7 @@ class BounceAnimation : animation.animation
|
||||
# Calculate time delta
|
||||
var dt = time_ms - self.last_update_time
|
||||
if dt <= 0
|
||||
return true
|
||||
return
|
||||
end
|
||||
self.last_update_time = time_ms
|
||||
|
||||
@ -132,8 +130,6 @@ class BounceAnimation : animation.animation
|
||||
|
||||
# Calculate bounced colors
|
||||
self._calculate_bounce()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update bounce physics
|
||||
@ -220,14 +216,9 @@ class BounceAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render bounce to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
var current_strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
@ -255,7 +246,6 @@ def bounce_basic(engine)
|
||||
bounce.bounce_range = 0 # full strip range
|
||||
bounce.damping = 250
|
||||
bounce.gravity = 0
|
||||
bounce.name = "bounce_basic"
|
||||
return bounce
|
||||
end
|
||||
|
||||
@ -269,7 +259,6 @@ def bounce_gravity(engine)
|
||||
bounce.bounce_range = 0 # full strip range
|
||||
bounce.damping = 240
|
||||
bounce.gravity = 128
|
||||
bounce.name = "bounce_gravity"
|
||||
return bounce
|
||||
end
|
||||
|
||||
@ -283,7 +272,6 @@ def bounce_constrained(engine)
|
||||
bounce.bounce_range = 15 # constrained range
|
||||
bounce.damping = 250
|
||||
bounce.gravity = 0
|
||||
bounce.name = "bounce_constrained"
|
||||
return bounce
|
||||
end
|
||||
|
||||
|
||||
@ -89,9 +89,7 @@ class JitterAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var jitter_frequency = self.jitter_frequency
|
||||
@ -116,8 +114,6 @@ class JitterAnimation : animation.animation
|
||||
|
||||
# Calculate jittered colors
|
||||
self._calculate_jitter()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update jitter offsets
|
||||
@ -237,17 +233,9 @@ class JitterAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render jitter to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var current_strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
|
||||
@ -100,9 +100,7 @@ class PlasmaAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update time phase based on speed
|
||||
var current_time_speed = self.time_speed
|
||||
@ -117,8 +115,6 @@ class PlasmaAnimation : animation.animation
|
||||
|
||||
# Calculate plasma colors
|
||||
self._calculate_plasma(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate plasma colors for all pixels
|
||||
@ -184,15 +180,7 @@ class PlasmaAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render plasma to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
@ -230,7 +218,6 @@ def plasma_rainbow(engine)
|
||||
# Use default rainbow color (nil triggers rainbow in on_param_changed)
|
||||
anim.color = nil
|
||||
anim.time_speed = 50
|
||||
anim.name = "plasma_rainbow"
|
||||
return anim
|
||||
end
|
||||
|
||||
@ -244,7 +231,6 @@ def plasma_fast(engine)
|
||||
anim.time_speed = 150
|
||||
anim.freq_x = 48
|
||||
anim.freq_y = 35
|
||||
anim.name = "plasma_fast"
|
||||
return anim
|
||||
end
|
||||
|
||||
|
||||
@ -69,9 +69,7 @@ class ScaleAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var current_scale_speed = self.scale_speed
|
||||
@ -98,8 +96,6 @@ class ScaleAnimation : animation.animation
|
||||
|
||||
# Calculate scaled colors
|
||||
self._calculate_scale()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate current scale factor based on mode
|
||||
@ -235,17 +231,9 @@ class ScaleAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render scale to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var current_strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
|
||||
@ -56,9 +56,7 @@ class ShiftAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var current_shift_speed = self.shift_speed
|
||||
@ -95,8 +93,6 @@ class ShiftAnimation : animation.animation
|
||||
|
||||
# Calculate shifted colors
|
||||
self._calculate_shift()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Calculate shifted colors for all pixels
|
||||
@ -152,17 +148,9 @@ class ShiftAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render shift to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var current_strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
|
||||
@ -94,21 +94,17 @@ class SparkleAnimation : animation.animation
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
if !super(self).update(time_ms)
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update at approximately 30 FPS
|
||||
var update_interval = 33 # ~30 FPS
|
||||
if time_ms - self.last_update < update_interval
|
||||
return true
|
||||
return
|
||||
end
|
||||
self.last_update = time_ms
|
||||
|
||||
# Update sparkle simulation
|
||||
self._update_sparkles(time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Update sparkle states and create new sparkles
|
||||
@ -198,17 +194,9 @@ class SparkleAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Render sparkles to frame buffer
|
||||
def render(frame, time_ms)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
var current_strip_length = self.engine.strip_length
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < current_strip_length
|
||||
while i < strip_length
|
||||
if i < frame.width
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
@ -241,7 +229,6 @@ end
|
||||
def sparkle_white(engine)
|
||||
var anim = animation.sparkle_animation(engine)
|
||||
anim.color = 0xFFFFFFFF # white sparkles
|
||||
anim.name = "sparkle_white"
|
||||
return anim
|
||||
end
|
||||
|
||||
@ -256,7 +243,6 @@ def sparkle_rainbow(engine)
|
||||
|
||||
var anim = animation.sparkle_animation(engine)
|
||||
anim.color = rainbow_provider
|
||||
anim.name = "sparkle_rainbow"
|
||||
return anim
|
||||
end
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ class Animation : animation.parameterized_object
|
||||
# Parameter definitions (extends Playable's PARAMS)
|
||||
static var PARAMS = animation.enc_params({
|
||||
# Inherited from Playable: is_running
|
||||
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
||||
"id": {"type": "string", "default": ""}, # Optional id for the animation
|
||||
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
||||
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
||||
"loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached
|
||||
@ -40,15 +40,7 @@ class Animation : animation.parameterized_object
|
||||
# This method should be called regularly by the animation engine
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
def update(time_ms)
|
||||
# do nothing if not running
|
||||
if (!self.is_running) return false end
|
||||
|
||||
# auto-fix time_ms and start_time
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
|
||||
|
||||
# Access parameters via virtual members
|
||||
var current_duration = self.duration
|
||||
|
||||
@ -66,12 +58,9 @@ class Animation : animation.parameterized_object
|
||||
# Animation completed, make it inactive
|
||||
# Set directly in values map to avoid triggering on_param_changed
|
||||
self.is_running = false
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the animation to the provided frame buffer
|
||||
@ -79,12 +68,11 @@ class Animation : animation.parameterized_object
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
if (!self.is_running) return false end
|
||||
|
||||
def render(frame, time_ms, strip_length)
|
||||
# Access parameters via virtual members (auto-resolves ValueProviders)
|
||||
var current_color = self.color
|
||||
var current_color = self.member("color")
|
||||
|
||||
# Fill the entire frame with the current color if not transparent
|
||||
if (current_color != 0x00000000)
|
||||
@ -98,7 +86,8 @@ class Animation : animation.parameterized_object
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
def post_render(frame, time_ms)
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
def post_render(frame, time_ms, strip_length)
|
||||
# no need to auto-fix time_ms and start_time
|
||||
# Handle opacity - can be number, frame buffer, or animation
|
||||
var current_opacity = self.opacity
|
||||
@ -109,7 +98,7 @@ class Animation : animation.parameterized_object
|
||||
frame.apply_opacity(frame.pixels, current_opacity)
|
||||
else
|
||||
# Opacity is a frame buffer
|
||||
self._apply_opacity(frame, current_opacity, time_ms)
|
||||
self._apply_opacity(frame, current_opacity, time_ms, strip_length)
|
||||
end
|
||||
end
|
||||
|
||||
@ -118,7 +107,8 @@ class Animation : animation.parameterized_object
|
||||
# @param frame: FrameBuffer - The frame buffer to apply opacity to
|
||||
# @param opacity: int|Animation - Opacity value or animation
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
def _apply_opacity(frame, opacity, time_ms)
|
||||
# @param strip_length: int - Length of the LED strip in pixels
|
||||
def _apply_opacity(frame, opacity, time_ms, strip_length)
|
||||
# Check if opacity is an animation instance
|
||||
if isinstance(opacity, animation.animation)
|
||||
# Animation mode: render opacity animation to frame buffer and use as mask
|
||||
@ -139,7 +129,7 @@ class Animation : animation.parameterized_object
|
||||
|
||||
# Update and render opacity animation
|
||||
opacity_animation.update(time_ms)
|
||||
opacity_animation.render(self.opacity_frame, time_ms)
|
||||
opacity_animation.render(self.opacity_frame, time_ms, strip_length)
|
||||
|
||||
# Use rendered frame buffer as opacity mask
|
||||
frame.apply_opacity(frame.pixels, self.opacity_frame.pixels)
|
||||
@ -166,7 +156,7 @@ class Animation : animation.parameterized_object
|
||||
|
||||
# String representation of the animation
|
||||
def tostring()
|
||||
return f"Animation({self.name}, priority={self.priority}, duration={self.duration}, loop={self.loop}, running={self.is_running})"
|
||||
return f"Animation(priority={self.priority}, duration={self.duration}, loop={self.loop}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -74,7 +74,6 @@ class AnimationEngine
|
||||
|
||||
# Create root EngineProxy to manage all children
|
||||
self.root_animation = animation.engine_proxy(self)
|
||||
self.root_animation.name = "root"
|
||||
|
||||
# Initialize state
|
||||
self.is_running = false
|
||||
@ -455,11 +454,11 @@ class AnimationEngine
|
||||
end
|
||||
|
||||
# Interrupt specific animation by name
|
||||
def interrupt_animation(name)
|
||||
def interrupt_animation(id)
|
||||
var i = 0
|
||||
while i < size(self.root_animation.children)
|
||||
var child = self.root_animation.children[i]
|
||||
if isinstance(child, animation.animation) && child.name != nil && child.name == name
|
||||
if isinstance(child, animation.animation) && child.id == id
|
||||
child.stop()
|
||||
self.root_animation.children.remove(i)
|
||||
return
|
||||
|
||||
@ -254,7 +254,14 @@ class EngineProxy : animation.animation
|
||||
idx += 1
|
||||
end
|
||||
|
||||
# Start all animations SECOND (they use values from providers and sequences)
|
||||
# Start all value providers SECOND (they provide dynamic values)
|
||||
idx = 0
|
||||
while idx < size(self.value_providers)
|
||||
self.value_providers[idx].start(time_ms)
|
||||
idx += 1
|
||||
end
|
||||
|
||||
# Start all animations THIRD (they use values from providers and sequences)
|
||||
idx = 0
|
||||
while idx < size(self.animations)
|
||||
self.animations[idx].start(time_ms)
|
||||
@ -306,24 +313,27 @@ class EngineProxy : animation.animation
|
||||
# Update the hybrid animation and all its children
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if still running, false if completed
|
||||
def update(time_ms)
|
||||
# Cache time for child access
|
||||
self.time_ms = time_ms
|
||||
self.strip_length = self.engine.strip_length
|
||||
self.time_ms = time_ms # We have 'self.time' attribute to mimick 'engine' behavior
|
||||
self.strip_length = self.engine.strip_length # We have 'self.strip_length' attribute to mimick 'engine' behavior
|
||||
|
||||
# Update parent animation state
|
||||
var still_running = super(self).update(time_ms)
|
||||
|
||||
if !still_running
|
||||
return false
|
||||
end
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Update all value providers FIRST (they may produce values used by sequences and animations)
|
||||
var idx = 0
|
||||
var sz = size(self.value_providers)
|
||||
while idx < sz
|
||||
self.value_providers[idx].update(time_ms)
|
||||
var vp = self.value_providers[idx]
|
||||
if vp.is_running
|
||||
# Set start time if needed
|
||||
if vp.start_time == nil
|
||||
vp.start_time = time_ms
|
||||
end
|
||||
# Call actual update
|
||||
vp.update(time_ms)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
@ -331,7 +341,15 @@ class EngineProxy : animation.animation
|
||||
idx = 0
|
||||
sz = size(self.sequences)
|
||||
while idx < sz
|
||||
self.sequences[idx].update(time_ms)
|
||||
var sq = self.sequences[idx]
|
||||
if sq.is_running
|
||||
# Set start time if needed
|
||||
if sq.start_time == nil
|
||||
sq.start_time = time_ms
|
||||
end
|
||||
# Call actual update
|
||||
sq.update(time_ms)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
@ -339,11 +357,17 @@ class EngineProxy : animation.animation
|
||||
idx = 0
|
||||
sz = size(self.animations)
|
||||
while idx < sz
|
||||
var child = self.animations[idx].update(time_ms)
|
||||
var an = self.animations[idx]
|
||||
if an.is_running
|
||||
# Set start time if needed
|
||||
if an.start_time == nil
|
||||
an.start_time = time_ms
|
||||
end
|
||||
# Call actual update
|
||||
an.update(time_ms)
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Render the hybrid animation
|
||||
@ -351,12 +375,18 @@ class EngineProxy : animation.animation
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to render to
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @param strip_length: int - Length of the LED strip in pixels (optional, defaults to self.strip_length)
|
||||
# @return bool - True if frame was modified, false otherwise
|
||||
def render(frame, time_ms)
|
||||
def render(frame, time_ms, strip_length)
|
||||
if !self.is_running || frame == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Use cached strip_length if not provided
|
||||
if strip_length == nil
|
||||
strip_length = self.strip_length
|
||||
end
|
||||
|
||||
# # update sequences first
|
||||
# var i = 0
|
||||
# while i < size(self.sequences)
|
||||
@ -367,7 +397,7 @@ class EngineProxy : animation.animation
|
||||
var modified = false
|
||||
|
||||
# We don't call super method for optimization, skipping color computation
|
||||
# modified = super(self).render(frame, time_ms)
|
||||
# modified = super(self).render(frame, time_ms, strip_length)
|
||||
|
||||
# Render all child animations (but not sequences - they don't render)
|
||||
var idx = 0
|
||||
@ -380,11 +410,11 @@ class EngineProxy : animation.animation
|
||||
self.temp_buffer.clear()
|
||||
|
||||
# Render child
|
||||
var child_rendered = child.render(self.temp_buffer, time_ms)
|
||||
var child_rendered = child.render(self.temp_buffer, time_ms, strip_length)
|
||||
|
||||
if child_rendered
|
||||
# Apply child's post-processing
|
||||
child.post_render(self.temp_buffer, time_ms)
|
||||
child.post_render(self.temp_buffer, time_ms, strip_length)
|
||||
|
||||
# Blend child into main frame
|
||||
frame.blend_pixels(frame.pixels, self.temp_buffer.pixels)
|
||||
@ -446,7 +476,7 @@ class EngineProxy : animation.animation
|
||||
|
||||
# String representation
|
||||
def tostring()
|
||||
return f"{classname(self)}({self.name}, animations={size(self.animations)}, sequences={size(self.sequences)}, value_providers={size(self.value_providers)}, running={self.is_running})"
|
||||
return f"{classname(self)}(animations={size(self.animations)}, sequences={size(self.sequences)}, value_providers={size(self.value_providers)}, running={self.is_running})"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -108,14 +108,16 @@ class ParameterizedObject
|
||||
# end
|
||||
# Check if it's a parameter (either set in values or defined in PARAMS)
|
||||
# Implement a fast-track if the value exists
|
||||
if self.values.contains(name)
|
||||
var value = self.values[name]
|
||||
|
||||
# main case, the value is numerical and present, so `find()` will get it in one search
|
||||
var value = self.values.find(name)
|
||||
if (value != nil) # in not nil, there is a value
|
||||
if type(value) != "instance"
|
||||
return value
|
||||
end
|
||||
|
||||
# Apply produce_value() if it' a ValueProvider
|
||||
return self.resolve_value(value, name, self.engine.time_ms)
|
||||
elif self.values.contains(name) # second case, nil is the actual value (and not returned because not found)
|
||||
return nil
|
||||
else
|
||||
# Return default if available from class hierarchy
|
||||
var encoded_constraints = self._get_param_def(name)
|
||||
@ -375,9 +377,6 @@ class ParameterizedObject
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
# if time_ms == nil
|
||||
# raise "value_error", "engine.time_ms should not be 'nil'"
|
||||
# end
|
||||
if self.start_time == nil
|
||||
self.start_time = time_ms
|
||||
end
|
||||
@ -428,10 +427,8 @@ class ParameterizedObject
|
||||
# Subclasses must override this to implement their update logic
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if object is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Default implementation just returns running state
|
||||
return self.is_running
|
||||
# Default implementation does nothing - subclasses override as needed
|
||||
end
|
||||
|
||||
# Method called when a parameter is changed
|
||||
|
||||
@ -177,10 +177,14 @@ class SequenceManager : animation.parameterized_object
|
||||
end
|
||||
|
||||
# Update sequence state - called from fast_loop
|
||||
# Returns true if still running, false if completed
|
||||
def update(current_time)
|
||||
if !self.is_running || size(self.steps) == 0
|
||||
return false
|
||||
return
|
||||
end
|
||||
|
||||
# Safety check: ensure step_index is valid
|
||||
if self.step_index >= size(self.steps)
|
||||
return
|
||||
end
|
||||
|
||||
var current_step = self.steps[self.step_index]
|
||||
@ -189,7 +193,8 @@ class SequenceManager : animation.parameterized_object
|
||||
if current_step["type"] == "subsequence"
|
||||
# Handle sub-sequence (including repeat sequences)
|
||||
var sub_seq = current_step["sequence_manager"]
|
||||
if !sub_seq.update(current_time)
|
||||
sub_seq.update(current_time)
|
||||
if !sub_seq.is_running
|
||||
# Sub-sequence finished, advance to next step
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
@ -221,8 +226,6 @@ class SequenceManager : animation.parameterized_object
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
end
|
||||
|
||||
return self.is_running
|
||||
end
|
||||
|
||||
# Execute the current step
|
||||
|
||||
@ -46,15 +46,10 @@ class SimpleDSLTranspiler
|
||||
self.instance_for_validation = instance_for_validation # nil by default
|
||||
end
|
||||
|
||||
# Check if this expression needs closure wrapping
|
||||
# Check if this expression needs closure/function wrapping
|
||||
def needs_closure()
|
||||
return self.has_dynamic
|
||||
end
|
||||
|
||||
# Check if this expression needs function wrapping
|
||||
def needs_function()
|
||||
return self.has_dynamic
|
||||
end
|
||||
|
||||
# String representation for debugging
|
||||
def tostring()
|
||||
@ -367,46 +362,6 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
# Transpile template body (similar to main transpile but without imports/engine start)
|
||||
def transpile_template_body()
|
||||
try
|
||||
# Process all statements in template body until we hit the closing brace
|
||||
var brace_depth = 0
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
|
||||
# Check for template end condition
|
||||
if tok != nil && tok.type == 27 #-animation_dsl.Token.RIGHT_BRACE-# && brace_depth == 0
|
||||
# This is the closing brace of the template - stop processing
|
||||
break
|
||||
end
|
||||
|
||||
# Track brace depth for nested braces
|
||||
if tok != nil && tok.type == 26 #-animation_dsl.Token.LEFT_BRACE-#
|
||||
brace_depth += 1
|
||||
elif tok != nil && tok.type == 27 #-animation_dsl.Token.RIGHT_BRACE-#
|
||||
brace_depth -= 1
|
||||
end
|
||||
|
||||
self.process_statement()
|
||||
end
|
||||
|
||||
# For templates, process run statements immediately instead of collecting them
|
||||
if size(self.run_statements) > 0
|
||||
for run_stmt : self.run_statements
|
||||
var obj_name = run_stmt["name"]
|
||||
var comment = run_stmt["comment"]
|
||||
# In templates, use underscore suffix for local variables
|
||||
self.add(f"engine.add({obj_name}_){comment}")
|
||||
end
|
||||
end
|
||||
|
||||
return self.join_output()
|
||||
except .. as e, msg
|
||||
self.error(f"Template body transpilation failed: {msg}")
|
||||
end
|
||||
end
|
||||
|
||||
# Transpile template animation body (for engine_proxy classes)
|
||||
# Similar to template body but uses self.add() instead of engine.add()
|
||||
def transpile_template_animation_body()
|
||||
@ -1430,9 +1385,8 @@ class SimpleDSLTranspiler
|
||||
def process_value(context)
|
||||
var result = self.process_additive_expression(context, true, false) # true = top-level, false = not raw mode
|
||||
# Handle closure wrapping for top-level expressions (not in raw mode) only if there is computation needed
|
||||
# print(f"> process_value {context=} {result.needs_function()=} {result=}")
|
||||
if (((context == self.CONTEXT_VARIABLE) || (context == self.CONTEXT_PROPERTY)) && result.needs_closure())
|
||||
|| ((context == self.CONTEXT_REPEAT_COUNT) && result.needs_function())
|
||||
|| ((context == self.CONTEXT_REPEAT_COUNT) && result.needs_closure())
|
||||
# Special handling for repeat_count context - always create simple function for property access
|
||||
if context == self.CONTEXT_REPEAT_COUNT
|
||||
# print(f">>> CONTEXT_REPEAT_COUNT")
|
||||
@ -2317,11 +2271,14 @@ class SimpleDSLTranspiler
|
||||
self.error(f"Cannot redefine predefined color '{name}'. Use a different name like '{name}_custom' or 'my_{name}'")
|
||||
return false
|
||||
elif entry.is_builtin
|
||||
self.error(f"Cannot redefine built-in symbol '{name}' (type: {entry.type}). Use a different name like '{name}_custom' or 'my_{name}'")
|
||||
self.error(f"Cannot redefine built-in symbol '{name}'. Use a different name like '{name}_custom' or 'my_{name}'")
|
||||
return false
|
||||
elif definition_type == "extern function" && entry.type == 5 #-animation_dsl._symbol_entry.TYPE_USER_FUNCTION-#
|
||||
# Allow duplicate extern function declarations for the same function
|
||||
return true
|
||||
else
|
||||
# User-defined symbol already exists - this is a redefinition error
|
||||
self.error(f"Symbol '{name}' is already defined as {entry.type}. Cannot redefine as {definition_type}.")
|
||||
self.error(f"Symbol '{name}' is already defined. Cannot redefine as {definition_type}.")
|
||||
return false
|
||||
end
|
||||
|
||||
@ -2389,11 +2346,8 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
def join_output()
|
||||
var result = ""
|
||||
for line : self.output
|
||||
result += line + "\n"
|
||||
end
|
||||
return result
|
||||
# Use list.concat() for O(n) performance instead of O(n²) string concatenation
|
||||
return self.output.concat("\n") + "\n"
|
||||
end
|
||||
|
||||
def error(msg)
|
||||
@ -2729,6 +2683,15 @@ class SimpleDSLTranspiler
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Check if already declared (duplicate extern function is allowed, skip code generation)
|
||||
if self.symbol_table.contains(func_name)
|
||||
var entry = self.symbol_table.get(func_name)
|
||||
if entry != nil && entry.type == 5 #-animation_dsl._symbol_entry.TYPE_USER_FUNCTION-#
|
||||
# Already declared as extern function, skip duplicate registration
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Validate function name
|
||||
self.validate_user_name(func_name, "extern function")
|
||||
|
||||
|
||||
@ -239,7 +239,6 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# Subclasses must override this to implement their update logic
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if object is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Rebuild LUT if dirty
|
||||
if self._lut_dirty || self._color_lut == nil
|
||||
@ -247,9 +246,7 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
end
|
||||
|
||||
# Cache the brightness to an instance variable for this tick
|
||||
self._brightness = self.brightness
|
||||
|
||||
return self.is_running
|
||||
self._brightness = self.member("brightness")
|
||||
end
|
||||
|
||||
# Produce a color value for any parameter name (optimized version from Animate_palette)
|
||||
|
||||
@ -35,16 +35,6 @@ class ValueProvider : animation.parameterized_object
|
||||
def produce_value(name, time_ms)
|
||||
return module("undefined") # Default behavior - return undefined
|
||||
end
|
||||
|
||||
# Update object state based on current time
|
||||
# Subclasses must override this to implement their update logic
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if object is still running, false if completed
|
||||
def update(time_ms)
|
||||
# Default implementation just returns running state
|
||||
return self.is_running
|
||||
end
|
||||
end
|
||||
|
||||
# Add a method to check if an object is a value provider
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -42,17 +42,14 @@ print("\n--- Test 2: Animation Management ---")
|
||||
var anim1 = animation.solid(engine) # Red, priority 10
|
||||
anim1.color = 0xFFFF0000
|
||||
anim1.priority = 10
|
||||
anim1.name = "red"
|
||||
|
||||
var anim2 = animation.solid(engine) # Green, priority 5
|
||||
anim2.color = 0xFF00FF00
|
||||
anim2.priority = 5
|
||||
anim2.name = "green"
|
||||
|
||||
var anim3 = animation.solid(engine) # Blue, priority 15
|
||||
anim3.color = 0xFF0000FF
|
||||
anim3.priority = 15
|
||||
anim3.name = "blue"
|
||||
|
||||
assert_test(engine.add(anim1), "Should add first animation")
|
||||
assert_test(engine.add(anim2), "Should add second animation")
|
||||
@ -92,7 +89,6 @@ engine.clear()
|
||||
var test_anim = animation.solid(engine)
|
||||
test_anim.color = 0xFFFF0000
|
||||
test_anim.priority = 10
|
||||
test_anim.name = "test"
|
||||
engine.add(test_anim)
|
||||
engine.run()
|
||||
|
||||
@ -136,7 +132,6 @@ for i : 0..49
|
||||
var anim = animation.solid(engine)
|
||||
anim.color = color
|
||||
anim.priority = i
|
||||
anim.name = f"perf_{i}"
|
||||
engine.add(anim)
|
||||
end
|
||||
|
||||
|
||||
@ -40,7 +40,6 @@ var base_anim = animation.solid(opacity_engine)
|
||||
base_anim.color = 0xFFFF0000 # Red
|
||||
base_anim.opacity = 128 # 50% opacity
|
||||
base_anim.priority = 10
|
||||
base_anim.name = "base_red"
|
||||
|
||||
opacity_engine.add(base_anim)
|
||||
opacity_engine.run()
|
||||
@ -61,17 +60,17 @@ print("\n--- Test 11b: Animation as opacity mask - basic setup ---")
|
||||
var opacity_mask = animation.solid(opacity_engine)
|
||||
opacity_mask.color = 0xFF808080 # Gray (128 brightness)
|
||||
opacity_mask.priority = 5
|
||||
opacity_mask.name = "opacity_mask"
|
||||
opacity_mask.id = "opacity_mask"
|
||||
|
||||
# Create main animation with animation opacity
|
||||
var masked_anim = animation.solid(opacity_engine)
|
||||
masked_anim.color = 0xFF00FF00 # Green
|
||||
masked_anim.opacity = opacity_mask # Use animation as opacity
|
||||
masked_anim.priority = 15
|
||||
masked_anim.name = "masked_green"
|
||||
masked_anim.id = "masked_green"
|
||||
|
||||
assert_test(isinstance(masked_anim.opacity, animation.animation), "Opacity should be an animation instance")
|
||||
assert_equals(masked_anim.opacity.name, "opacity_mask", "Opacity animation should be correctly assigned")
|
||||
assert_equals(masked_anim.opacity.id, "opacity_mask", "Opacity animation should be correctly assigned")
|
||||
|
||||
# Test 11c: Animation opacity rendering
|
||||
print("\n--- Test 11c: Animation opacity rendering ---")
|
||||
@ -100,14 +99,12 @@ print("\n--- Test 11e: Complex opacity animation scenarios ---")
|
||||
var pulsing_opacity = animation.solid(opacity_engine)
|
||||
pulsing_opacity.color = 0xFF000000 # Start with black (0 opacity)
|
||||
pulsing_opacity.priority = 1
|
||||
pulsing_opacity.name = "pulsing_opacity"
|
||||
|
||||
# Create animated color base
|
||||
var rainbow_base = animation.solid(opacity_engine)
|
||||
rainbow_base.color = 0xFFFF0000 # Red base
|
||||
rainbow_base.opacity = pulsing_opacity # Pulsing opacity
|
||||
rainbow_base.priority = 20
|
||||
rainbow_base.name = "rainbow_with_pulse"
|
||||
|
||||
# Test multiple renders with changing opacity
|
||||
opacity_engine.clear()
|
||||
@ -134,14 +131,12 @@ print("\n--- Test 11f: Opacity animation lifecycle management ---")
|
||||
var auto_start_opacity = animation.solid(opacity_engine)
|
||||
auto_start_opacity.color = 0xFF808080 # Gray
|
||||
auto_start_opacity.priority = 1
|
||||
auto_start_opacity.name = "auto_start_opacity"
|
||||
auto_start_opacity.is_running = false # Start stopped
|
||||
|
||||
var auto_start_main = animation.solid(opacity_engine)
|
||||
auto_start_main.color = 0xFFFFFF00 # Yellow
|
||||
auto_start_main.opacity = auto_start_opacity
|
||||
auto_start_main.priority = 10
|
||||
auto_start_main.name = "auto_start_main"
|
||||
|
||||
# Opacity animation should not be running initially
|
||||
assert_test(!auto_start_opacity.is_running, "Opacity animation should start stopped")
|
||||
@ -163,17 +158,14 @@ print("\n--- Test 11g: Nested animation opacity ---")
|
||||
var base_nested = animation.solid(opacity_engine)
|
||||
base_nested.color = 0xFF00FFFF # Cyan
|
||||
base_nested.priority = 30
|
||||
base_nested.name = "base_nested"
|
||||
|
||||
var opacity1 = animation.solid(opacity_engine)
|
||||
opacity1.color = 0xFF808080 # 50% gray
|
||||
opacity1.priority = 25
|
||||
opacity1.name = "opacity1"
|
||||
|
||||
var opacity2 = animation.solid(opacity_engine)
|
||||
opacity2.color = 0xFFC0C0C0 # 75% gray
|
||||
opacity2.priority = 20
|
||||
opacity2.name = "opacity2"
|
||||
|
||||
# Chain the opacities: base uses opacity1, opacity1 uses opacity2
|
||||
opacity1.opacity = opacity2
|
||||
@ -202,12 +194,10 @@ print("\n--- Test 11h: Opacity animation parameter changes ---")
|
||||
var param_base = animation.solid(opacity_engine)
|
||||
param_base.color = 0xFFFF00FF # Magenta
|
||||
param_base.priority = 10
|
||||
param_base.name = "param_base"
|
||||
|
||||
var param_opacity = animation.solid(opacity_engine)
|
||||
param_opacity.color = 0xFF404040 # Dark gray
|
||||
param_opacity.priority = 5
|
||||
param_opacity.name = "param_opacity"
|
||||
|
||||
param_base.opacity = param_opacity
|
||||
|
||||
@ -244,7 +234,6 @@ print("\n--- Test 11i: Opacity edge cases ---")
|
||||
var edge_base = animation.solid(opacity_engine)
|
||||
edge_base.color = 0xFF0080FF # Blue
|
||||
edge_base.priority = 10
|
||||
edge_base.name = "edge_base"
|
||||
|
||||
# Test full transparency (should still render but with no visible effect)
|
||||
edge_base.opacity = 0
|
||||
@ -264,7 +253,6 @@ assert_test(render_result, "Animation with full opacity should render normally")
|
||||
var transparent_opacity = animation.solid(opacity_engine)
|
||||
transparent_opacity.color = 0x00000000 # Fully transparent
|
||||
transparent_opacity.priority = 5
|
||||
transparent_opacity.name = "transparent_opacity"
|
||||
|
||||
edge_base.opacity = transparent_opacity
|
||||
transparent_opacity.start()
|
||||
@ -285,12 +273,10 @@ for i : 0..9
|
||||
var perf_base = animation.solid(opacity_engine)
|
||||
perf_base.color = 0xFF000000 | ((i * 25) << 16) | ((i * 15) << 8) | (i * 10)
|
||||
perf_base.priority = 50 + i
|
||||
perf_base.name = f"perf_base_{i}"
|
||||
|
||||
|
||||
var perf_opacity = animation.solid(opacity_engine)
|
||||
perf_opacity.color = 0xFF808080 # 50% gray
|
||||
perf_opacity.priority = 40 + i
|
||||
perf_opacity.name = f"perf_opacity_{i}"
|
||||
|
||||
perf_base.opacity = perf_opacity
|
||||
|
||||
|
||||
@ -24,14 +24,12 @@ anim.priority = 20
|
||||
anim.duration = 5000
|
||||
anim.loop = true # Use boolean for loop parameter
|
||||
anim.opacity = 255
|
||||
anim.name = "test_animation"
|
||||
anim.color = 0xFF0000
|
||||
assert(anim.is_running == false, "Animation should not be running initially")
|
||||
assert(anim.priority == 20, "Animation priority should be 20")
|
||||
assert(anim.duration == 5000, "Animation duration should be 5000ms")
|
||||
assert(anim.loop == true, "Animation should be set to loop")
|
||||
assert(anim.opacity == 255, "Animation opacity should be 255")
|
||||
assert(anim.name == "test_animation", "Animation name should be 'test_animation'")
|
||||
assert(anim.color == 0xFF0000, "Animation color should be red")
|
||||
|
||||
# Test default values
|
||||
@ -40,19 +38,21 @@ assert(default_anim.priority == 10, "Default priority should be 10")
|
||||
assert(default_anim.duration == 0, "Default duration should be 0 (infinite)")
|
||||
assert(default_anim.loop == false, "Default loop should be false")
|
||||
assert(default_anim.opacity == 255, "Default opacity should be 255")
|
||||
assert(default_anim.name == "animation", "Default name should be 'animation'")
|
||||
assert(default_anim.color == 0x00000000, "Default color should be transparent")
|
||||
|
||||
# Test start method
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
# because engine_proxy normally sets it before dispatching update/render calls
|
||||
engine.time_ms = 1000
|
||||
anim.start()
|
||||
anim.update()
|
||||
anim.start_time = engine.time_ms # Set start_time manually for direct testing
|
||||
anim.start(engine.time_ms)
|
||||
anim.update(engine.time_ms)
|
||||
assert(anim.is_running == true, "Animation should be running after start")
|
||||
assert(anim.start_time == 1000, "Animation start time should be 1000")
|
||||
|
||||
# Test restart functionality - start() acts as restart
|
||||
engine.time_ms = 2000
|
||||
anim.start()
|
||||
anim.start(engine.time_ms)
|
||||
assert(anim.is_running == true, "Animation should be running after start")
|
||||
assert(anim.start_time == 2000, "Animation start time should be 2000")
|
||||
var first_start_time = anim.start_time
|
||||
@ -69,23 +69,21 @@ non_loop_anim.priority = 1
|
||||
non_loop_anim.duration = 1000
|
||||
non_loop_anim.loop = false
|
||||
non_loop_anim.opacity = 255
|
||||
non_loop_anim.name = "non_loop"
|
||||
non_loop_anim.color = 0xFF0000
|
||||
engine.time_ms = 2000
|
||||
non_loop_anim.start_time = engine.time_ms # Set start_time manually for direct testing
|
||||
non_loop_anim.start(2000)
|
||||
non_loop_anim.update(2000)
|
||||
assert(non_loop_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Update within duration
|
||||
engine.time_ms = 2500
|
||||
var result = non_loop_anim.update(engine.time_ms)
|
||||
assert(result == true, "Update should return true when animation is still running")
|
||||
non_loop_anim.update(engine.time_ms)
|
||||
assert(non_loop_anim.is_running == true, "Animation should still be running")
|
||||
|
||||
# Update after duration
|
||||
engine.time_ms = 3100
|
||||
result = non_loop_anim.update(engine.time_ms)
|
||||
assert(result == false, "Update should return false when animation is complete")
|
||||
non_loop_anim.update(engine.time_ms)
|
||||
assert(non_loop_anim.is_running == false, "Animation should stop after duration")
|
||||
|
||||
# Test update method with looping animation
|
||||
@ -94,16 +92,15 @@ loop_anim.priority = 1
|
||||
loop_anim.duration = 1000
|
||||
loop_anim.loop = true
|
||||
loop_anim.opacity = 255
|
||||
loop_anim.name = "loop"
|
||||
loop_anim.color = 0xFF0000
|
||||
engine.time_ms = 4000
|
||||
loop_anim.start_time = engine.time_ms # Set start_time manually for direct testing
|
||||
loop_anim.start(engine.time_ms)
|
||||
loop_anim.update(engine.time_ms) # update must be explictly called to start time
|
||||
|
||||
# Update after one loop
|
||||
engine.time_ms = 5100
|
||||
result = loop_anim.update(engine.time_ms)
|
||||
assert(result == true, "Update should return true for looping animation")
|
||||
loop_anim.update(engine.time_ms)
|
||||
assert(loop_anim.is_running == true, "Looping animation should still be running after duration")
|
||||
assert(loop_anim.start_time == 5000, "Start time should be adjusted for looping")
|
||||
|
||||
@ -156,15 +153,15 @@ var invalid_opacity_result = param_anim.set_param("opacity", 300) # Invalid: ab
|
||||
assert(valid_priority_result == true, "Valid priority parameter should succeed")
|
||||
assert(valid_color_result == true, "Valid color parameter should succeed")
|
||||
|
||||
# Test render method (base implementation should do nothing)
|
||||
# Test render method (base implementation renders the color)
|
||||
# Note: is_running check is now in engine_proxy, not in base render()
|
||||
# Create a frame buffer for testing
|
||||
var frame = animation.frame_buffer(10)
|
||||
result = setter_anim.render(frame, engine.time_ms)
|
||||
assert(result == false, "Base render method should return false")
|
||||
var render_result = setter_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
assert(render_result == true, "Base render method should return true (renders color)")
|
||||
|
||||
# Test tostring method
|
||||
var anim_str = str(anim)
|
||||
assert(string.find(anim_str, "Animation") >= 0, "String representation should contain 'Animation'")
|
||||
assert(string.find(anim_str, anim.name) >= 0, "String representation should contain the animation name")
|
||||
|
||||
print("All Animation tests passed!")
|
||||
@ -85,7 +85,7 @@ def run_tests()
|
||||
pulse.back_color = 0xFF000000 # Transparent
|
||||
pulse.start()
|
||||
|
||||
var rendered = pulse.render(frame, engine.time_ms)
|
||||
var rendered = pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
test_assert(rendered, "Render returns true when running")
|
||||
|
||||
# Check that pixels 3 and 4 are red, others are transparent
|
||||
@ -97,7 +97,7 @@ def run_tests()
|
||||
# Test 6: Frame rendering with background
|
||||
frame.clear()
|
||||
pulse.back_color = 0xFF000080 # Dark blue background
|
||||
pulse.render(frame, engine.time_ms)
|
||||
pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
test_assert(frame.get_pixel_color(0) == 0xFF000080, "Background pixel is dark blue")
|
||||
test_assert(frame.get_pixel_color(3) == 0xFFFF0000, "Pulse pixel overrides background")
|
||||
@ -109,7 +109,7 @@ def run_tests()
|
||||
pulse.pos = 4
|
||||
pulse.beacon_size = 2
|
||||
pulse.slew_size = 1
|
||||
pulse.render(frame, engine.time_ms)
|
||||
pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Check main pulse
|
||||
test_assert(frame.get_pixel_color(4) == 0xFFFF0000, "Main pulse pixel 1 is red")
|
||||
@ -128,7 +128,7 @@ def run_tests()
|
||||
pulse.pos = 0
|
||||
pulse.beacon_size = 2
|
||||
pulse.slew_size = 1
|
||||
pulse.render(frame, engine.time_ms)
|
||||
pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
test_assert(frame.get_pixel_color(0) == 0xFFFF0000, "Pulse at start boundary works")
|
||||
test_assert(frame.get_pixel_color(1) == 0xFFFF0000, "Pulse at start boundary works")
|
||||
@ -137,7 +137,7 @@ def run_tests()
|
||||
pulse.pos = 8
|
||||
pulse.beacon_size = 2
|
||||
pulse.slew_size = 1
|
||||
pulse.render(frame, engine.time_ms)
|
||||
pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
test_assert(frame.get_pixel_color(8) == 0xFFFF0000, "Pulse at end boundary works")
|
||||
test_assert(frame.get_pixel_color(9) == 0xFFFF0000, "Pulse at end boundary works")
|
||||
@ -147,7 +147,7 @@ def run_tests()
|
||||
pulse.pos = 5
|
||||
pulse.beacon_size = 0
|
||||
pulse.slew_size = 2
|
||||
pulse.render(frame, engine.time_ms)
|
||||
pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Should have slew on both sides but no main pulse
|
||||
var left_slew1 = frame.get_pixel_color(3)
|
||||
|
||||
@ -26,7 +26,6 @@ def test_atomic_closure_batch_execution()
|
||||
red_anim.priority = 0
|
||||
red_anim.duration = 0
|
||||
red_anim.loop = true
|
||||
red_anim.name = "red"
|
||||
|
||||
var blue_provider = animation.static_color(engine)
|
||||
blue_provider.color = 0xFF0000FF
|
||||
@ -35,7 +34,6 @@ def test_atomic_closure_batch_execution()
|
||||
blue_anim.priority = 0
|
||||
blue_anim.duration = 0
|
||||
blue_anim.loop = true
|
||||
blue_anim.name = "blue"
|
||||
|
||||
# Simple test - just verify the basic functionality works
|
||||
# We'll check that closures execute and animations transition properly
|
||||
@ -96,7 +94,6 @@ def test_multiple_consecutive_closures()
|
||||
green_anim.priority = 0
|
||||
green_anim.duration = 0
|
||||
green_anim.loop = true
|
||||
green_anim.name = "green"
|
||||
|
||||
var yellow_provider = animation.static_color(engine)
|
||||
yellow_provider.color = 0xFFFFFF00
|
||||
@ -105,7 +102,6 @@ def test_multiple_consecutive_closures()
|
||||
yellow_anim.priority = 0
|
||||
yellow_anim.duration = 0
|
||||
yellow_anim.loop = true
|
||||
yellow_anim.name = "yellow"
|
||||
|
||||
# Track closure execution order
|
||||
var closure_order = []
|
||||
@ -165,7 +161,6 @@ def test_closure_batch_at_sequence_start()
|
||||
purple_anim.priority = 0
|
||||
purple_anim.duration = 0
|
||||
purple_anim.loop = true
|
||||
purple_anim.name = "purple"
|
||||
|
||||
# Track initial closure execution
|
||||
var initial_setup_done = false
|
||||
@ -204,7 +199,6 @@ def test_repeat_sequence_closure_batching()
|
||||
cyan_anim.priority = 0
|
||||
cyan_anim.duration = 0
|
||||
cyan_anim.loop = true
|
||||
cyan_anim.name = "cyan"
|
||||
|
||||
# Track iteration state
|
||||
var iteration_count = 0
|
||||
@ -275,7 +269,6 @@ def test_black_frame_fix_integration()
|
||||
shutter_anim.priority = 0
|
||||
shutter_anim.duration = 0
|
||||
shutter_anim.loop = true
|
||||
shutter_anim.name = "shutter"
|
||||
|
||||
# Simulate color cycle (like col1.next = 1)
|
||||
var color_index = 0
|
||||
|
||||
@ -19,12 +19,10 @@ def test_bounce_animation_basic()
|
||||
# Create a simple source animation
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFFFF0000
|
||||
source.name = "test_source"
|
||||
|
||||
# Test with default parameters using new parameterized pattern
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
bounce_anim.source_animation = source
|
||||
bounce_anim.name = "test_bounce"
|
||||
|
||||
assert(bounce_anim != nil, "BounceAnimation should be created")
|
||||
assert(bounce_anim.bounce_speed == 128, "Default bounce_speed should be 128")
|
||||
@ -46,7 +44,6 @@ def test_bounce_animation_custom()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFF00FF00
|
||||
source.name = "test_source"
|
||||
|
||||
# Test with custom parameters using new parameterized pattern
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
@ -59,7 +56,6 @@ def test_bounce_animation_custom()
|
||||
bounce_anim.duration = 5000
|
||||
bounce_anim.loop = false
|
||||
bounce_anim.opacity = 200
|
||||
bounce_anim.name = "custom_bounce"
|
||||
|
||||
assert(bounce_anim.bounce_speed == 200, "Custom bounce_speed should be 200")
|
||||
assert(bounce_anim.bounce_range == 15, "Custom bounce_range should be 15")
|
||||
@ -83,11 +79,9 @@ def test_bounce_animation_parameters()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFF0000FF
|
||||
source.name = "test_source"
|
||||
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
bounce_anim.source_animation = source
|
||||
bounce_anim.name = "param_test"
|
||||
|
||||
# Test parameter changes using virtual member assignment
|
||||
bounce_anim.bounce_speed = 180
|
||||
@ -118,7 +112,6 @@ def test_bounce_animation_physics()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFFFFFF00
|
||||
source.name = "test_source"
|
||||
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
bounce_anim.source_animation = source
|
||||
@ -126,7 +119,6 @@ def test_bounce_animation_physics()
|
||||
bounce_anim.bounce_range = 0
|
||||
bounce_anim.damping = 250
|
||||
bounce_anim.gravity = 0
|
||||
bounce_anim.name = "physics_test"
|
||||
|
||||
# Start animation
|
||||
bounce_anim.start(1000)
|
||||
@ -158,7 +150,6 @@ def test_bounce_animation_update_render()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFFFF00FF
|
||||
source.name = "test_source"
|
||||
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
bounce_anim.source_animation = source
|
||||
@ -166,7 +157,6 @@ def test_bounce_animation_update_render()
|
||||
bounce_anim.bounce_range = 0
|
||||
bounce_anim.damping = 250
|
||||
bounce_anim.gravity = 0
|
||||
bounce_anim.name = "update_test"
|
||||
|
||||
var frame = animation.frame_buffer(10)
|
||||
|
||||
@ -175,11 +165,11 @@ def test_bounce_animation_update_render()
|
||||
assert(bounce_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Test update
|
||||
var result = bounce_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
bounce_anim.update(1500)
|
||||
assert(bounce_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = bounce_anim.render(frame, 1500)
|
||||
var result = bounce_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that frame was modified (colors should be set)
|
||||
@ -207,7 +197,6 @@ def test_bounce_constructors()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFF00FFFF
|
||||
source.name = "test_source"
|
||||
|
||||
# Test bounce_basic
|
||||
var basic_bounce = animation.bounce_basic(engine)
|
||||
@ -247,7 +236,6 @@ def test_bounce_animation_gravity()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFFFFFFFF
|
||||
source.name = "test_source"
|
||||
|
||||
var gravity_bounce = animation.bounce_animation(engine)
|
||||
gravity_bounce.source_animation = source
|
||||
@ -255,7 +243,6 @@ def test_bounce_animation_gravity()
|
||||
gravity_bounce.bounce_range = 0
|
||||
gravity_bounce.damping = 240
|
||||
gravity_bounce.gravity = 100
|
||||
gravity_bounce.name = "gravity_test"
|
||||
|
||||
gravity_bounce.start(1000)
|
||||
|
||||
@ -282,7 +269,6 @@ def test_bounce_tostring()
|
||||
|
||||
var source = animation.solid(engine)
|
||||
source.color = 0xFF888888
|
||||
source.name = "test_source"
|
||||
|
||||
var bounce_anim = animation.bounce_animation(engine)
|
||||
bounce_anim.source_animation = source
|
||||
@ -290,7 +276,6 @@ def test_bounce_tostring()
|
||||
bounce_anim.bounce_range = 10
|
||||
bounce_anim.damping = 240
|
||||
bounce_anim.gravity = 30
|
||||
bounce_anim.name = "string_test"
|
||||
|
||||
var str_repr = str(bounce_anim)
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ print(f"Color at full cycle: 0x{color_full :08x}")
|
||||
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(5)
|
||||
blue_breathe.render(frame, engine.time_ms)
|
||||
blue_breathe.render(frame, engine.time_ms, engine.strip_length)
|
||||
print(f"First pixel after rendering: 0x{frame.get_pixel_color(0) :08x}")
|
||||
|
||||
# Test parameter validation
|
||||
|
||||
@ -61,7 +61,6 @@ class ColorCycleAnimationTest
|
||||
anim.duration = 0
|
||||
anim.loop = false
|
||||
anim.opacity = 255
|
||||
anim.name = "test_default"
|
||||
|
||||
# Check that the color was set correctly
|
||||
self.assert_equal(anim.color != nil, true, "Color is set")
|
||||
@ -80,7 +79,6 @@ class ColorCycleAnimationTest
|
||||
anim2.duration = 0
|
||||
anim2.loop = false
|
||||
anim2.opacity = 255
|
||||
anim2.name = "test_custom"
|
||||
|
||||
# Check that the color was set correctly
|
||||
self.assert_equal(anim2.color != nil, true, "Custom color is set")
|
||||
@ -105,7 +103,6 @@ class ColorCycleAnimationTest
|
||||
anim.duration = 0
|
||||
anim.loop = false
|
||||
anim.opacity = 255
|
||||
anim.name = "test_render"
|
||||
|
||||
# Create a frame buffer
|
||||
var frame = animation.frame_buffer(10) # 10 pixels
|
||||
@ -115,26 +112,26 @@ class ColorCycleAnimationTest
|
||||
|
||||
# Test brutal color switching - colors should change abruptly, not smoothly
|
||||
anim.update(0)
|
||||
anim.render(frame, engine.time_ms)
|
||||
anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var pixel_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(pixel_color != 0, true, "Start color is not zero")
|
||||
|
||||
# Test at middle of cycle - should still be a solid color (brutal switching)
|
||||
anim.update(500) # 50% through cycle
|
||||
anim.render(frame, engine.time_ms)
|
||||
anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
pixel_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(pixel_color != 0, true, "Middle color is not zero")
|
||||
|
||||
# Test at end of cycle - should be a different solid color
|
||||
anim.update(1000) # 100% through cycle
|
||||
anim.render(frame, engine.time_ms)
|
||||
anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
pixel_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(pixel_color != 0, true, "End color is not zero")
|
||||
|
||||
# Test manual next color trigger
|
||||
var initial_color = pixel_color
|
||||
provider.next = 1 # Trigger move to next color
|
||||
anim.render(frame, engine.time_ms)
|
||||
anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var next_color = frame.get_pixel_color(0)
|
||||
# Color should change when next is triggered (though it might be the same if cycling back)
|
||||
self.assert_equal(next_color != 0, true, "Next color is not zero")
|
||||
@ -154,7 +151,6 @@ class ColorCycleAnimationTest
|
||||
manual_anim.duration = 0
|
||||
manual_anim.loop = false
|
||||
manual_anim.opacity = 255
|
||||
manual_anim.name = "manual_test"
|
||||
|
||||
# Create a frame buffer
|
||||
var frame = animation.frame_buffer(10) # 10 pixels
|
||||
@ -164,20 +160,20 @@ class ColorCycleAnimationTest
|
||||
|
||||
# Test that color doesn't change with time in manual mode
|
||||
manual_anim.update(0)
|
||||
manual_anim.render(frame, engine.time_ms)
|
||||
manual_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var initial_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(initial_color != 0, true, "Initial color should not be zero")
|
||||
|
||||
# Advance time significantly - color should NOT change in manual mode
|
||||
engine.time_ms = 10000 # 10 seconds later
|
||||
manual_anim.update(engine.time_ms)
|
||||
manual_anim.render(frame, engine.time_ms)
|
||||
manual_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var same_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(same_color, initial_color, "Color should not change with time in manual mode")
|
||||
|
||||
# Manually trigger next color
|
||||
manual_provider.next = 1
|
||||
manual_anim.render(frame, engine.time_ms)
|
||||
manual_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var next_color = frame.get_pixel_color(0)
|
||||
# Color might be the same if we cycled back to the same color, but the trigger should work
|
||||
self.assert_equal(next_color != 0, true, "Next color should not be zero")
|
||||
@ -185,7 +181,7 @@ class ColorCycleAnimationTest
|
||||
# Trigger next again to ensure it works multiple times
|
||||
var previous_color = next_color
|
||||
manual_provider.next = 1
|
||||
manual_anim.render(frame, engine.time_ms)
|
||||
manual_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var third_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(third_color != 0, true, "Third color should not be zero")
|
||||
|
||||
@ -204,7 +200,6 @@ class ColorCycleAnimationTest
|
||||
anim.duration = 0
|
||||
anim.loop = false
|
||||
anim.opacity = 255
|
||||
anim.name = "test_direct"
|
||||
|
||||
# Check that the animation was created correctly
|
||||
self.assert_equal(anim != nil, true, "Animation was created")
|
||||
|
||||
@ -65,7 +65,6 @@ comet.direction = -1
|
||||
comet.wrap_around = 0
|
||||
comet.fade_factor = 150
|
||||
comet.priority = 15
|
||||
comet.name = "test_comet"
|
||||
|
||||
assert_equals(comet.color, 0xFFFF0000, "Color should be set correctly")
|
||||
assert_equals(comet.tail_length, 8, "Tail length should be set correctly")
|
||||
@ -74,7 +73,6 @@ assert_equals(comet.direction, -1, "Direction should be set correctly")
|
||||
assert_equals(comet.wrap_around, 0, "Wrap around should be disabled")
|
||||
assert_equals(comet.fade_factor, 150, "Fade factor should be set correctly")
|
||||
assert_equals(comet.priority, 15, "Priority should be set correctly")
|
||||
assert_equals(comet.name, "test_comet", "Name should be set correctly")
|
||||
|
||||
# Test 2: Multiple Comet Animations
|
||||
print("\n--- Test 2: Multiple Comet Animations ---")
|
||||
@ -149,8 +147,10 @@ pos_comet.tail_length = 3
|
||||
pos_comet.speed = 2560 # 10 pixels/sec (10 * 256)
|
||||
|
||||
# Use engine time for testing
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
engine.time_ms = 1000
|
||||
var start_time = engine.time_ms
|
||||
pos_comet.start_time = start_time # Set start_time manually for direct testing
|
||||
pos_comet.start(start_time)
|
||||
pos_comet.update(start_time)
|
||||
|
||||
@ -173,6 +173,7 @@ dir_comet.direction = -1 # Backward
|
||||
|
||||
engine.time_ms = 2000
|
||||
start_time = engine.time_ms
|
||||
dir_comet.start_time = start_time # Set start_time manually for direct testing
|
||||
dir_comet.start(start_time)
|
||||
dir_comet.update(start_time)
|
||||
var initial_pos = dir_comet.head_position
|
||||
@ -199,6 +200,7 @@ wrap_comet.wrap_around = 1 # Enable wrapping
|
||||
|
||||
small_engine.time_ms = 3000
|
||||
start_time = small_engine.time_ms
|
||||
wrap_comet.start_time = start_time # Set start_time manually for direct testing
|
||||
wrap_comet.start(start_time)
|
||||
wrap_comet.update(start_time)
|
||||
small_engine.time_ms = start_time + 2000 # 2 seconds - should wrap multiple times
|
||||
@ -216,6 +218,7 @@ bounce_comet.wrap_around = 0 # Disable wrapping (enable bouncing)
|
||||
|
||||
small_engine.time_ms = 4000
|
||||
start_time = small_engine.time_ms
|
||||
bounce_comet.start_time = start_time # Set start_time manually for direct testing
|
||||
bounce_comet.start(start_time)
|
||||
bounce_comet.update(small_engine.time_ms)
|
||||
small_engine.time_ms = start_time + 200 # Should hit the end and bounce
|
||||
@ -234,12 +237,13 @@ render_comet.tail_length = 3
|
||||
render_comet.speed = 256 # Slow (1 pixel/sec)
|
||||
|
||||
small_engine.time_ms = 5000
|
||||
render_comet.start_time = small_engine.time_ms # Set start_time manually for direct testing
|
||||
render_comet.start(small_engine.time_ms)
|
||||
render_comet.update(small_engine.time_ms)
|
||||
|
||||
# Clear frame and render
|
||||
frame.clear()
|
||||
var rendered = render_comet.render(frame, small_engine.time_ms)
|
||||
var rendered = render_comet.render(frame, small_engine.time_ms, small_engine.strip_length)
|
||||
assert_true(rendered, "Render should return true when successful")
|
||||
|
||||
# Check that pixels were set (comet should be at position 0 with tail)
|
||||
@ -269,6 +273,7 @@ provider_comet.speed = 1280
|
||||
assert_not_nil(provider_comet, "Comet with color provider should be created")
|
||||
|
||||
engine.time_ms = 6000
|
||||
provider_comet.start_time = engine.time_ms # Set start_time manually for direct testing
|
||||
provider_comet.start(engine.time_ms)
|
||||
provider_comet.update(engine.time_ms)
|
||||
|
||||
@ -295,6 +300,7 @@ assert_equals(strip_length, 30, "Strip length should come from engine")
|
||||
|
||||
# Test engine time usage
|
||||
engine.time_ms = 7000
|
||||
engine_comet.start_time = engine.time_ms # Set start_time manually for direct testing
|
||||
engine_comet.start(engine.time_ms)
|
||||
engine_comet.update(engine.time_ms)
|
||||
assert_equals(engine_comet.start_time, 7000, "Animation should use engine time for start")
|
||||
|
||||
@ -45,7 +45,6 @@ def run_tests()
|
||||
crenel.duration = 0
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_crenel"
|
||||
|
||||
test_assert(crenel.color == 0xFFFF0000, "Initial color setting")
|
||||
test_assert(crenel.pos == 4, "Initial position setting")
|
||||
@ -99,7 +98,7 @@ def run_tests()
|
||||
crenel.back_color = 0xFF000000 # Transparent
|
||||
crenel.start()
|
||||
|
||||
var rendered = crenel.render(frame, engine.time_ms)
|
||||
var rendered = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
test_assert(rendered, "Render returns true when running")
|
||||
|
||||
# Check pattern: 2 on, 3 off, 2 on, 3 off...
|
||||
@ -118,7 +117,7 @@ def run_tests()
|
||||
# Test 6: Frame rendering with background
|
||||
frame.clear()
|
||||
crenel.back_color = 0xFF000080 # Dark blue background
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
test_assert(frame.get_pixel_color(2) == 0xFF000080, "Gap pixel has background color")
|
||||
test_assert(frame.get_pixel_color(0) == 0xFFFF0000, "Pulse pixel overrides background")
|
||||
@ -127,7 +126,7 @@ def run_tests()
|
||||
frame.clear()
|
||||
crenel.back_color = 0xFF000000 # Transparent background
|
||||
crenel.nb_pulse = 2 # Only 2 pulses
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Should have 2 pulses: positions 0,1 and 5,6
|
||||
test_assert(frame.get_pixel_color(0) == 0xFFFF0000, "Limited pulse 1 pixel 1 is red")
|
||||
@ -143,7 +142,7 @@ def run_tests()
|
||||
frame.clear()
|
||||
crenel.pos = 2 # Start at position 2
|
||||
crenel.nb_pulse = -1 # Back to infinite
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Pattern should start at position 2: positions 2,3 and 7,8
|
||||
test_assert(frame.get_pixel_color(0) == 0x00000000, "Offset pattern - position 0 is transparent")
|
||||
@ -158,7 +157,7 @@ def run_tests()
|
||||
frame.clear()
|
||||
crenel.pos = 0
|
||||
crenel.pulse_size = 0
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# All pixels should remain transparent
|
||||
for i:0..9
|
||||
@ -169,7 +168,7 @@ def run_tests()
|
||||
frame.clear()
|
||||
crenel.pulse_size = 1
|
||||
crenel.low_size = 2
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Pattern: 1 on, 2 off, 1 on, 2 off...
|
||||
# Positions: 0 = red, 1,2 = transparent, 3 = red, 4,5 = transparent, 6 = red, 7,8 = transparent, 9 = red
|
||||
@ -185,7 +184,7 @@ def run_tests()
|
||||
crenel.pulse_size = 2
|
||||
crenel.low_size = 3
|
||||
crenel.pos = -1
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# With period = 5 and pos = -1, the pattern should be shifted
|
||||
# The algorithm should handle negative positions correctly
|
||||
@ -202,7 +201,7 @@ def run_tests()
|
||||
frame.clear()
|
||||
crenel.pos = 0
|
||||
crenel.nb_pulse = 0
|
||||
crenel.render(frame, engine.time_ms)
|
||||
crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# All pixels should remain transparent
|
||||
for i:0..9
|
||||
@ -235,7 +234,7 @@ def run_tests()
|
||||
crenel.pulse_size = 10
|
||||
crenel.low_size = 5
|
||||
crenel.nb_pulse = 3 # 3 pulses
|
||||
crenel.render(large_frame)
|
||||
crenel.render(large_frame, engine.time_ms, 100) # Use frame size as strip_length for this test
|
||||
|
||||
# Should have 3 pulses of 10 pixels each, separated by 5 pixels
|
||||
# Pulse 1: 0-9, Gap: 10-14, Pulse 2: 15-24, Gap: 25-29, Pulse 3: 30-39
|
||||
|
||||
@ -31,13 +31,12 @@ def test_crenel_with_integer_color()
|
||||
crenel.duration = 0 # infinite
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_crenel_int"
|
||||
|
||||
# Start and render
|
||||
crenel.start()
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result = crenel.render(frame, engine.time_ms)
|
||||
var result = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
assert(result == true, "Render should succeed with integer color")
|
||||
assert(crenel.is_running == true, "Animation should be running")
|
||||
@ -74,13 +73,12 @@ def test_crenel_with_color_provider()
|
||||
crenel.duration = 0 # infinite
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_crenel_provider"
|
||||
|
||||
# Start and render
|
||||
crenel.start()
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result = crenel.render(frame, engine.time_ms)
|
||||
var result = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
assert(result == true, "Render should succeed with ColorProvider")
|
||||
assert(crenel.is_running == true, "Animation should be running")
|
||||
@ -117,7 +115,6 @@ def test_crenel_with_dynamic_color_provider()
|
||||
crenel.duration = 0 # infinite
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_crenel_dynamic"
|
||||
|
||||
# Start and render at different times to verify color changes
|
||||
crenel.start()
|
||||
@ -125,14 +122,14 @@ def test_crenel_with_dynamic_color_provider()
|
||||
# Render at time 0
|
||||
crenel.update(0)
|
||||
frame.clear()
|
||||
var result1 = crenel.render(frame, engine.time_ms)
|
||||
var result1 = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
assert(result1 == true, "First render should succeed")
|
||||
|
||||
# Render at time 1000 (different color expected)
|
||||
engine.time_ms = 1000 # Simulate time passage
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result2 = crenel.render(frame, engine.time_ms)
|
||||
var result2 = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
assert(result2 == true, "Second render should succeed")
|
||||
|
||||
print("✓ CrenelPositionAnimation with dynamic ColorProvider test passed")
|
||||
@ -166,13 +163,12 @@ def test_crenel_with_generic_value_provider()
|
||||
crenel.duration = 0 # infinite
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_crenel_generic"
|
||||
|
||||
# Start and render
|
||||
crenel.start()
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result = crenel.render(frame, engine.time_ms)
|
||||
var result = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
assert(result == true, "Render should succeed with generic ValueProvider")
|
||||
assert(crenel.is_running == true, "Animation should be running")
|
||||
@ -204,7 +200,6 @@ def test_crenel_set_color_methods()
|
||||
crenel.duration = 0 # infinite
|
||||
crenel.loop = true
|
||||
crenel.opacity = 255
|
||||
crenel.name = "test_set_color"
|
||||
|
||||
crenel.start()
|
||||
|
||||
@ -212,7 +207,7 @@ def test_crenel_set_color_methods()
|
||||
crenel.color = 0xFF00FF00 # Green
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result1 = crenel.render(frame, engine.time_ms)
|
||||
var result1 = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
assert(result1 == true, "Render with new integer color should succeed")
|
||||
|
||||
# Test setting color provider via direct assignment
|
||||
@ -221,7 +216,7 @@ def test_crenel_set_color_methods()
|
||||
crenel.color = yellow_provider
|
||||
crenel.update(1000)
|
||||
frame.clear()
|
||||
var result2 = crenel.render(frame, engine.time_ms)
|
||||
var result2 = crenel.render(frame, engine.time_ms, engine.strip_length)
|
||||
assert(result2 == true, "Render with ColorProvider should succeed")
|
||||
|
||||
print("✓ CrenelPositionAnimation direct color assignment test passed")
|
||||
@ -247,7 +242,6 @@ def test_crenel_tostring()
|
||||
crenel_int.duration = 0
|
||||
crenel_int.loop = true
|
||||
crenel_int.opacity = 255
|
||||
crenel_int.name = "test_tostring_int"
|
||||
|
||||
var str_int = str(crenel_int)
|
||||
# Just verify the string is not empty and contains expected parts
|
||||
@ -269,7 +263,6 @@ def test_crenel_tostring()
|
||||
crenel_provider.duration = 0
|
||||
crenel_provider.loop = true
|
||||
crenel_provider.opacity = 255
|
||||
crenel_provider.name = "test_tostring_provider"
|
||||
|
||||
var str_provider = str(crenel_provider)
|
||||
# Just verify the string is not empty
|
||||
|
||||
@ -396,7 +396,7 @@ def test_external_function_complex()
|
||||
'extern function rand_meter\n' +
|
||||
'extern function breathing_effect\n' +
|
||||
'palette rainbow = [0xFF0000, 0x00FF00, 0x0000FF]\n' +
|
||||
'animation meter = palette_meter_animation(value_func = rand_meter)\n' +
|
||||
'animation meter = palette_meter_animation(level = rand_meter())\n' +
|
||||
'animation breath = solid(color=blue)\n' +
|
||||
'breath.opacity = breathing_effect\n' +
|
||||
'run meter'
|
||||
@ -550,6 +550,46 @@ def test_external_function_execution()
|
||||
end
|
||||
end
|
||||
|
||||
# Test duplicate extern function declarations are allowed
|
||||
def test_duplicate_extern_function()
|
||||
print("Testing duplicate extern function declarations...")
|
||||
|
||||
var dsl_source = 'berry """\n' +
|
||||
'def dup_func()\n' +
|
||||
' return 50\n' +
|
||||
'end\n' +
|
||||
'"""\n' +
|
||||
'extern function dup_func\n' +
|
||||
'extern function dup_func\n' +
|
||||
'extern function dup_func\n' +
|
||||
'animation test = solid(color=red)\n' +
|
||||
'test.opacity = dup_func\n' +
|
||||
'run test'
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for duplicate extern declarations")
|
||||
|
||||
# Verify only one registration is generated (not duplicated)
|
||||
import string
|
||||
var count = 0
|
||||
var idx = 0
|
||||
while true
|
||||
idx = string.find(berry_code, 'animation.register_user_function("dup_func"', idx)
|
||||
if idx < 0 break end
|
||||
count += 1
|
||||
idx += 1
|
||||
end
|
||||
|
||||
assert(count == 1, f"Should generate exactly one registration, got {count}")
|
||||
|
||||
# Verify the function call is still generated correctly
|
||||
assert(string.find(berry_code, "animation.get_user_function('dup_func')(engine)") >= 0, "Should call external function")
|
||||
|
||||
print("✓ Duplicate extern function declarations test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_berry_block_tests()
|
||||
print("=== DSL Berry Code Blocks and External Functions Test Suite ===")
|
||||
@ -575,7 +615,8 @@ def run_all_berry_block_tests()
|
||||
test_external_error_missing_function_name,
|
||||
test_external_function_reserved_name_validation,
|
||||
test_external_function_in_sequences,
|
||||
test_external_function_execution
|
||||
test_external_function_execution,
|
||||
test_duplicate_extern_function
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
|
||||
@ -24,11 +24,9 @@ print("✓ Basic creation test passed")
|
||||
print("\n=== Test 2: Add Child Animations ===")
|
||||
var child1 = animation.solid(engine)
|
||||
child1.color = 0xFFFF0000 # Red
|
||||
child1.name = "red_child"
|
||||
|
||||
var child2 = animation.solid(engine)
|
||||
child2.color = 0xFF00FF00 # Green
|
||||
child2.name = "green_child"
|
||||
|
||||
proxy.add(child1)
|
||||
proxy.add(child2)
|
||||
@ -60,15 +58,15 @@ print("✓ Start engine proxy test passed")
|
||||
# Test 5: Update engine proxy (should update all animations)
|
||||
print("\n=== Test 5: Update Engine Proxy ===")
|
||||
engine.time_ms = 1500
|
||||
var result = proxy.update(engine.time_ms)
|
||||
assert(result == true, "Engine proxy should still be running")
|
||||
proxy.update(engine.time_ms)
|
||||
assert(proxy.is_running == true, "Engine proxy should still be running")
|
||||
print("✓ Update engine proxy test passed")
|
||||
|
||||
# Test 6: Render engine proxy
|
||||
print("\n=== Test 6: Render Engine Proxy ===")
|
||||
var frame = animation.frame_buffer(30)
|
||||
engine.time_ms = 2000
|
||||
result = proxy.render(frame, engine.time_ms)
|
||||
result = proxy.render(frame, engine.time_ms, engine.strip_length)
|
||||
# Rendering should work (may or may not modify frame depending on animations)
|
||||
print("✓ Render engine proxy test passed")
|
||||
|
||||
@ -94,12 +92,10 @@ print("✓ Remove child test passed")
|
||||
print("\n=== Test 9: Engine Proxy with Own Rendering ===")
|
||||
var proxy2 = animation.engine_proxy(engine)
|
||||
proxy2.color = 0xFF0000FF # Blue background
|
||||
proxy2.name = "blue_proxy"
|
||||
|
||||
var pulse = animation.breathe_animation(engine)
|
||||
pulse.color = 0xFFFFFF00 # Yellow
|
||||
pulse.period = 2000
|
||||
pulse.name = "yellow_pulse"
|
||||
|
||||
proxy2.add(pulse)
|
||||
engine.time_ms = 3000
|
||||
@ -115,7 +111,6 @@ print("\n=== Test 10: Engine Integration ===")
|
||||
var proxy3 = animation.engine_proxy(engine)
|
||||
proxy3.color = 0xFFFF00FF # Magenta
|
||||
proxy3.priority = 15
|
||||
proxy3.name = "engine_proxy"
|
||||
|
||||
# Add to engine (should work since EngineProxy is a Playable)
|
||||
engine.add(proxy3)
|
||||
|
||||
@ -37,7 +37,7 @@ class TestAnimation : animation.animation
|
||||
def update(time_ms)
|
||||
self.update_called = true
|
||||
self.update_time = time_ms
|
||||
return super(self).update(time_ms)
|
||||
super(self).update(time_ms)
|
||||
end
|
||||
|
||||
def reset_test_state()
|
||||
|
||||
@ -8,13 +8,15 @@ import animation
|
||||
# Create a mock engine for testing
|
||||
class MockEngine
|
||||
var time_ms
|
||||
var strip_length
|
||||
|
||||
def init()
|
||||
self.time_ms = 1000 # Fixed time for testing
|
||||
self.strip_length = 10 # Mock strip length
|
||||
end
|
||||
|
||||
def get_strip_length()
|
||||
return 10 # Mock strip length
|
||||
return self.strip_length
|
||||
end
|
||||
|
||||
# Fake add() method for value provider auto-registration
|
||||
@ -36,7 +38,6 @@ solid_anim.priority = 10
|
||||
solid_anim.duration = 0
|
||||
solid_anim.loop = false # Use boolean instead of integer
|
||||
solid_anim.opacity = 255
|
||||
solid_anim.name = "solid_test"
|
||||
assert(solid_anim != nil, "Failed to create solid animation")
|
||||
|
||||
# Start the animation
|
||||
@ -46,7 +47,7 @@ assert(solid_anim.is_running, "Animation should be running")
|
||||
# Update and render
|
||||
solid_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
var result = solid_anim.render(frame, mock_engine.time_ms)
|
||||
var result = solid_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Check the color of the first pixel
|
||||
@ -66,7 +67,6 @@ cycle_anim.priority = 10
|
||||
cycle_anim.duration = 0
|
||||
cycle_anim.loop = false # Use boolean instead of integer
|
||||
cycle_anim.opacity = 255
|
||||
cycle_anim.name = "cycle_test"
|
||||
assert(cycle_anim != nil, "Failed to create cycle animation")
|
||||
|
||||
# Start the animation
|
||||
@ -76,7 +76,7 @@ assert(cycle_anim.is_running, "Animation should be running")
|
||||
# Update and render
|
||||
cycle_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = cycle_anim.render(frame, mock_engine.time_ms)
|
||||
result = cycle_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 3: animation.solid with a rich palette provider
|
||||
@ -93,7 +93,6 @@ palette_anim.priority = 10
|
||||
palette_anim.duration = 0
|
||||
palette_anim.loop = false # Use boolean instead of integer
|
||||
palette_anim.opacity = 255
|
||||
palette_anim.name = "palette_test"
|
||||
assert(palette_anim != nil, "Failed to create palette animation")
|
||||
|
||||
# Start the animation
|
||||
@ -103,7 +102,7 @@ assert(palette_anim.is_running, "Animation should be running")
|
||||
# Update and render
|
||||
palette_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = palette_anim.render(frame, mock_engine.time_ms)
|
||||
result = palette_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 4: animation.solid with a composite provider
|
||||
@ -124,7 +123,6 @@ composite_anim.priority = 10
|
||||
composite_anim.duration = 0
|
||||
composite_anim.loop = false # Use boolean instead of integer
|
||||
composite_anim.opacity = 255
|
||||
composite_anim.name = "composite_test"
|
||||
assert(composite_anim != nil, "Failed to create composite animation")
|
||||
|
||||
# Start the animation
|
||||
@ -134,7 +132,7 @@ assert(composite_anim.is_running, "Animation should be running")
|
||||
# Update and render
|
||||
composite_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = composite_anim.render(frame, mock_engine.time_ms)
|
||||
result = composite_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 5: Changing color provider dynamically
|
||||
@ -145,7 +143,6 @@ dynamic_anim.priority = 10
|
||||
dynamic_anim.duration = 0
|
||||
dynamic_anim.loop = false # Use boolean instead of integer
|
||||
dynamic_anim.opacity = 255
|
||||
dynamic_anim.name = "dynamic_test"
|
||||
assert(dynamic_anim != nil, "Failed to create dynamic animation")
|
||||
|
||||
# Start the animation
|
||||
@ -155,7 +152,7 @@ assert(dynamic_anim.is_running, "Animation should be running")
|
||||
# Update and render with initial color
|
||||
dynamic_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms)
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Check the color of the first pixel
|
||||
@ -166,7 +163,7 @@ assert(pixel_color == 0xFF0000FF, f"Expected 0xFF0000FF, got {pixel_color:08X}")
|
||||
dynamic_anim.color = 0x00FF00FF # Green
|
||||
dynamic_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms)
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Check the color of the first pixel
|
||||
@ -177,7 +174,7 @@ assert(pixel_color == 0x00FF00FF, f"Expected 0x00FF00FF, got {pixel_color:08X}")
|
||||
dynamic_anim.color = cycle_provider
|
||||
dynamic_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms)
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
print("All tests passed!")
|
||||
@ -19,7 +19,6 @@ fire.flicker_amount = 100
|
||||
fire.cooling_rate = 55
|
||||
fire.sparking_rate = 120
|
||||
fire.priority = 255
|
||||
fire.name = "test_fire"
|
||||
|
||||
print(f"Created fire animation: {fire}")
|
||||
print(f"Initial state - running: {fire.is_running}, priority: {fire.priority}")
|
||||
@ -78,7 +77,7 @@ var frame = animation.frame_buffer(30)
|
||||
frame.clear()
|
||||
|
||||
# Render the fire animation
|
||||
var rendered = fire.render(frame, engine.time_ms)
|
||||
var rendered = fire.render(frame, engine.time_ms, engine.strip_length)
|
||||
print(f"Rendered to frame buffer: {rendered}")
|
||||
|
||||
# Check that some pixels have been set (fire should create non-black pixels)
|
||||
@ -154,7 +153,7 @@ tiny_fire.start()
|
||||
tiny_engine.time_ms = current_time + 125
|
||||
tiny_fire.update(current_time + 125)
|
||||
var tiny_frame = animation.frame_buffer(1)
|
||||
tiny_fire.render(tiny_frame, tiny_engine.time_ms)
|
||||
tiny_fire.render(tiny_frame, tiny_engine.time_ms, tiny_engine.strip_length)
|
||||
print("Tiny fire (1 pixel) created and rendered successfully")
|
||||
|
||||
# Zero intensity
|
||||
@ -167,7 +166,7 @@ dim_fire.start()
|
||||
dim_engine.time_ms = current_time + 250
|
||||
dim_fire.update(current_time + 250)
|
||||
var dim_frame = animation.frame_buffer(10)
|
||||
dim_fire.render(dim_frame, dim_engine.time_ms)
|
||||
dim_fire.render(dim_frame, dim_engine.time_ms, dim_engine.strip_length)
|
||||
print("Dim fire (0 intensity) created and rendered successfully")
|
||||
|
||||
print("\n=== Fire Animation Test Complete ===")
|
||||
|
||||
@ -31,7 +31,6 @@ def test_get_param_value_with_color_provider()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = "test"
|
||||
|
||||
# Create a ColorProvider that we can track calls on
|
||||
class TrackingColorProvider : animation.color_provider
|
||||
@ -78,7 +77,6 @@ def test_get_param_value_with_generic_provider()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = "test"
|
||||
|
||||
# Create a generic ValueProvider that we can track calls on
|
||||
class TrackingValueProvider : animation.value_provider
|
||||
@ -125,7 +123,6 @@ def test_get_param_value_with_context_aware_provider()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = "test"
|
||||
|
||||
# Create a ValueProvider that returns different values based on parameter name
|
||||
class ContextAwareProvider : animation.value_provider
|
||||
@ -180,7 +177,6 @@ def test_get_param_value_with_static_value()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = "test"
|
||||
|
||||
# Set a static value (using the 'opacity' parameter that exists in base Animation)
|
||||
test_anim.opacity = 123
|
||||
|
||||
@ -22,9 +22,7 @@ def test_gradient_creation()
|
||||
# Test single color gradient
|
||||
var red_gradient = animation.gradient_animation(engine)
|
||||
red_gradient.color = 0xFFFF0000
|
||||
red_gradient.name = "red_gradient"
|
||||
assert(red_gradient != nil, "Should create red gradient")
|
||||
assert(red_gradient.name == "red_gradient", "Should set name")
|
||||
|
||||
# Test radial gradient
|
||||
var radial_gradient = animation.gradient_animation(engine)
|
||||
@ -35,7 +33,6 @@ def test_gradient_creation()
|
||||
radial_gradient.priority = 10
|
||||
radial_gradient.duration = 5000
|
||||
radial_gradient.loop = false
|
||||
radial_gradient.name = "radial_gradient"
|
||||
assert(radial_gradient != nil, "Should create radial gradient")
|
||||
assert(radial_gradient.gradient_type == 1, "Should be radial gradient")
|
||||
|
||||
@ -50,7 +47,6 @@ def test_gradient_parameters()
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFFFFFFFF
|
||||
gradient.name = "test"
|
||||
|
||||
# Test parameter setting via virtual members
|
||||
gradient.gradient_type = 1
|
||||
@ -84,16 +80,20 @@ def test_gradient_updates()
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFF00FF00
|
||||
gradient.movement_speed = 100
|
||||
gradient.name = "test"
|
||||
|
||||
# Start the animation
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
gradient.start(1000)
|
||||
assert(gradient.is_running == true, "Should be running after start")
|
||||
|
||||
# Test update at different times
|
||||
assert(gradient.update(1000) == true, "Should update successfully at start time")
|
||||
assert(gradient.update(1500) == true, "Should update successfully after 500ms")
|
||||
assert(gradient.update(2000) == true, "Should update successfully after 1000ms")
|
||||
gradient.update(1000)
|
||||
assert(gradient.is_running == true, "Should be running after update at start time")
|
||||
gradient.update(1500)
|
||||
assert(gradient.is_running == true, "Should be running after update at 500ms")
|
||||
gradient.update(2000)
|
||||
assert(gradient.is_running == true, "Should be running after update at 1000ms")
|
||||
|
||||
# Test that movement_speed affects phase_offset
|
||||
var initial_offset = gradient.phase_offset
|
||||
@ -113,17 +113,17 @@ def test_gradient_rendering()
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFFFF0000
|
||||
gradient.movement_speed = 0
|
||||
gradient.name = "test"
|
||||
|
||||
# Create a frame buffer
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
|
||||
# Start and update the animation
|
||||
gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
gradient.start(1000)
|
||||
gradient.update(1000)
|
||||
|
||||
# Test rendering
|
||||
var result = gradient.render(frame, 1000)
|
||||
var result = gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
# Test that colors were set (basic check)
|
||||
@ -176,14 +176,14 @@ def test_gradient_position_calculations()
|
||||
var linear_gradient = animation.gradient_animation(engine)
|
||||
linear_gradient.color = 0xFFFFFFFF
|
||||
linear_gradient.movement_speed = 0
|
||||
linear_gradient.name = "test"
|
||||
linear_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
linear_gradient.start(1000)
|
||||
linear_gradient.update(1000)
|
||||
|
||||
# The _calculate_linear_position method is private, but we can test the overall effect
|
||||
# by checking that different pixels get different colors in a linear gradient
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
linear_gradient.render(frame, 1000)
|
||||
linear_gradient.render(frame, 1000, engine.strip_length)
|
||||
|
||||
var first_color = frame.get_pixel_color(0)
|
||||
var last_color = frame.get_pixel_color(9)
|
||||
@ -195,10 +195,10 @@ def test_gradient_position_calculations()
|
||||
radial_gradient.color = 0xFFFFFFFF
|
||||
radial_gradient.gradient_type = 1
|
||||
radial_gradient.movement_speed = 0
|
||||
radial_gradient.name = "test"
|
||||
radial_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
radial_gradient.start(1000)
|
||||
radial_gradient.update(1000)
|
||||
radial_gradient.render(frame, 1000)
|
||||
radial_gradient.render(frame, 1000, engine.strip_length)
|
||||
|
||||
# In a radial gradient, center pixel should be different from edge pixels
|
||||
var center_color = frame.get_pixel_color(5) # Middle pixel
|
||||
@ -217,13 +217,11 @@ def test_gradient_color_refactoring()
|
||||
# Test with static color
|
||||
var static_gradient = animation.gradient_animation(engine)
|
||||
static_gradient.color = 0xFFFF0000
|
||||
static_gradient.name = "static_test"
|
||||
assert(static_gradient.color == 0xFFFF0000, "Should have color set")
|
||||
|
||||
# Test with nil color (default rainbow)
|
||||
var rainbow_gradient = animation.gradient_animation(engine)
|
||||
rainbow_gradient.color = nil
|
||||
rainbow_gradient.name = "rainbow_test"
|
||||
assert(rainbow_gradient.color == nil, "Should accept nil color for rainbow")
|
||||
|
||||
# Test color resolution
|
||||
@ -232,14 +230,16 @@ def test_gradient_color_refactoring()
|
||||
|
||||
# Test basic rendering with different color types
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
static_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
static_gradient.start(1000)
|
||||
static_gradient.update(1000)
|
||||
var result = static_gradient.render(frame, 1000)
|
||||
var result = static_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render with static color")
|
||||
|
||||
rainbow_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
rainbow_gradient.start(1000)
|
||||
rainbow_gradient.update(1000)
|
||||
result = rainbow_gradient.render(frame, 1000)
|
||||
result = rainbow_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render with rainbow color")
|
||||
|
||||
print("✓ GradientAnimation color refactoring test passed")
|
||||
@ -252,7 +252,6 @@ def test_gradient_virtual_parameters()
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.name = "test"
|
||||
|
||||
# Test virtual parameter assignment and access
|
||||
gradient.color = 0xFFFF00FF
|
||||
@ -289,7 +288,6 @@ def test_gradient_tostring()
|
||||
var static_gradient = animation.gradient_animation(engine)
|
||||
static_gradient.color = 0xFFFF0000
|
||||
static_gradient.movement_speed = 50
|
||||
static_gradient.name = "static_test"
|
||||
var str_static = str(static_gradient)
|
||||
assert(str_static != nil, "Should have string representation")
|
||||
assert(string.find(str_static, "linear") != -1, "Should mention gradient type")
|
||||
@ -302,7 +300,6 @@ def test_gradient_tostring()
|
||||
provider_gradient.color = color_provider
|
||||
provider_gradient.gradient_type = 1
|
||||
provider_gradient.movement_speed = 25
|
||||
provider_gradient.name = "provider_test"
|
||||
var str_provider = str(provider_gradient)
|
||||
assert(str_provider != nil, "Should have string representation")
|
||||
assert(string.find(str_provider, "radial") != -1, "Should mention radial type")
|
||||
|
||||
@ -18,7 +18,7 @@ rainbow_gradient.update(1000)
|
||||
|
||||
# Create frame and render
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
var result = rainbow_gradient.render(frame, 1000)
|
||||
var result = rainbow_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render rainbow gradient successfully")
|
||||
|
||||
# Check that different pixels have different colors (rainbow effect)
|
||||
|
||||
@ -25,12 +25,12 @@ assert(gradient.movement_speed == 50, "Should set movement speed")
|
||||
gradient.start(1000)
|
||||
assert(gradient.is_running == true, "Should be running")
|
||||
|
||||
var result = gradient.update(1000)
|
||||
assert(result == true, "Should update successfully")
|
||||
gradient.update(1000)
|
||||
assert(gradient.is_running == true, "Should still be running after update")
|
||||
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
result = gradient.render(frame, 1000)
|
||||
result = gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
print("✓ Basic GradientAnimation test passed!")
|
||||
|
||||
@ -158,11 +158,11 @@ def test_jitter_animation_update_render()
|
||||
jitter_anim.start(1000)
|
||||
|
||||
# Test update
|
||||
var result = jitter_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
jitter_anim.update(1500)
|
||||
assert(jitter_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = jitter_anim.render(frame, 1500)
|
||||
var result = jitter_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that jitter offsets were initialized
|
||||
|
||||
@ -172,10 +172,10 @@ def test_motion_effects_update_render()
|
||||
bounce_anim.start(1000)
|
||||
assert(bounce_anim.is_running == true, "Bounce animation should be running after start")
|
||||
|
||||
var result = bounce_anim.update(1500)
|
||||
assert(result == true, "Bounce update should return true for running animation")
|
||||
bounce_anim.update(1500)
|
||||
assert(bounce_anim.is_running == true, "Bounce animation should still be running after update")
|
||||
|
||||
result = bounce_anim.render(frame, 1500)
|
||||
var result = bounce_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Bounce render should return true for running animation")
|
||||
|
||||
# Test scale update/render
|
||||
@ -190,10 +190,10 @@ def test_motion_effects_update_render()
|
||||
scale_anim.start(2000)
|
||||
assert(scale_anim.is_running == true, "Scale animation should be running after start")
|
||||
|
||||
result = scale_anim.update(2500)
|
||||
assert(result == true, "Scale update should return true for running animation")
|
||||
scale_anim.update(2500)
|
||||
assert(scale_anim.is_running == true, "Scale animation should still be running after update")
|
||||
|
||||
result = scale_anim.render(frame, 2500)
|
||||
result = scale_anim.render(frame, 2500, engine.strip_length)
|
||||
assert(result == true, "Scale render should return true for running animation")
|
||||
|
||||
# Test jitter update/render
|
||||
@ -209,10 +209,10 @@ def test_motion_effects_update_render()
|
||||
jitter_anim.start(3000)
|
||||
assert(jitter_anim.is_running == true, "Jitter animation should be running after start")
|
||||
|
||||
result = jitter_anim.update(3500)
|
||||
assert(result == true, "Jitter update should return true for running animation")
|
||||
jitter_anim.update(3500)
|
||||
assert(jitter_anim.is_running == true, "Jitter animation should still be running after update")
|
||||
|
||||
result = jitter_anim.render(frame, 3500)
|
||||
result = jitter_anim.render(frame, 3500, engine.strip_length)
|
||||
assert(result == true, "Jitter render should return true for running animation")
|
||||
|
||||
print("✓ Motion effects update/render test passed")
|
||||
|
||||
@ -101,15 +101,17 @@ def test_noise_animation_update_render()
|
||||
var frame = animation.frame_buffer(10)
|
||||
|
||||
# Start animation
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
noise_anim.start_time = 1000 # Set start_time manually for direct testing
|
||||
noise_anim.start(1000)
|
||||
assert(noise_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Test update
|
||||
var result = noise_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
noise_anim.update(1500)
|
||||
assert(noise_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = noise_anim.render(frame, 1500)
|
||||
var result = noise_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that colors were set (should not all be black)
|
||||
|
||||
@ -0,0 +1,236 @@
|
||||
# Test suite for GradientMeterAnimation (palette_meter_animation)
|
||||
#
|
||||
# Tests the VU meter style animation with gradient colors and peak hold.
|
||||
|
||||
import animation
|
||||
|
||||
# Test basic creation
|
||||
def test_gradient_meter_creation()
|
||||
print("Testing GradientMeterAnimation creation...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
assert(meter != nil, "Should create gradient meter animation")
|
||||
assert(meter.level == 255, "Should default to level 255")
|
||||
assert(meter.peak_hold == 1000, "Should default to peak_hold 1000ms")
|
||||
assert(meter.shift_period == 0, "Should default to static gradient")
|
||||
|
||||
print("✓ GradientMeterAnimation creation test passed")
|
||||
end
|
||||
|
||||
# Test level parameter
|
||||
def test_gradient_meter_level()
|
||||
print("Testing GradientMeterAnimation level...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
|
||||
# Test level setting
|
||||
meter.level = 128
|
||||
assert(meter.level == 128, "Should set level to 128")
|
||||
|
||||
meter.level = 255
|
||||
assert(meter.level == 255, "Should set level to 255")
|
||||
|
||||
meter.level = 0
|
||||
assert(meter.level == 0, "Should set level to 0")
|
||||
|
||||
# Test validation
|
||||
assert(meter.set_param("level", 300) == false, "Should reject level > 255")
|
||||
assert(meter.set_param("level", -1) == false, "Should reject level < 0")
|
||||
|
||||
print("✓ GradientMeterAnimation level test passed")
|
||||
end
|
||||
|
||||
# Test peak hold functionality
|
||||
def test_gradient_meter_peak_hold()
|
||||
print("Testing GradientMeterAnimation peak hold...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
|
||||
# Enable peak hold
|
||||
meter.peak_hold = 1000 # 1 second hold
|
||||
assert(meter.peak_hold == 1000, "Should set peak_hold to 1000ms")
|
||||
|
||||
# Start animation
|
||||
meter.start_time = 1000
|
||||
meter.start(1000)
|
||||
|
||||
# Set initial level and update at time 1000
|
||||
meter.level = 200
|
||||
meter.update(1000)
|
||||
assert(meter.peak_level == 200, "Peak should track level")
|
||||
# peak_time is now 1000
|
||||
|
||||
# Lower level at time 1500 - peak should stay (500ms elapsed, within 1000ms hold)
|
||||
meter.level = 100
|
||||
meter.update(1500)
|
||||
assert(meter.peak_level == 200, "Peak should hold at 200")
|
||||
|
||||
# At time 2100 (1100ms since peak was set at 1000), hold has expired
|
||||
meter.update(2100)
|
||||
assert(meter.peak_level == 100, "Peak should drop to current level after hold expires")
|
||||
|
||||
print("✓ GradientMeterAnimation peak hold test passed")
|
||||
end
|
||||
|
||||
# Test rendering
|
||||
def test_gradient_meter_rendering()
|
||||
print("Testing GradientMeterAnimation rendering...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
|
||||
# Use a color provider (required for PaletteGradientAnimation)
|
||||
var color_source = animation.rich_palette(engine)
|
||||
meter.color_source = color_source
|
||||
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
|
||||
# Start and update (color_source needs update to init LUT)
|
||||
meter.start_time = 0
|
||||
meter.start(0)
|
||||
color_source.update(0)
|
||||
meter.update(0)
|
||||
|
||||
# Test 50% level (should fill ~5 pixels)
|
||||
meter.level = 128
|
||||
color_source.update(100)
|
||||
meter.update(100)
|
||||
meter.render(frame, 100, 10)
|
||||
|
||||
# First pixels should have color, last pixels should be transparent/black
|
||||
var first_color = frame.get_pixel_color(0)
|
||||
assert(first_color != 0x00000000, "First pixel should have color")
|
||||
|
||||
# Test 0% level (nothing filled)
|
||||
frame.fill_pixels(frame.pixels, 0x00000000) # Clear frame
|
||||
meter.level = 0
|
||||
color_source.update(200)
|
||||
meter.update(200)
|
||||
meter.render(frame, 200, 10)
|
||||
|
||||
print("✓ GradientMeterAnimation rendering test passed")
|
||||
end
|
||||
|
||||
# Test peak indicator rendering
|
||||
def test_gradient_meter_peak_indicator()
|
||||
print("Testing GradientMeterAnimation peak indicator...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
|
||||
# Use a color provider
|
||||
var color_source = animation.rich_palette(engine)
|
||||
meter.color_source = color_source
|
||||
meter.peak_hold = 2000
|
||||
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
|
||||
# Start animation
|
||||
meter.start_time = 0
|
||||
meter.start(0)
|
||||
color_source.update(0)
|
||||
|
||||
# Set high level then lower it
|
||||
meter.level = 200
|
||||
meter.update(0)
|
||||
|
||||
meter.level = 50
|
||||
color_source.update(100)
|
||||
meter.update(100)
|
||||
|
||||
# Peak should still be at 200, current at 50
|
||||
assert(meter.peak_level == 200, "Peak should be at 200")
|
||||
assert(meter.level == 50, "Level should be at 50")
|
||||
|
||||
# Render and check peak pixel is set
|
||||
meter.render(frame, 100, 10)
|
||||
|
||||
# Peak pixel position (200/255 * 9 ≈ 7)
|
||||
var peak_pixel = tasmota.scale_uint(200, 0, 255, 0, 9)
|
||||
var peak_color = frame.get_pixel_color(peak_pixel)
|
||||
assert(peak_color != 0x00000000, "Peak indicator pixel should have color")
|
||||
|
||||
print("✓ GradientMeterAnimation peak indicator test passed")
|
||||
end
|
||||
|
||||
# Test with color provider
|
||||
def test_gradient_meter_with_color_provider()
|
||||
print("Testing GradientMeterAnimation with color provider...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
|
||||
# Use a color cycle provider
|
||||
var color_cycle = animation.color_cycle(engine)
|
||||
meter.color_source = color_cycle
|
||||
|
||||
meter.level = 200
|
||||
meter.start_time = 0
|
||||
meter.start(0)
|
||||
color_cycle.update(0)
|
||||
meter.update(0)
|
||||
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
var result = meter.render(frame, 0, 10)
|
||||
assert(result == true, "Should render with color provider")
|
||||
|
||||
print("✓ GradientMeterAnimation with color provider test passed")
|
||||
end
|
||||
|
||||
# Test tostring
|
||||
def test_gradient_meter_tostring()
|
||||
print("Testing GradientMeterAnimation tostring...")
|
||||
|
||||
import string
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var meter = animation.palette_meter_animation(engine)
|
||||
meter.level = 150
|
||||
meter.peak_hold = 500
|
||||
|
||||
var s = str(meter)
|
||||
assert(s != nil, "Should have string representation")
|
||||
assert(string.find(s, "GradientMeterAnimation") != -1, "Should contain class name")
|
||||
assert(string.find(s, "level=150") != -1, "Should contain level")
|
||||
assert(string.find(s, "peak_hold=500") != -1, "Should contain peak_hold")
|
||||
|
||||
print("✓ GradientMeterAnimation tostring test passed")
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_palette_meter_animation_tests()
|
||||
print("=== GradientMeterAnimation Tests ===")
|
||||
|
||||
try
|
||||
test_gradient_meter_creation()
|
||||
test_gradient_meter_level()
|
||||
test_gradient_meter_peak_hold()
|
||||
test_gradient_meter_rendering()
|
||||
test_gradient_meter_peak_indicator()
|
||||
test_gradient_meter_with_color_provider()
|
||||
test_gradient_meter_tostring()
|
||||
|
||||
print("=== All GradientMeterAnimation tests passed! ===")
|
||||
return true
|
||||
except .. as e, msg
|
||||
print(f"Test failed: {e} - {msg}")
|
||||
raise "test_failed"
|
||||
end
|
||||
end
|
||||
|
||||
animation.run_palette_meter_animation_tests = run_palette_meter_animation_tests
|
||||
|
||||
run_palette_meter_animation_tests()
|
||||
|
||||
return run_palette_meter_animation_tests
|
||||
@ -47,20 +47,21 @@ gradient_anim.priority = 10
|
||||
gradient_anim.duration = 0
|
||||
gradient_anim.loop = false
|
||||
gradient_anim.opacity = 255
|
||||
gradient_anim.name = "gradient_test"
|
||||
|
||||
assert(gradient_anim != nil, "Failed to create gradient animation")
|
||||
assert(gradient_anim.shift_period == 3000, "Shift period should be 3000")
|
||||
|
||||
# Start the animation
|
||||
gradient_anim.start()
|
||||
gradient_anim.update() # force first tick
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
gradient_anim.start_time = mock_engine.time_ms # Set start_time manually for direct testing
|
||||
gradient_anim.start(mock_engine.time_ms)
|
||||
gradient_anim.update(mock_engine.time_ms) # force first tick
|
||||
assert(gradient_anim.is_running, "Animation should be running")
|
||||
|
||||
# Update and render
|
||||
gradient_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = gradient_anim.render(frame, mock_engine.time_ms)
|
||||
result = gradient_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test parameter changes
|
||||
@ -78,47 +79,6 @@ assert(gradient_anim.phase_shift == 64, "Phase shift should be updated to 64")
|
||||
gradient_anim.shift_period = 0
|
||||
assert(gradient_anim.shift_period == 0, "Shift period should be updated to 0 (static)")
|
||||
|
||||
# Test 2: PaletteMeterAnimation
|
||||
print("Test 2: PaletteMeterAnimation")
|
||||
var meter_anim = animation.palette_meter_animation(mock_engine)
|
||||
meter_anim.color_source = mock_color_source
|
||||
|
||||
# Create a value function that returns 50% (half the strip)
|
||||
def meter_value_func(engine, time_ms, animation)
|
||||
return 50 # 50% of the strip (this is still 0-100 for meter logic)
|
||||
end
|
||||
meter_anim.value_func = meter_value_func
|
||||
|
||||
meter_anim.priority = 10
|
||||
meter_anim.duration = 0
|
||||
meter_anim.loop = false
|
||||
meter_anim.opacity = 255
|
||||
meter_anim.name = "meter_test"
|
||||
|
||||
assert(meter_anim != nil, "Failed to create meter animation")
|
||||
|
||||
# Start the animation
|
||||
meter_anim.start()
|
||||
meter_anim.update() # force first tick
|
||||
assert(meter_anim.is_running, "Animation should be running")
|
||||
|
||||
# Update and render
|
||||
meter_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = meter_anim.render(frame, mock_engine.time_ms)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test changing value function
|
||||
def new_meter_value_func(engine, time_ms, animation)
|
||||
return 75 # 75% of the strip (this is still 0-100 for meter logic)
|
||||
end
|
||||
meter_anim.value_func = new_meter_value_func
|
||||
|
||||
meter_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = meter_anim.render(frame, mock_engine.time_ms)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 3: Changing color sources dynamically
|
||||
print("Test 3: Changing color sources dynamically")
|
||||
var dynamic_anim = animation.palette_gradient_animation(mock_engine)
|
||||
@ -127,14 +87,15 @@ dynamic_anim.shift_period = 1000
|
||||
dynamic_anim.spatial_period = 3
|
||||
|
||||
# Start the animation
|
||||
dynamic_anim.start()
|
||||
dynamic_anim.update() # force first tick
|
||||
dynamic_anim.start_time = mock_engine.time_ms # Set start_time manually for direct testing
|
||||
dynamic_anim.start(mock_engine.time_ms)
|
||||
dynamic_anim.update(mock_engine.time_ms) # force first tick
|
||||
assert(dynamic_anim.is_running, "Animation should be running")
|
||||
|
||||
# Update and render with initial color source
|
||||
dynamic_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms)
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Create another mock color source
|
||||
@ -150,7 +111,7 @@ var mock_color_source2 = MockColorSource2()
|
||||
dynamic_anim.color_source = mock_color_source2
|
||||
dynamic_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms)
|
||||
result = dynamic_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 4: Parameter validation
|
||||
@ -188,14 +149,15 @@ rich_anim.color_source = rainbow_color_source
|
||||
rich_anim.shift_period = 1000
|
||||
|
||||
# Start the animation
|
||||
rich_anim.start()
|
||||
rich_anim.update() # force first tick
|
||||
rich_anim.start_time = mock_engine.time_ms # Set start_time manually for direct testing
|
||||
rich_anim.start(mock_engine.time_ms)
|
||||
rich_anim.update(mock_engine.time_ms) # force first tick
|
||||
assert(rich_anim.is_running, "Animation should be running")
|
||||
|
||||
# Update and render
|
||||
rich_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = rich_anim.render(frame, mock_engine.time_ms)
|
||||
result = rich_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(result, "Render should return true")
|
||||
|
||||
# Test 6: Animation timing and synchronization
|
||||
@ -208,21 +170,12 @@ anim1.color_source = mock_color_source
|
||||
anim1.shift_period = 1000
|
||||
anim1.spatial_period = 4
|
||||
|
||||
var anim2 = animation.palette_meter_animation(mock_engine)
|
||||
anim2.color_source = mock_color_source2
|
||||
def meter_func(engine, time_ms, animation)
|
||||
return 128
|
||||
end
|
||||
anim2.value_func = meter_func
|
||||
|
||||
# Start both animations at the same time
|
||||
anim1.start_time = sync_time # Set start_time manually for direct testing
|
||||
anim1.start(sync_time)
|
||||
anim1.update(sync_time) # force first tick
|
||||
anim2.start(sync_time)
|
||||
anim2.update(sync_time) # force first tick
|
||||
|
||||
assert(anim1.start_time == sync_time, "Animation 1 should have correct start time")
|
||||
assert(anim2.start_time == sync_time, "Animation 2 should have correct start time")
|
||||
|
||||
# Test 7: Animation without color source (should handle gracefully)
|
||||
print("Test 7: Animation without color source")
|
||||
@ -231,10 +184,11 @@ no_color_anim.shift_period = 1000
|
||||
no_color_anim.spatial_period = 3
|
||||
# Note: no color_source set
|
||||
|
||||
no_color_anim.start()
|
||||
no_color_anim.start_time = mock_engine.time_ms # Set start_time manually for direct testing
|
||||
no_color_anim.start(mock_engine.time_ms)
|
||||
no_color_anim.update(mock_engine.time_ms)
|
||||
frame.clear()
|
||||
result = no_color_anim.render(frame, mock_engine.time_ms)
|
||||
result = no_color_anim.render(frame, mock_engine.time_ms, mock_engine.strip_length)
|
||||
assert(!result, "Render should return false when no color source is set")
|
||||
|
||||
# Test 8: String representation
|
||||
|
||||
@ -22,7 +22,6 @@ def test_parameter_accepts_value_providers()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = "test"
|
||||
|
||||
# Test with static integer value (using existing 'opacity' parameter with range 0-255)
|
||||
assert(test_anim.set_param("opacity", 42) == true, "Should accept static integer")
|
||||
|
||||
@ -16,7 +16,6 @@ def test_plasma_animation_basic()
|
||||
|
||||
# Test with default parameters
|
||||
var plasma_anim = animation.plasma_animation(engine)
|
||||
plasma_anim.name = "test_plasma"
|
||||
|
||||
assert(plasma_anim != nil, "PlasmaAnimation should be created")
|
||||
assert(plasma_anim.freq_x == 32, "Default freq_x should be 32")
|
||||
@ -50,7 +49,6 @@ def test_plasma_animation_custom()
|
||||
plasma_anim.priority = 15
|
||||
plasma_anim.duration = 5000
|
||||
plasma_anim.loop = false
|
||||
plasma_anim.name = "custom_plasma"
|
||||
|
||||
assert(plasma_anim.freq_x == 50, "Custom freq_x should be 50")
|
||||
assert(plasma_anim.freq_y == 40, "Custom freq_y should be 40")
|
||||
@ -74,7 +72,6 @@ def test_plasma_animation_parameters()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var plasma_anim = animation.plasma_animation(engine)
|
||||
plasma_anim.name = "param_test"
|
||||
|
||||
# Test parameter changes using virtual member assignment
|
||||
plasma_anim.freq_x = 60
|
||||
@ -109,7 +106,6 @@ def test_plasma_animation_update_render()
|
||||
plasma_anim.freq_x = 40
|
||||
plasma_anim.freq_y = 30
|
||||
plasma_anim.time_speed = 60
|
||||
plasma_anim.name = "update_test"
|
||||
|
||||
var frame = animation.frame_buffer(10)
|
||||
|
||||
@ -118,11 +114,11 @@ def test_plasma_animation_update_render()
|
||||
assert(plasma_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Test update
|
||||
var result = plasma_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
plasma_anim.update(1500)
|
||||
assert(plasma_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = plasma_anim.render(frame, 1500)
|
||||
var result = plasma_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that colors were set (should not all be black)
|
||||
@ -152,7 +148,6 @@ def test_plasma_constructors()
|
||||
var rainbow_plasma = animation.plasma_rainbow(engine)
|
||||
assert(rainbow_plasma != nil, "plasma_rainbow should create animation")
|
||||
assert(rainbow_plasma.time_speed == 50, "Rainbow plasma should have correct time_speed")
|
||||
assert(rainbow_plasma.name == "plasma_rainbow", "Rainbow plasma should have correct name")
|
||||
|
||||
# Test plasma_fast
|
||||
var fast_plasma = animation.plasma_fast(engine)
|
||||
@ -179,7 +174,6 @@ def test_plasma_tostring()
|
||||
plasma_anim.phase_y = 70
|
||||
plasma_anim.time_speed = 85
|
||||
plasma_anim.blend_mode = 1
|
||||
plasma_anim.name = "string_test"
|
||||
|
||||
var str_repr = str(plasma_anim)
|
||||
|
||||
|
||||
@ -40,25 +40,6 @@ def test_pull_lexer_basic()
|
||||
return berry_code == direct_berry_code
|
||||
end
|
||||
|
||||
def test_pull_lexer_template_mode()
|
||||
print("=== Testing Pull Lexer Template Mode ===")
|
||||
|
||||
var template_source = "animation test = solid(color=red)\n" +
|
||||
"test.opacity = 200\n" +
|
||||
"run test"
|
||||
|
||||
# Test with template mode enabled
|
||||
var pull_lexer = animation_dsl.create_lexer(template_source)
|
||||
var transpiler = animation_dsl.SimpleDSLTranspiler(pull_lexer)
|
||||
|
||||
var berry_code = transpiler.transpile_template_body()
|
||||
|
||||
print("Template Body Result:")
|
||||
print(berry_code)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def test_pull_lexer_token_access()
|
||||
print("=== Testing Pull Lexer Token Access Methods ===")
|
||||
|
||||
@ -112,7 +93,6 @@ def run_all_tests()
|
||||
|
||||
var tests = [
|
||||
test_pull_lexer_basic,
|
||||
test_pull_lexer_template_mode,
|
||||
test_pull_lexer_token_access,
|
||||
test_pull_lexer_position_info
|
||||
]
|
||||
@ -151,7 +131,6 @@ run_all_tests()
|
||||
|
||||
return {
|
||||
"test_pull_lexer_basic": test_pull_lexer_basic,
|
||||
"test_pull_lexer_template_mode": test_pull_lexer_template_mode,
|
||||
"test_pull_lexer_token_access": test_pull_lexer_token_access,
|
||||
"test_pull_lexer_position_info": test_pull_lexer_position_info,
|
||||
"run_all_tests": run_all_tests
|
||||
|
||||
@ -98,7 +98,7 @@ print(f"Color at full cycle: 0x{color_full :08x}")
|
||||
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(5)
|
||||
blue_pulse.render(frame, engine.time_ms)
|
||||
blue_pulse.render(frame, engine.time_ms, engine.strip_length)
|
||||
print(f"First pixel after rendering: 0x{frame.get_pixel_color(0) :08x}")
|
||||
|
||||
# Validate key test results
|
||||
|
||||
@ -22,7 +22,6 @@ print("Created rich palette animation with engine-only constructor")
|
||||
# Test that it's created successfully
|
||||
print(f"Animation created: {anim}")
|
||||
print(f"Animation type: {type(anim)}")
|
||||
print(f"Animation name: {anim.name}")
|
||||
|
||||
# Test 2: Set parameters using virtual member assignment (parameter forwarding)
|
||||
anim.palette = bytes("00FF0000" "80FFFF00" "FF0000FF") # Red to Yellow to Blue
|
||||
@ -61,22 +60,22 @@ print(f"Animation running: {anim.is_running}")
|
||||
|
||||
# Test 5: Test rendering
|
||||
var frame = animation.frame_buffer(5)
|
||||
anim.render(frame, engine.time_ms)
|
||||
anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
var pixel_color = frame.get_pixel_color(0)
|
||||
print(f"Rendered pixel color: {pixel_color}")
|
||||
|
||||
# Test 6: Test color changes over time
|
||||
engine.time_ms = 0
|
||||
anim.start(0)
|
||||
anim.render(frame, 0)
|
||||
anim.render(frame, 0, engine.strip_length)
|
||||
var color_t0 = frame.get_pixel_color(0)
|
||||
|
||||
engine.time_ms = 1500 # Half cycle
|
||||
anim.render(frame, 1500)
|
||||
anim.render(frame, 1500, engine.strip_length)
|
||||
var color_t1500 = frame.get_pixel_color(0)
|
||||
|
||||
engine.time_ms = 3000 # Full cycle
|
||||
anim.render(frame, 3000)
|
||||
anim.render(frame, 3000, engine.strip_length)
|
||||
var color_t3000 = frame.get_pixel_color(0)
|
||||
|
||||
print(f"Color at t=0: {color_t0}")
|
||||
@ -124,7 +123,6 @@ print(f"CSS gradient available: {bool(css_gradient)}")
|
||||
# Validate key test results
|
||||
assert(anim != nil, "Rich palette animation should be created")
|
||||
assert(type(anim) == "instance", "Animation should be an instance")
|
||||
assert(anim.name == "rich_palette", "Animation should have correct default name")
|
||||
|
||||
# Test parameter forwarding
|
||||
assert(anim.cycle_period == 2000, "Cycle period should be forwarded")
|
||||
|
||||
@ -11,13 +11,15 @@ import animation
|
||||
# Create a mock engine for testing
|
||||
class MockEngine
|
||||
var time_ms
|
||||
var strip_length
|
||||
|
||||
def init()
|
||||
self.time_ms = 0 # Start at time 0
|
||||
self.strip_length = 10 # Mock strip length
|
||||
end
|
||||
|
||||
def get_strip_length()
|
||||
return 10 # Mock strip length
|
||||
return self.strip_length
|
||||
end
|
||||
|
||||
def set_time(time)
|
||||
@ -132,28 +134,28 @@ class RichPaletteAnimationTest
|
||||
# Test at start - update engine time and get color
|
||||
mock_engine.set_time(0)
|
||||
anim.update(0)
|
||||
anim.render(frame, 0)
|
||||
anim.render(frame, 0, mock_engine.strip_length)
|
||||
var pixel_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(pixel_color != 0, true, "Start color is not zero")
|
||||
|
||||
# Test at middle - update engine time and get color
|
||||
mock_engine.set_time(500)
|
||||
anim.update(500) # 50% through cycle
|
||||
anim.render(frame, 500)
|
||||
anim.render(frame, 500, mock_engine.strip_length)
|
||||
var middle_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(middle_color != 0, true, "Middle color is not zero")
|
||||
|
||||
# Test at end - update engine time and get color
|
||||
mock_engine.set_time(1000)
|
||||
anim.update(1000) # 100% through cycle
|
||||
anim.render(frame, 1000)
|
||||
anim.render(frame, 1000, mock_engine.strip_length)
|
||||
var end_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(end_color != 0, true, "End color is not zero")
|
||||
|
||||
# Test looping - should be back to start color
|
||||
mock_engine.set_time(2000)
|
||||
anim.update(2000) # After another full cycle
|
||||
anim.render(frame, 2000)
|
||||
anim.render(frame, 2000, mock_engine.strip_length)
|
||||
var loop_color = frame.get_pixel_color(0)
|
||||
self.assert_equal(loop_color, pixel_color, "Loop color matches start color")
|
||||
|
||||
|
||||
@ -232,11 +232,11 @@ def test_scale_animation_update_render()
|
||||
scale_anim.start(1000)
|
||||
|
||||
# Test update
|
||||
var result = scale_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
scale_anim.update(1500)
|
||||
assert(scale_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = scale_anim.render(frame, 1500)
|
||||
var result = scale_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that colors were calculated
|
||||
|
||||
@ -34,7 +34,6 @@ def test_multiple_sequence_managers()
|
||||
red_anim.duration = 0
|
||||
red_anim.loop = false
|
||||
red_anim.opacity = 255
|
||||
red_anim.name = "red"
|
||||
|
||||
var green_provider = animation.static_color(engine)
|
||||
green_provider.color = 0xFF00FF00
|
||||
@ -44,7 +43,6 @@ def test_multiple_sequence_managers()
|
||||
green_anim.duration = 0
|
||||
green_anim.loop = false
|
||||
green_anim.opacity = 255
|
||||
green_anim.name = "green"
|
||||
|
||||
var blue_provider = animation.static_color(engine)
|
||||
blue_provider.color = 0xFF0000FF
|
||||
@ -54,7 +52,6 @@ def test_multiple_sequence_managers()
|
||||
blue_anim.duration = 0
|
||||
blue_anim.loop = false
|
||||
blue_anim.opacity = 255
|
||||
blue_anim.name = "blue"
|
||||
|
||||
# Create different sequences for each manager using fluent interface
|
||||
seq_manager1.push_play_step(red_anim, 2000)
|
||||
@ -105,7 +102,6 @@ def test_sequence_manager_coordination()
|
||||
anim1.duration = 0
|
||||
anim1.loop = false
|
||||
anim1.opacity = 255
|
||||
anim1.name = "anim1"
|
||||
|
||||
var provider2 = animation.static_color(engine)
|
||||
provider2.color = 0xFF00FF00
|
||||
@ -115,7 +111,6 @@ def test_sequence_manager_coordination()
|
||||
anim2.duration = 0
|
||||
anim2.loop = false
|
||||
anim2.opacity = 255
|
||||
anim2.name = "anim2"
|
||||
|
||||
# Create sequences that will overlap using fluent interface
|
||||
seq_manager1.push_play_step(anim1, 3000) # 3 seconds
|
||||
@ -172,7 +167,6 @@ def test_sequence_manager_engine_integration()
|
||||
test_anim1.duration = 0
|
||||
test_anim1.loop = false
|
||||
test_anim1.opacity = 255
|
||||
test_anim1.name = "test1"
|
||||
|
||||
var provider2 = animation.static_color(engine)
|
||||
provider2.color = 0xFF00FF00
|
||||
@ -182,7 +176,6 @@ def test_sequence_manager_engine_integration()
|
||||
test_anim2.duration = 0
|
||||
test_anim2.loop = false
|
||||
test_anim2.opacity = 255
|
||||
test_anim2.name = "test2"
|
||||
|
||||
# Create sequences using fluent interface
|
||||
seq_manager1.push_play_step(test_anim1, 1000)
|
||||
@ -274,7 +267,6 @@ def test_sequence_manager_clear_all()
|
||||
test_anim1.duration = 0
|
||||
test_anim1.loop = false
|
||||
test_anim1.opacity = 255
|
||||
test_anim1.name = "test1"
|
||||
|
||||
var provider2 = animation.static_color(engine)
|
||||
provider2.color = 0xFF00FF00
|
||||
@ -284,7 +276,6 @@ def test_sequence_manager_clear_all()
|
||||
test_anim2.duration = 0
|
||||
test_anim2.loop = false
|
||||
test_anim2.opacity = 255
|
||||
test_anim2.name = "test2"
|
||||
|
||||
# Create sequences using fluent interface
|
||||
seq_manager1.push_play_step(test_anim1, 5000)
|
||||
@ -341,7 +332,6 @@ def test_sequence_manager_stress()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = false
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = f"anim{i}"
|
||||
|
||||
# Create sequence using fluent interface
|
||||
seq_managers[i].push_play_step(test_anim, (i + 1) * 500) # Different durations
|
||||
|
||||
@ -42,7 +42,6 @@ def test_sequence_manager_step_creation()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
|
||||
# Test fluent interface step creation
|
||||
var seq_manager = animation.sequence_manager(engine)
|
||||
@ -89,7 +88,6 @@ def test_sequence_manager_execution()
|
||||
anim1.priority = 0
|
||||
anim1.duration = 0
|
||||
anim1.loop = true
|
||||
anim1.name = "anim1"
|
||||
|
||||
var color_provider2 = animation.static_color(engine)
|
||||
color_provider2.color = 0xFF00FF00
|
||||
@ -98,7 +96,6 @@ def test_sequence_manager_execution()
|
||||
anim2.priority = 0
|
||||
anim2.duration = 0
|
||||
anim2.loop = true
|
||||
anim2.name = "anim2"
|
||||
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(anim1, 1000)
|
||||
@ -137,7 +134,6 @@ def test_sequence_manager_timing()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
|
||||
# Create simple sequence with timed steps using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000) # 1 second
|
||||
@ -191,7 +187,6 @@ def test_sequence_manager_step_info()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 2000)
|
||||
.push_wait_step(1000)
|
||||
@ -221,7 +216,6 @@ def test_sequence_manager_stop()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 5000)
|
||||
|
||||
@ -259,7 +253,6 @@ def test_sequence_manager_is_running()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000)
|
||||
|
||||
@ -294,7 +287,6 @@ def test_sequence_manager_assignment_steps()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
test_anim.opacity = 255 # Initial opacity
|
||||
|
||||
# Create brightness value provider for assignment
|
||||
@ -353,7 +345,6 @@ def test_sequence_manager_complex_sequence()
|
||||
red_anim.priority = 0
|
||||
red_anim.duration = 0
|
||||
red_anim.loop = true
|
||||
red_anim.name = "red"
|
||||
|
||||
var green_provider = animation.static_color(engine)
|
||||
green_provider.color = 0xFF00FF00
|
||||
@ -362,7 +353,6 @@ def test_sequence_manager_complex_sequence()
|
||||
green_anim.priority = 0
|
||||
green_anim.duration = 0
|
||||
green_anim.loop = true
|
||||
green_anim.name = "green"
|
||||
|
||||
var blue_provider = animation.static_color(engine)
|
||||
blue_provider.color = 0xFF0000FF
|
||||
@ -371,7 +361,6 @@ def test_sequence_manager_complex_sequence()
|
||||
blue_anim.priority = 0
|
||||
blue_anim.duration = 0
|
||||
blue_anim.loop = true
|
||||
blue_anim.name = "blue"
|
||||
|
||||
# Create complex sequence using fluent interface
|
||||
seq_manager.push_play_step(red_anim, 1000) # Play red for 1s
|
||||
@ -436,7 +425,6 @@ def test_sequence_manager_integration()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000)
|
||||
|
||||
@ -517,7 +505,6 @@ def test_sequence_manager_repeat_execution_with_functions()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test_repeat"
|
||||
|
||||
# Create a function that returns repeat count (simulating palette_size)
|
||||
var repeat_count_func = def (engine) return 3 end
|
||||
@ -606,7 +593,6 @@ def test_sequence_manager_dynamic_repeat_changes()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "dynamic_test"
|
||||
|
||||
# Create dynamic repeat count that changes based on external state
|
||||
var external_state = {"multiplier": 2}
|
||||
@ -719,7 +705,6 @@ def test_sequence_manager_zero_iterations()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
|
||||
# Track execution count
|
||||
var execution_count = 0
|
||||
@ -842,7 +827,6 @@ def test_sequence_manager_boolean_repeat_counts()
|
||||
test_anim.priority = 0
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test_bool"
|
||||
|
||||
# Test 1: repeat_count = true (should execute once, true converts to 1)
|
||||
var execution_count = 0
|
||||
|
||||
@ -116,11 +116,11 @@ def test_shift_animation_update_render()
|
||||
assert(shift_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Test update
|
||||
var result = shift_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
shift_anim.update(1500)
|
||||
assert(shift_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = shift_anim.render(frame, 1500)
|
||||
var result = shift_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that colors were set
|
||||
|
||||
@ -25,7 +25,6 @@ anim.priority = 10
|
||||
anim.duration = 0 # Infinite
|
||||
anim.loop = false
|
||||
anim.opacity = 255
|
||||
anim.name = "test_solid"
|
||||
|
||||
print("Set parameters using virtual member assignment")
|
||||
|
||||
@ -42,7 +41,6 @@ print(f"Priority: {anim.priority}")
|
||||
print(f"Opacity: {anim.opacity}")
|
||||
print(f"Duration: {anim.duration}")
|
||||
print(f"Loop: {anim.loop}")
|
||||
print(f"Name: {anim.name}")
|
||||
|
||||
# Test 2: Create animation with red color
|
||||
var red_anim = animation.solid(engine)
|
||||
@ -56,7 +54,6 @@ blue_anim.priority = 20
|
||||
blue_anim.duration = 5000
|
||||
blue_anim.loop = true
|
||||
blue_anim.opacity = 200
|
||||
blue_anim.name = "test_blue"
|
||||
print(f"Blue animation - priority: {blue_anim.priority}, duration: {blue_anim.duration}, loop: {blue_anim.loop}")
|
||||
|
||||
# Test 4: Create animation with ValueProvider
|
||||
@ -69,7 +66,7 @@ print("Green animation with color provider created")
|
||||
# Test 5: Test rendering
|
||||
var frame = animation.frame_buffer(5)
|
||||
red_anim.start() # Uses engine time automatically
|
||||
red_anim.render(frame, engine.time_ms)
|
||||
red_anim.render(frame, engine.time_ms, engine.strip_length)
|
||||
print("Rendering test completed")
|
||||
|
||||
# Test 6: Test engine time usage
|
||||
|
||||
@ -20,7 +20,6 @@ def test_unified_solid_function()
|
||||
red_solid.duration = 0
|
||||
red_solid.loop = false
|
||||
red_solid.opacity = 255
|
||||
red_solid.name = "solid"
|
||||
|
||||
# Verify it's created successfully
|
||||
assert(red_solid != nil, "solid() should return a valid object")
|
||||
@ -31,7 +30,6 @@ def test_unified_solid_function()
|
||||
assert(red_solid.opacity == 255, "Should have opacity 255")
|
||||
assert(red_solid.duration == 0, "Should have infinite duration")
|
||||
assert(red_solid.loop == false, "Should have no looping")
|
||||
assert(red_solid.name == "solid", "Should have name 'solid'")
|
||||
|
||||
print("✅ Basic solid animation creation test passed")
|
||||
end
|
||||
@ -46,14 +44,12 @@ def test_solid_with_all_parameters()
|
||||
blue_solid.duration = 5000
|
||||
blue_solid.loop = true
|
||||
blue_solid.opacity = 200
|
||||
blue_solid.name = "test_blue"
|
||||
|
||||
# Verify all parameters are set correctly
|
||||
assert(blue_solid.priority == 20, "Should have priority 20")
|
||||
assert(blue_solid.opacity == 200, "Should have opacity 200")
|
||||
assert(blue_solid.duration == 5000, "Should have duration 5000")
|
||||
assert(blue_solid.loop == true, "Should have loop enabled")
|
||||
assert(blue_solid.name == "test_blue", "Should have name 'test_blue'")
|
||||
|
||||
print("✅ Solid with all parameters test passed")
|
||||
end
|
||||
@ -68,12 +64,10 @@ def test_solid_composition()
|
||||
green_solid.duration = 0
|
||||
green_solid.loop = false
|
||||
green_solid.opacity = 255
|
||||
green_solid.name = "green_solid"
|
||||
|
||||
# Create another animation for comparison (if pulse exists with new API)
|
||||
var another_solid = animation.solid(engine)
|
||||
another_solid.color = 0xFFFFFF00 # Yellow
|
||||
another_solid.name = "yellow_solid"
|
||||
|
||||
# Verify both animations are created
|
||||
assert(green_solid != nil, "Green solid should be created")
|
||||
@ -97,7 +91,6 @@ def test_solid_color_provider()
|
||||
yellow_solid.duration = 0
|
||||
yellow_solid.loop = false
|
||||
yellow_solid.opacity = 255
|
||||
yellow_solid.name = "yellow_solid"
|
||||
|
||||
# Verify it works with color providers
|
||||
assert(yellow_solid != nil, "Should create animation with color provider")
|
||||
@ -120,14 +113,13 @@ def test_solid_rendering()
|
||||
red_solid.duration = 0
|
||||
red_solid.loop = false
|
||||
red_solid.opacity = 255
|
||||
red_solid.name = "red_solid"
|
||||
|
||||
# Create a frame buffer
|
||||
var frame = animation.frame_buffer(5)
|
||||
|
||||
# Start and render the animation (uses engine time)
|
||||
red_solid.start()
|
||||
var result = red_solid.render(frame, engine.time_ms)
|
||||
var result = red_solid.render(frame, engine.time_ms, engine.strip_length)
|
||||
|
||||
# Verify rendering worked
|
||||
assert(result == true, "Render should return true")
|
||||
|
||||
@ -49,7 +49,6 @@ def test_sparkle_animation_custom()
|
||||
sparkle_anim.priority = 15
|
||||
sparkle_anim.duration = 5000
|
||||
sparkle_anim.loop = false
|
||||
sparkle_anim.name = "custom_sparkle"
|
||||
|
||||
assert(sparkle_anim.back_color == 0xFF111111, "Custom background should be set")
|
||||
assert(sparkle_anim.density == 80, "Custom density should be 80")
|
||||
@ -73,7 +72,6 @@ def test_sparkle_animation_parameters()
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var sparkle_anim = animation.sparkle_animation(engine)
|
||||
sparkle_anim.name = "param_test"
|
||||
|
||||
# Test parameter changes using virtual member assignment
|
||||
sparkle_anim.density = 100
|
||||
@ -112,7 +110,6 @@ def test_sparkle_animation_update_render()
|
||||
sparkle_anim.sparkle_duration = 30
|
||||
sparkle_anim.min_brightness = 100
|
||||
sparkle_anim.max_brightness = 255
|
||||
sparkle_anim.name = "update_test"
|
||||
|
||||
var frame = animation.frame_buffer(10)
|
||||
|
||||
@ -128,7 +125,7 @@ def test_sparkle_animation_update_render()
|
||||
end
|
||||
|
||||
# Test render
|
||||
var result = sparkle_anim.render(frame, 1500)
|
||||
var result = sparkle_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# With high density (255), we should have some sparkles
|
||||
@ -159,12 +156,10 @@ def test_sparkle_constructors()
|
||||
var white_sparkle = animation.sparkle_white(engine)
|
||||
assert(white_sparkle != nil, "sparkle_white should create animation")
|
||||
assert(white_sparkle.color == 0xFFFFFFFF, "White sparkle should have white color")
|
||||
assert(white_sparkle.name == "sparkle_white", "White sparkle should have correct name")
|
||||
|
||||
# Test sparkle_rainbow
|
||||
var rainbow_sparkle = animation.sparkle_rainbow(engine)
|
||||
assert(rainbow_sparkle != nil, "sparkle_rainbow should create animation")
|
||||
assert(rainbow_sparkle.name == "sparkle_rainbow", "Rainbow sparkle should have correct name")
|
||||
# Check that color is set to a provider (not the default white color)
|
||||
var color_param = rainbow_sparkle.get_param("color")
|
||||
assert(color_param != nil, "Rainbow sparkle should have color parameter set")
|
||||
@ -190,7 +185,6 @@ def test_sparkle_tostring()
|
||||
var sparkle_anim = animation.sparkle_animation(engine)
|
||||
sparkle_anim.density = 75
|
||||
sparkle_anim.fade_speed = 45
|
||||
sparkle_anim.name = "string_test"
|
||||
|
||||
var str_repr = str(sparkle_anim)
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ def test_static_value_provider_interface()
|
||||
var provider = animation.static_value(engine)
|
||||
|
||||
# Test default methods
|
||||
assert(provider.get_value(1000) == nil, "Default get_value should return nil (no value set)")
|
||||
assert(provider.update(1000) == false, "Update should return false")
|
||||
assert(provider.produce_value("test", 1000) == nil, "Default produce_value should return nil (no value set)")
|
||||
provider.update(1000) # update() does not return a value
|
||||
|
||||
# Test setting a value
|
||||
provider.value = 42
|
||||
assert(provider.get_value(1000) == 42, "Should return set value")
|
||||
assert(provider.get_value(2000) == 42, "Should return same value regardless of time")
|
||||
assert(provider.produce_value("test", 1000) == 42, "Should return set value")
|
||||
assert(provider.produce_value("test", 2000) == 42, "Should return same value regardless of time")
|
||||
|
||||
print("✓ StaticValueProvider interface test passed")
|
||||
end
|
||||
@ -39,24 +39,24 @@ def test_static_value_provider_types()
|
||||
# Test with integer
|
||||
var int_provider = animation.static_value(engine)
|
||||
int_provider.value = 123
|
||||
assert(int_provider.get_value(1000) == 123, "Should handle integer values")
|
||||
assert(int_provider.produce_value("test", 1000) == 123, "Should handle integer values")
|
||||
|
||||
# Test with string
|
||||
var str_provider = animation.static_value(engine)
|
||||
str_provider.value = "hello"
|
||||
assert(str_provider.get_value(1000) == "hello", "Should handle string values")
|
||||
assert(str_provider.produce_value("test", 1000) == "hello", "Should handle string values")
|
||||
|
||||
# Test with color (hex value)
|
||||
var color_provider = animation.static_value(engine)
|
||||
color_provider.value = 0xFF00FF00
|
||||
assert(color_provider.get_value(1000) == 0xFF00FF00, "Should handle color values")
|
||||
assert(color_provider.produce_value("test", 1000) == 0xFF00FF00, "Should handle color values")
|
||||
|
||||
print("✓ StaticValueProvider types test passed")
|
||||
end
|
||||
|
||||
# Test universal get_XXX methods via member() construct
|
||||
def test_universal_get_methods()
|
||||
print("Testing universal get_XXX methods...")
|
||||
# Test produce_value method with different parameter names
|
||||
def test_produce_value_method()
|
||||
print("Testing produce_value method...")
|
||||
|
||||
# Create engine for testing
|
||||
var strip = global.Leds()
|
||||
@ -65,29 +65,13 @@ def test_universal_get_methods()
|
||||
var provider = animation.static_value(engine)
|
||||
provider.value = 99
|
||||
|
||||
# Test various get_XXX methods
|
||||
var get_pulse_size = provider.("get_pulse_size")
|
||||
assert(type(get_pulse_size) == "function", "Should return function for get_pulse_size")
|
||||
assert(get_pulse_size(1000) == 99, "get_pulse_size should return static value")
|
||||
# Test produce_value with various parameter names - should return same value
|
||||
assert(provider.produce_value("pulse_size", 1000) == 99, "produce_value should return static value for pulse_size")
|
||||
assert(provider.produce_value("pos", 1000) == 99, "produce_value should return static value for pos")
|
||||
assert(provider.produce_value("color", 1000) == 99, "produce_value should return static value for color")
|
||||
assert(provider.produce_value("any_param", 2000) == 99, "produce_value should return static value for any param name")
|
||||
|
||||
var get_pos = provider.("get_pos")
|
||||
assert(type(get_pos) == "function", "Should return function for get_pos")
|
||||
assert(get_pos(1000) == 99, "get_pos should return static value")
|
||||
|
||||
var get_color = provider.("get_color")
|
||||
assert(type(get_color) == "function", "Should return function for get_color")
|
||||
assert(get_color(1000) == 99, "get_color should return static value")
|
||||
|
||||
# Test that non-get methods return undefined
|
||||
try
|
||||
var other_method = provider.("some_other_method")
|
||||
# Should return undefined module, not a function
|
||||
assert(type(other_method) != "function", "Non-get methods should not return functions")
|
||||
except .. as e
|
||||
# Exception is also acceptable
|
||||
end
|
||||
|
||||
print("✓ Universal get_XXX methods test passed")
|
||||
print("✓ produce_value method test passed")
|
||||
end
|
||||
|
||||
# Test comparison operators
|
||||
@ -149,17 +133,17 @@ def test_value_changes()
|
||||
var provider = animation.static_value(engine)
|
||||
|
||||
# Test initial state
|
||||
assert(provider.get_value(1000) == nil, "Initial value should be nil")
|
||||
assert(provider.produce_value("test", 1000) == nil, "Initial value should be nil")
|
||||
|
||||
# Test setting and changing values
|
||||
provider.value = 10
|
||||
assert(provider.get_value(1000) == 10, "Should return first set value")
|
||||
assert(provider.produce_value("test", 1000) == 10, "Should return first set value")
|
||||
|
||||
provider.value = 20
|
||||
assert(provider.get_value(1000) == 20, "Should return updated value")
|
||||
assert(provider.produce_value("test", 1000) == 20, "Should return updated value")
|
||||
|
||||
provider.value = "changed"
|
||||
assert(provider.get_value(1000) == "changed", "Should handle type changes")
|
||||
assert(provider.produce_value("test", 1000) == "changed", "Should handle type changes")
|
||||
|
||||
print("✓ Value changes test passed")
|
||||
end
|
||||
@ -189,7 +173,7 @@ def run_static_value_provider_tests()
|
||||
try
|
||||
test_static_value_provider_interface()
|
||||
test_static_value_provider_types()
|
||||
test_universal_get_methods()
|
||||
test_produce_value_method()
|
||||
test_comparison_operators()
|
||||
test_parameterized_object_integration()
|
||||
test_value_changes()
|
||||
|
||||
@ -75,6 +75,7 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/crenel_position_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/beacon_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/gradient_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/palette_meter_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/noise_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/plasma_animation_test.be",
|
||||
# "lib/libesp32/berry_animation/src/tests/sparkle_animation_test.be",
|
||||
|
||||
@ -22,7 +22,6 @@ twinkle.fade_speed = 180
|
||||
twinkle.min_brightness = 32
|
||||
twinkle.max_brightness = 255
|
||||
twinkle.priority = 10
|
||||
twinkle.name = "test_twinkle"
|
||||
print(f"Created twinkle animation: {twinkle}")
|
||||
print(f"Initial state - running: {twinkle.is_running}, priority: {twinkle.priority}")
|
||||
|
||||
@ -62,7 +61,7 @@ while dsl_cycle < 10
|
||||
|
||||
dsl_twinkle.update(dsl_test_time)
|
||||
dsl_frame.clear()
|
||||
dsl_twinkle.render(dsl_frame, dsl_test_time)
|
||||
dsl_twinkle.render(dsl_frame, dsl_test_time, engine.strip_length)
|
||||
|
||||
var dsl_pixels_lit = 0
|
||||
var i = 0
|
||||
@ -200,7 +199,7 @@ while cycle < 10
|
||||
|
||||
# Clear and render
|
||||
render_frame.clear()
|
||||
var rendered = twinkle.render(render_frame, test_time)
|
||||
var rendered = twinkle.render(render_frame, test_time, engine.strip_length)
|
||||
|
||||
# Count non-black pixels
|
||||
var non_black_pixels = 0
|
||||
@ -245,7 +244,6 @@ deterministic_twinkle.fade_speed = 100
|
||||
deterministic_twinkle.min_brightness = 128
|
||||
deterministic_twinkle.max_brightness = 255
|
||||
deterministic_twinkle.priority = 10
|
||||
deterministic_twinkle.name = "deterministic"
|
||||
deterministic_twinkle.start()
|
||||
|
||||
# Force a specific random seed for reproducible results
|
||||
@ -262,7 +260,7 @@ while det_cycle < 5
|
||||
deterministic_twinkle.update(det_time)
|
||||
|
||||
det_frame.clear()
|
||||
deterministic_twinkle.render(det_frame, det_time)
|
||||
deterministic_twinkle.render(det_frame, det_time, engine.strip_length)
|
||||
|
||||
var det_non_black = 0
|
||||
i = 0
|
||||
@ -351,7 +349,6 @@ high_density_twinkle.fade_speed = 50
|
||||
high_density_twinkle.min_brightness = 200
|
||||
high_density_twinkle.max_brightness = 255
|
||||
high_density_twinkle.priority = 10
|
||||
high_density_twinkle.name = "high_density"
|
||||
high_density_twinkle.start()
|
||||
|
||||
var hd_frame = animation.frame_buffer(10)
|
||||
@ -364,7 +361,7 @@ while hd_cycle < 3
|
||||
high_density_twinkle.update(hd_time)
|
||||
|
||||
hd_frame.clear()
|
||||
high_density_twinkle.render(hd_frame, hd_time)
|
||||
high_density_twinkle.render(hd_frame, hd_time, engine.strip_length)
|
||||
|
||||
var hd_non_black = 0
|
||||
i = 0
|
||||
@ -390,7 +387,7 @@ tiny_twinkle.density = 200
|
||||
tiny_twinkle.start()
|
||||
tiny_twinkle.update(current_time + 167)
|
||||
var tiny_frame = animation.frame_buffer(1)
|
||||
tiny_twinkle.render(tiny_frame)
|
||||
tiny_twinkle.render(tiny_frame, current_time, tiny_engine.strip_length)
|
||||
print("Tiny twinkle (1 pixel) created and rendered successfully")
|
||||
|
||||
# Zero density
|
||||
@ -399,7 +396,7 @@ no_twinkle.density = 0
|
||||
no_twinkle.start()
|
||||
no_twinkle.update(current_time + 334)
|
||||
var no_frame = animation.frame_buffer(10)
|
||||
no_twinkle.render(no_frame)
|
||||
no_twinkle.render(no_frame, current_time, engine.strip_length)
|
||||
print("No twinkle (0 density) created and rendered successfully")
|
||||
|
||||
# Maximum density
|
||||
@ -408,7 +405,7 @@ max_twinkle.density = 255
|
||||
max_twinkle.start()
|
||||
max_twinkle.update(current_time + 501)
|
||||
var max_frame = animation.frame_buffer(10)
|
||||
max_twinkle.render(max_frame)
|
||||
max_twinkle.render(max_frame, current_time, engine.strip_length)
|
||||
print("Max twinkle (255 density) created and rendered successfully")
|
||||
|
||||
# Test 17: Alpha-Based Fading Verification
|
||||
|
||||
@ -110,15 +110,17 @@ def test_wave_animation_update_render()
|
||||
var frame = animation.frame_buffer(10)
|
||||
|
||||
# Start animation
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
wave_anim.start_time = 1000 # Set start_time manually for direct testing
|
||||
wave_anim.start(1000)
|
||||
assert(wave_anim.is_running == true, "Animation should be running after start")
|
||||
|
||||
# Test update
|
||||
var result = wave_anim.update(1500)
|
||||
assert(result == true, "Update should return true for running animation")
|
||||
wave_anim.update(1500)
|
||||
assert(wave_anim.is_running == true, "Animation should still be running after update")
|
||||
|
||||
# Test render
|
||||
result = wave_anim.render(frame, 1500)
|
||||
var result = wave_anim.render(frame, 1500, engine.strip_length)
|
||||
assert(result == true, "Render should return true for running animation")
|
||||
|
||||
# Check that colors were set (should not all be black with high amplitude)
|
||||
@ -156,9 +158,10 @@ def test_wave_types()
|
||||
wave_anim.frequency = 50
|
||||
wave_anim.wave_speed = 0 # No movement for testing
|
||||
|
||||
wave_anim.start_time = 1000 # Set start_time manually for direct testing
|
||||
wave_anim.start(1000)
|
||||
wave_anim.update(1000)
|
||||
var result = wave_anim.render(frame, 1000)
|
||||
var result = wave_anim.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, f"Wave type {wave_types[i]} should render successfully")
|
||||
|
||||
i += 1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user