Berry animation improvements to sequence (#23858)
This commit is contained in:
parent
d20cebb654
commit
6d4b49c88b
@ -24,7 +24,7 @@ aurora_base_.transition_type = animation.SINE # transition type (explicit for c
|
||||
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(aurora_base_, nil) # infinite duration (no 'for' clause)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.add(demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ breathing_.opacity = (def (engine)
|
||||
return provider
|
||||
end)(engine)
|
||||
# Start the animation
|
||||
engine.add_animation(breathing_)
|
||||
engine.add(breathing_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -140,16 +140,16 @@ stripe10_.pos = (def (engine)
|
||||
return provider
|
||||
end)(engine)
|
||||
# Start all stripes
|
||||
engine.add_animation(stripe1_)
|
||||
engine.add_animation(stripe2_)
|
||||
engine.add_animation(stripe3_)
|
||||
engine.add_animation(stripe4_)
|
||||
engine.add_animation(stripe5_)
|
||||
engine.add_animation(stripe6_)
|
||||
engine.add_animation(stripe7_)
|
||||
engine.add_animation(stripe8_)
|
||||
engine.add_animation(stripe9_)
|
||||
engine.add_animation(stripe10_)
|
||||
engine.add(stripe1_)
|
||||
engine.add(stripe2_)
|
||||
engine.add(stripe3_)
|
||||
engine.add(stripe4_)
|
||||
engine.add(stripe5_)
|
||||
engine.add(stripe6_)
|
||||
engine.add(stripe7_)
|
||||
engine.add(stripe8_)
|
||||
engine.add(stripe9_)
|
||||
engine.add(stripe10_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -61,11 +61,11 @@ garland_.tail_length = 6 # garland length (tail length)
|
||||
garland_.speed = 4000 # slow movement (speed)
|
||||
garland_.priority = 5
|
||||
# Start all animations
|
||||
engine.add_animation(tree_base_)
|
||||
engine.add_animation(ornaments_)
|
||||
engine.add_animation(tree_star_)
|
||||
engine.add_animation(snow_sparkles_)
|
||||
engine.add_animation(garland_)
|
||||
engine.add(tree_base_)
|
||||
engine.add(ornaments_)
|
||||
engine.add(tree_star_)
|
||||
engine.add(snow_sparkles_)
|
||||
engine.add(garland_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -36,10 +36,10 @@ comet_sparkles_.density = 8 # density (moderate sparkles)
|
||||
comet_sparkles_.twinkle_speed = 400 # twinkle speed (quick sparkle)
|
||||
comet_sparkles_.priority = 8
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(comet_main_)
|
||||
engine.add_animation(comet_secondary_)
|
||||
engine.add_animation(comet_sparkles_)
|
||||
engine.add(background_)
|
||||
engine.add(comet_main_)
|
||||
engine.add(comet_secondary_)
|
||||
engine.add(comet_sparkles_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -31,8 +31,8 @@ stream2_.priority = 5
|
||||
stream1_.tail_length = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 5 end)
|
||||
stream2_.opacity = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) * 4 end)
|
||||
# Run both animations
|
||||
engine.add_animation(stream1_)
|
||||
engine.add_animation(stream2_)
|
||||
engine.add(stream1_)
|
||||
engine.add(stream2_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ def cylon_effect_template(engine, eye_color_, back_color_, duration_)
|
||||
eye_animation_.beacon_size = 3 # small 3 pixels eye
|
||||
eye_animation_.slew_size = 2 # with 2 pixel shading around
|
||||
eye_animation_.priority = 5
|
||||
engine.add_animation(eye_animation_)
|
||||
engine.add(eye_animation_)
|
||||
end
|
||||
|
||||
animation.register_user_function('cylon_effect', cylon_effect_template)
|
||||
|
||||
@ -38,11 +38,11 @@ red_eye_.beacon_size = 3 # small 3 pixels eye
|
||||
red_eye_.slew_size = 2 # with 2 pixel shading around
|
||||
var cylon_eye_ = animation.SequenceManager(engine, -1)
|
||||
.push_play_step(red_eye_, eye_duration_) # use COSINE movement
|
||||
.push_assign_step(def (engine) red_eye_.pos = triangle_val_ end) # switch to TRIANGLE
|
||||
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end) # switch to TRIANGLE
|
||||
.push_play_step(red_eye_, eye_duration_)
|
||||
.push_assign_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
||||
.push_assign_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
||||
engine.add_sequence_manager(cylon_eye_)
|
||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # switch back to COSINE for next iteration
|
||||
.push_closure_step(def (engine) eye_color_.next = 1 end) # advance to next color
|
||||
engine.add(cylon_eye_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ red_eye_.pos = (def (engine)
|
||||
end)(engine)
|
||||
red_eye_.beacon_size = 3 # small 3 pixels eye
|
||||
red_eye_.slew_size = 2 # with 2 pixel shading around
|
||||
engine.add_animation(red_eye_)
|
||||
engine.add(red_eye_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -18,16 +18,17 @@ fire_color_.palette = fire_colors_
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = 0xFF000088
|
||||
background_.priority = 20
|
||||
var eye_mask_ = animation.beacon_animation(engine)
|
||||
eye_mask_.color = 0x00000000
|
||||
eye_mask_.back_color = 0xFFFFFFFF
|
||||
eye_mask_.pos = (def (engine)
|
||||
var eye_pos_ = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = (-1)
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = 3000
|
||||
provider.duration = 6000
|
||||
return provider
|
||||
end)(engine)
|
||||
var eye_mask_ = animation.beacon_animation(engine)
|
||||
eye_mask_.color = 0xFFFFFFFF
|
||||
eye_mask_.back_color = 0x00000000
|
||||
eye_mask_.pos = eye_pos_
|
||||
eye_mask_.beacon_size = 4 # small 3 pixels eye
|
||||
eye_mask_.slew_size = 2 # with 2 pixel shading around
|
||||
eye_mask_.priority = 5
|
||||
@ -35,8 +36,8 @@ var fire_pattern_ = animation.palette_gradient_animation(engine)
|
||||
fire_pattern_.color_source = fire_color_
|
||||
fire_pattern_.spatial_period = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 4 end)
|
||||
fire_pattern_.opacity = eye_mask_
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(fire_pattern_)
|
||||
engine.add(background_)
|
||||
engine.add(fire_pattern_)
|
||||
engine.start()
|
||||
|
||||
|
||||
@ -57,10 +58,11 @@ color fire_color = rich_palette(palette=fire_colors)
|
||||
animation background = solid(color=0x000088, priority=20)
|
||||
run background
|
||||
|
||||
set eye_pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 6s)
|
||||
animation eye_mask = beacon_animation(
|
||||
color = transparent
|
||||
back_color = white
|
||||
pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 3s)
|
||||
color = white
|
||||
back_color = transparent
|
||||
pos = eye_pos
|
||||
beacon_size = 4 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 5
|
||||
|
||||
@ -0,0 +1,138 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: demo_shutter_rainbow.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: shutter_left_right
|
||||
def shutter_left_right_template(engine, colors_, duration_)
|
||||
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 = duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = colors_
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = colors_
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
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
|
||||
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 (self) return self.resolve(strip_len_) - self.resolve(shutter_size_) end)
|
||||
shutter_rl_animation_.slew_size = 0
|
||||
shutter_rl_animation_.priority = 5
|
||||
var shutter_seq_ = animation.SequenceManager(engine)
|
||||
#repeat col1.palette_size times {
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, 7)
|
||||
.push_closure_step(def (engine) shutter_size_.start(engine.time_ms) end)
|
||||
.push_play_step(shutter_lr_animation_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
.push_repeat_subsequence(animation.SequenceManager(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_, duration_)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
)
|
||||
engine.add(shutter_seq_)
|
||||
end
|
||||
|
||||
animation.register_user_function('shutter_left_right', shutter_left_right_template)
|
||||
|
||||
var Violet_ = 0xFF112233
|
||||
var rainbow_with_white_ = bytes("FFFF0000" "FFFFA500" "FFFFFF00" "FF008000" "FF0000FF" "FF4B0082" "FFFFFFFF")
|
||||
shutter_left_right_template(engine, rainbow_with_white_, 1500)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
|
||||
template shutter_left_right {
|
||||
param colors type palette
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
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 col1.palette_size times {
|
||||
repeat 7 times {
|
||||
reset shutter_size
|
||||
play shutter_lr_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
reset shutter_size
|
||||
play shutter_rl_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
color Violet = 0x112233
|
||||
|
||||
palette rainbow_with_white = [
|
||||
red
|
||||
orange
|
||||
yellow
|
||||
green
|
||||
blue
|
||||
indigo
|
||||
white
|
||||
]
|
||||
|
||||
shutter_left_right(rainbow_with_white, 1.5s)
|
||||
|
||||
-#
|
||||
@ -0,0 +1,84 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: demo_shutter_rainbow2.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var duration_ = 3000
|
||||
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 = duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var col1_ = animation.color_cycle(engine)
|
||||
col1_.palette = animation.PALETTE_RAINBOW
|
||||
col1_.cycle_period = 0
|
||||
var col2_ = animation.color_cycle(engine)
|
||||
col2_.palette = animation.PALETTE_RAINBOW
|
||||
col2_.cycle_period = 0
|
||||
col2_.next = 1
|
||||
var shutter_animation_ = animation.beacon_animation(engine)
|
||||
shutter_animation_.color = col1_
|
||||
shutter_animation_.back_color = col2_
|
||||
shutter_animation_.pos = 0
|
||||
shutter_animation_.beacon_size = shutter_size_
|
||||
shutter_animation_.slew_size = 0
|
||||
shutter_animation_.priority = 5
|
||||
log(f"foobar", 3)
|
||||
var shutter_run_ = animation.SequenceManager(engine, -1)
|
||||
.push_closure_step(def (engine) log(f"before", 3) end)
|
||||
.push_play_step(shutter_animation_, duration_)
|
||||
.push_closure_step(def (engine) log(f"after", 3) end)
|
||||
.push_closure_step(def (engine) col1_.next = 1 end)
|
||||
.push_closure_step(def (engine) col2_.next = 1 end)
|
||||
.push_closure_step(def (engine) log(f"next", 3) end)
|
||||
engine.add(shutter_run_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
|
||||
set duration = 3s
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=PALETTE_RAINBOW, cycle_period=0)
|
||||
color col2 = color_cycle(palette=PALETTE_RAINBOW, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
animation shutter_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
log("foobar")
|
||||
sequence shutter_run repeat forever {
|
||||
log("before")
|
||||
play shutter_animation for duration
|
||||
log("after")
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
log("next")
|
||||
}
|
||||
|
||||
run shutter_run
|
||||
|
||||
-#
|
||||
@ -72,10 +72,10 @@ disco_pulse_.pos = (def (engine)
|
||||
return provider
|
||||
end)(engine) # Fast movement
|
||||
# Start all animations
|
||||
engine.add_animation(disco_base_)
|
||||
engine.add_animation(white_flash_)
|
||||
engine.add_animation(disco_sparkles_)
|
||||
engine.add_animation(disco_pulse_)
|
||||
engine.add(disco_base_)
|
||||
engine.add(white_flash_)
|
||||
engine.add(disco_sparkles_)
|
||||
engine.add(disco_pulse_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -40,8 +40,8 @@ fire_flicker_.density = 12 # density (number of flickers)
|
||||
fire_flicker_.twinkle_speed = 200 # twinkle speed (flicker duration)
|
||||
fire_flicker_.priority = 10
|
||||
# Start both animations
|
||||
engine.add_animation(fire_base_)
|
||||
engine.add_animation(fire_flicker_)
|
||||
engine.add(fire_base_)
|
||||
engine.add(fire_flicker_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -72,11 +72,11 @@ center_pulse_.opacity = (def (engine)
|
||||
return provider
|
||||
end)(engine) # Quick white flash
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(heart_glow_)
|
||||
engine.add_animation(heartbeat1_)
|
||||
engine.add_animation(heartbeat2_)
|
||||
engine.add_animation(center_pulse_)
|
||||
engine.add(background_)
|
||||
engine.add(heart_glow_)
|
||||
engine.add(heartbeat1_)
|
||||
engine.add(heartbeat2_)
|
||||
engine.add(center_pulse_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ var import_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(breathing_blue_, 3000)
|
||||
.push_play_step(dynamic_green_, 3000)
|
||||
# Run the demo
|
||||
engine.add_sequence_manager(import_demo_)
|
||||
engine.add(import_demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -87,11 +87,11 @@ heat_shimmer_.density = 6 # density (shimmer points)
|
||||
heat_shimmer_.twinkle_speed = 1500 # twinkle speed (slow shimmer)
|
||||
heat_shimmer_.priority = 15
|
||||
# Start all animations
|
||||
engine.add_animation(lava_base_)
|
||||
engine.add_animation(lava_blob1_)
|
||||
engine.add_animation(lava_blob2_)
|
||||
engine.add_animation(lava_blob3_)
|
||||
engine.add_animation(heat_shimmer_)
|
||||
engine.add(lava_base_)
|
||||
engine.add(lava_blob1_)
|
||||
engine.add(lava_blob2_)
|
||||
engine.add(lava_blob3_)
|
||||
engine.add(heat_shimmer_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -67,11 +67,11 @@ distant_flash_.density = 4 # density (few flashes)
|
||||
distant_flash_.twinkle_speed = 300 # twinkle speed (medium duration)
|
||||
distant_flash_.priority = 5
|
||||
# Start all animations
|
||||
engine.add_animation(storm_bg_)
|
||||
engine.add_animation(lightning_main_)
|
||||
engine.add_animation(lightning_partial_)
|
||||
engine.add_animation(afterglow_)
|
||||
engine.add_animation(distant_flash_)
|
||||
engine.add(storm_bg_)
|
||||
engine.add(lightning_main_)
|
||||
engine.add(lightning_partial_)
|
||||
engine.add(afterglow_)
|
||||
engine.add(distant_flash_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -57,11 +57,11 @@ code_flash_.density = 3 # density (few flashes)
|
||||
code_flash_.twinkle_speed = 150 # twinkle speed (quick flash)
|
||||
code_flash_.priority = 20
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stream1_)
|
||||
engine.add_animation(stream2_)
|
||||
engine.add_animation(stream3_)
|
||||
engine.add_animation(code_flash_)
|
||||
engine.add(background_)
|
||||
engine.add(stream1_)
|
||||
engine.add(stream2_)
|
||||
engine.add(stream3_)
|
||||
engine.add(code_flash_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -50,13 +50,13 @@ meteor_flash_.density = 1 # density (single flash)
|
||||
meteor_flash_.twinkle_speed = 100 # twinkle speed (very quick)
|
||||
meteor_flash_.priority = 25
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stars_)
|
||||
engine.add_animation(meteor1_)
|
||||
engine.add_animation(meteor2_)
|
||||
engine.add_animation(meteor3_)
|
||||
engine.add_animation(meteor4_)
|
||||
engine.add_animation(meteor_flash_)
|
||||
engine.add(background_)
|
||||
engine.add(stars_)
|
||||
engine.add(meteor1_)
|
||||
engine.add(meteor2_)
|
||||
engine.add(meteor3_)
|
||||
engine.add(meteor4_)
|
||||
engine.add(meteor_flash_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -72,12 +72,12 @@ arc_sparkles_.density = 4 # density (few arcs)
|
||||
arc_sparkles_.twinkle_speed = 100 # twinkle speed (quick arcs)
|
||||
arc_sparkles_.priority = 15
|
||||
# Start all animations
|
||||
engine.add_animation(neon_main_)
|
||||
engine.add_animation(neon_surge_)
|
||||
engine.add_animation(segment1_)
|
||||
engine.add_animation(segment2_)
|
||||
engine.add_animation(segment3_)
|
||||
engine.add_animation(arc_sparkles_)
|
||||
engine.add(neon_main_)
|
||||
engine.add(neon_surge_)
|
||||
engine.add(segment1_)
|
||||
engine.add(segment2_)
|
||||
engine.add(segment3_)
|
||||
engine.add(arc_sparkles_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -64,10 +64,10 @@ foam_.density = 6 # density (sparkle count)
|
||||
foam_.twinkle_speed = 300 # twinkle speed (quick sparkles)
|
||||
foam_.priority = 15
|
||||
# Start all animations
|
||||
engine.add_animation(ocean_base_)
|
||||
engine.add_animation(wave1_)
|
||||
engine.add_animation(wave2_)
|
||||
engine.add_animation(foam_)
|
||||
engine.add(ocean_base_)
|
||||
engine.add(wave1_)
|
||||
engine.add(wave2_)
|
||||
engine.add(foam_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ var palette_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(fire_anim_, 3000)
|
||||
.push_play_step(ocean_anim_, 3000)
|
||||
)
|
||||
engine.add_sequence_manager(palette_demo_)
|
||||
engine.add(palette_demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ var palette_showcase_ = animation.SequenceManager(engine)
|
||||
.push_play_step(aurora_lights_, 2000)
|
||||
.push_play_step(sunset_glow_, 2000)
|
||||
)
|
||||
engine.add_sequence_manager(palette_showcase_)
|
||||
engine.add(palette_showcase_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -84,10 +84,10 @@ plasma_base_.opacity = (def (engine)
|
||||
return provider
|
||||
end)(engine)
|
||||
# Start all animations
|
||||
engine.add_animation(plasma_base_)
|
||||
engine.add_animation(plasma_wave1_)
|
||||
engine.add_animation(plasma_wave2_)
|
||||
engine.add_animation(plasma_wave3_)
|
||||
engine.add(plasma_base_)
|
||||
engine.add(plasma_wave1_)
|
||||
engine.add(plasma_wave2_)
|
||||
engine.add(plasma_wave3_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -57,9 +57,9 @@ white_strobe_.opacity = (def (engine)
|
||||
end)(engine) # Quick bright flashes
|
||||
white_strobe_.priority = 20
|
||||
# Start all animations
|
||||
engine.add_animation(left_red_)
|
||||
engine.add_animation(right_blue_)
|
||||
engine.add_animation(white_strobe_)
|
||||
engine.add(left_red_)
|
||||
engine.add(right_blue_)
|
||||
engine.add(white_strobe_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(right_pulse_, 2000)
|
||||
.push_wait_step(1000)
|
||||
)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.add(demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ rainbow_cycle_.cycle_period = 5000 # cycle period
|
||||
var rainbow_animation_ = animation.solid(engine)
|
||||
rainbow_animation_.color = rainbow_cycle_
|
||||
# Start the animation
|
||||
engine.add_animation(rainbow_animation_)
|
||||
engine.add(rainbow_animation_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
BERRY_CMD="./berry -s -g -m lib/libesp32/berry_animation/src -e 'import tasmota'"
|
||||
BERRY_CMD="./berry -s -g -m lib/libesp32/berry_animation/src -e 'import tasmota def log(x) print(x) end '"
|
||||
COMPILED_DIR="lib/libesp32/berry_animation/anim_examples/compiled"
|
||||
|
||||
echo "Running compiled DSL examples..."
|
||||
|
||||
@ -48,9 +48,9 @@ end)(engine)
|
||||
scanner_trail_.pos = pos_test_
|
||||
scanner_trail_.opacity = 128 # Half brightness
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(scanner_trail_)
|
||||
engine.add_animation(scanner_)
|
||||
engine.add(background_)
|
||||
engine.add(scanner_trail_)
|
||||
engine.add(scanner_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -48,36 +48,36 @@ pulse_demo_.priority = 5
|
||||
# Sequence 1: Cylon Eye with Position Changes
|
||||
var cylon_eye_ = animation.SequenceManager(engine)
|
||||
.push_play_step(red_eye_, 3000)
|
||||
.push_assign_step(def (engine) red_eye_.pos = triangle_val_ end) # Change to triangle oscillator
|
||||
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end) # Change to triangle oscillator
|
||||
.push_play_step(red_eye_, 3000)
|
||||
.push_assign_step(def (engine) red_eye_.pos = cosine_val_ end) # Change back to cosine
|
||||
.push_assign_step(def (engine) eye_color_.next = 1 end) # Advance to next color
|
||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # Change back to cosine
|
||||
.push_closure_step(def (engine) eye_color_.next = 1 end) # Advance to next color
|
||||
.push_play_step(red_eye_, 2000)
|
||||
# Sequence 2: Brightness Control Demo
|
||||
var brightness_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(pulse_demo_, 2000)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_low_ end) # Dim the animation
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_low_ end) # Dim the animation
|
||||
.push_play_step(pulse_demo_, 2000)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_high_ end) # Brighten again
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_high_ end) # Brighten again
|
||||
.push_play_step(pulse_demo_, 2000)
|
||||
# Sequence 3: Multiple Property Changes
|
||||
var multi_change_ = animation.SequenceManager(engine)
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.color = 0xFFFF0000 end) # Change color
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_low_ end) # And brightness
|
||||
.push_closure_step(def (engine) pulse_demo_.color = 0xFFFF0000 end) # Change color
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_low_ end) # And brightness
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.color = 0xFF008000 end) # Change color again
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_high_ end) # Full brightness
|
||||
.push_closure_step(def (engine) pulse_demo_.color = 0xFF008000 end) # Change color again
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_high_ end) # Full brightness
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.color = 0xFF0000FF end) # Back to blue
|
||||
.push_closure_step(def (engine) pulse_demo_.color = 0xFF0000FF end) # Back to blue
|
||||
# Sequence 4: Assignments in Repeat Blocks
|
||||
var repeat_demo_ = animation.SequenceManager(engine)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, 3)
|
||||
.push_play_step(red_eye_, 1000)
|
||||
.push_assign_step(def (engine) red_eye_.pos = triangle_val_ end) # Change oscillator
|
||||
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end) # Change oscillator
|
||||
.push_play_step(red_eye_, 1000)
|
||||
.push_assign_step(def (engine) red_eye_.pos = cosine_val_ end) # Change back
|
||||
.push_assign_step(def (engine) eye_color_.next = 1 end) # Next color
|
||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end) # Change back
|
||||
.push_closure_step(def (engine) eye_color_.next = 1 end) # Next color
|
||||
)
|
||||
# Main demo sequence combining all examples
|
||||
var main_demo_ = animation.SequenceManager(engine)
|
||||
@ -85,31 +85,31 @@ var main_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(red_eye_, 1000)
|
||||
.push_wait_step(500)
|
||||
# Demonstrate position changes
|
||||
.push_assign_step(def (engine) red_eye_.pos = triangle_val_ end)
|
||||
.push_closure_step(def (engine) red_eye_.pos = triangle_val_ end)
|
||||
.push_play_step(red_eye_, 2000)
|
||||
.push_assign_step(def (engine) red_eye_.pos = cosine_val_ end)
|
||||
.push_closure_step(def (engine) red_eye_.pos = cosine_val_ end)
|
||||
.push_play_step(red_eye_, 2000)
|
||||
# Color cycling
|
||||
.push_assign_step(def (engine) eye_color_.next = 1 end)
|
||||
.push_closure_step(def (engine) eye_color_.next = 1 end)
|
||||
.push_play_step(red_eye_, 1000)
|
||||
.push_assign_step(def (engine) eye_color_.next = 1 end)
|
||||
.push_closure_step(def (engine) eye_color_.next = 1 end)
|
||||
.push_play_step(red_eye_, 1000)
|
||||
.push_wait_step(1000)
|
||||
# Brightness demo with pulse
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_low_ end)
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_low_ end)
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_high_ end)
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_high_ end)
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
# Multi-property changes
|
||||
.push_assign_step(def (engine) pulse_demo_.color = 0xFFFF0000 end)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_low_ end)
|
||||
.push_closure_step(def (engine) pulse_demo_.color = 0xFFFF0000 end)
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_low_ end)
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
.push_assign_step(def (engine) pulse_demo_.color = 0xFF008000 end)
|
||||
.push_assign_step(def (engine) pulse_demo_.opacity = brightness_high_ end)
|
||||
.push_closure_step(def (engine) pulse_demo_.color = 0xFF008000 end)
|
||||
.push_closure_step(def (engine) pulse_demo_.opacity = brightness_high_ end)
|
||||
.push_play_step(pulse_demo_, 1000)
|
||||
# Run the main demo
|
||||
engine.add_sequence_manager(main_demo_)
|
||||
engine.add(main_demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ rainbow_cycle_.cycle_period = 3000
|
||||
# Simple sequence
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(rainbow_cycle_, 15000)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.add(demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -74,10 +74,10 @@ stars_.opacity = (def (engine)
|
||||
return provider
|
||||
end)(engine) # Fade out during day
|
||||
# Start all animations
|
||||
engine.add_animation(daylight_cycle_)
|
||||
engine.add_animation(sun_position_)
|
||||
engine.add_animation(sun_glow_)
|
||||
engine.add_animation(stars_)
|
||||
engine.add(daylight_cycle_)
|
||||
engine.add(sun_position_)
|
||||
engine.add(sun_glow_)
|
||||
engine.add(stars_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -19,8 +19,8 @@ var swipe_animation_ = animation.solid(engine)
|
||||
swipe_animation_.color = olivary_
|
||||
var slide_colors_ = animation.SequenceManager(engine)
|
||||
.push_play_step(swipe_animation_, 1000)
|
||||
.push_assign_step(def (engine) olivary_.next = 1 end)
|
||||
engine.add_sequence_manager(slide_colors_)
|
||||
.push_closure_step(def (engine) olivary_.next = 1 end)
|
||||
engine.add(slide_colors_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -26,8 +26,8 @@ def rainbow_pulse_template(engine, pal1_, pal2_, duration_, back_color_)
|
||||
# Set pulse priority higher
|
||||
pulse_.priority = 10
|
||||
# Run both animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(pulse_)
|
||||
engine.add(background_)
|
||||
engine.add(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('rainbow_pulse', rainbow_pulse_template)
|
||||
|
||||
@ -17,7 +17,7 @@ def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
engine.add(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
|
||||
@ -17,7 +17,7 @@ def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
engine.add(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
|
||||
@ -29,9 +29,9 @@ bright_flash_.density = 2 # density (fewer bright flashes)
|
||||
bright_flash_.twinkle_speed = 300 # twinkle speed (quick flash)
|
||||
bright_flash_.priority = 15
|
||||
# Start all animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(stars_)
|
||||
engine.add_animation(bright_flash_)
|
||||
engine.add(background_)
|
||||
engine.add(stars_)
|
||||
engine.add(bright_flash_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -45,11 +45,11 @@ random_complex_.priority = 20
|
||||
# Complex expression with user function and math operations
|
||||
random_complex_.opacity = animation.create_closure_value(engine, def (self) return self.round((animation.get_user_function('rand_demo')(self.engine) + 128) / 2 + self.abs(animation.get_user_function('rand_demo')(self.engine) - 100)) end)
|
||||
# Run all animations to demonstrate the effects
|
||||
engine.add_animation(random_base_)
|
||||
engine.add_animation(random_bounded_)
|
||||
engine.add_animation(random_variation_)
|
||||
engine.add_animation(random_multi_)
|
||||
engine.add_animation(random_complex_)
|
||||
engine.add(random_base_)
|
||||
engine.add(random_bounded_)
|
||||
engine.add(random_variation_)
|
||||
engine.add(random_multi_)
|
||||
engine.add(random_complex_)
|
||||
engine.start()
|
||||
|
||||
|
||||
|
||||
@ -14,10 +14,11 @@ color fire_color = rich_palette(palette=fire_colors)
|
||||
animation background = solid(color=0x000088, priority=20)
|
||||
run background
|
||||
|
||||
set eye_pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 6s)
|
||||
animation eye_mask = beacon_animation(
|
||||
color = transparent
|
||||
back_color = white
|
||||
pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = 3s)
|
||||
color = white
|
||||
back_color = transparent
|
||||
pos = eye_pos
|
||||
beacon_size = 4 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 5
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
|
||||
template shutter_left_right {
|
||||
param colors type palette
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=colors, cycle_period=0)
|
||||
color col2 = color_cycle(palette=colors, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
animation shutter_lr_animation = beacon_animation(
|
||||
color = col2
|
||||
back_color = col1
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
|
||||
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 col1.palette_size times {
|
||||
repeat 7 times {
|
||||
reset shutter_size
|
||||
play shutter_lr_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
repeat col1.palette_size times {
|
||||
reset shutter_size
|
||||
play shutter_rl_animation for duration
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
run shutter_seq
|
||||
}
|
||||
|
||||
color Violet = 0x112233
|
||||
|
||||
palette rainbow_with_white = [
|
||||
red
|
||||
orange
|
||||
yellow
|
||||
green
|
||||
blue
|
||||
indigo
|
||||
white
|
||||
]
|
||||
|
||||
shutter_left_right(rainbow_with_white, 1.5s)
|
||||
@ -0,0 +1,32 @@
|
||||
# Demo Shutter Rainbow
|
||||
#
|
||||
# Shutter from left to right iterating in all colors
|
||||
|
||||
set duration = 3s
|
||||
|
||||
set strip_len = strip_length()
|
||||
set shutter_size = sawtooth(min_value = 0, max_value = strip_len, duration = duration)
|
||||
|
||||
color col1 = color_cycle(palette=PALETTE_RAINBOW, cycle_period=0)
|
||||
color col2 = color_cycle(palette=PALETTE_RAINBOW, cycle_period=0)
|
||||
col2.next = 1
|
||||
|
||||
animation shutter_animation = beacon_animation(
|
||||
color = col1
|
||||
back_color = col2
|
||||
pos = 0
|
||||
beacon_size = shutter_size
|
||||
slew_size = 0
|
||||
priority = 5
|
||||
)
|
||||
log("foobar")
|
||||
sequence shutter_run repeat forever {
|
||||
log("before")
|
||||
play shutter_animation for duration
|
||||
log("after")
|
||||
col1.next = 1
|
||||
col2.next = 1
|
||||
log("next")
|
||||
}
|
||||
|
||||
run shutter_run
|
||||
@ -248,6 +248,7 @@ Cycles through a palette of colors with brutal switching. Inherits from `ColorPr
|
||||
| `palette` | bytes | default palette | - | Palette bytes in AARRGGBB format |
|
||||
| `cycle_period` | int | 5000 | min: 0 | Cycle time in ms (0 = manual only) |
|
||||
| `next` | int | 0 | - | Write 1 to move to next color manually, or any number to go forward or backwars by `n` colors |
|
||||
| `palette_size` | int | 3 | read-only | Number of colors in the palette (automatically updated when palette changes) |
|
||||
|
||||
**Modes**: Auto-cycle (`cycle_period > 0`) or Manual-only (`cycle_period = 0`)
|
||||
|
||||
|
||||
@ -537,7 +537,7 @@ anim.color = 0xFFFF0000
|
||||
anim.pos = 5
|
||||
anim.beacon_size = 3
|
||||
|
||||
engine.add_animation(anim)
|
||||
engine.add(anim) # Unified method for animations and sequence managers
|
||||
engine.start()
|
||||
|
||||
# Let it run for a few seconds
|
||||
|
||||
@ -73,6 +73,8 @@ The following keywords are reserved and cannot be used as identifiers:
|
||||
- `times` - Loop count specifier
|
||||
- `for` - Duration specifier
|
||||
- `run` - Execute animation or sequence
|
||||
- `reset` - Reset value provider or animation to initial state
|
||||
- `restart` - Restart value provider or animation from beginning
|
||||
|
||||
**Easing Keywords:**
|
||||
- `linear` - Linear/triangle wave easing
|
||||
@ -329,11 +331,58 @@ palette timed_colors = [
|
||||
**Palette Rules:**
|
||||
- **Value-based**: Positions range from 0 to 255, represent intensity/brightness levels
|
||||
- **Tick-based**: Positions represent duration in arbitrary time units
|
||||
- Colors can be hex values or named colors
|
||||
- **Colors**: Only hex values (0xRRGGBB) or predefined color names (red, blue, green, etc.)
|
||||
- **Custom colors**: Previously defined custom colors are NOT allowed in palettes
|
||||
- **Dynamic palettes**: For palettes with custom colors, use user functions instead
|
||||
- Entries are automatically sorted by position
|
||||
- Comments are preserved
|
||||
- Automatically converted to efficient VRGB bytes format
|
||||
|
||||
### Palette Color Restrictions
|
||||
|
||||
Palettes have strict color validation to ensure compile-time safety:
|
||||
|
||||
**✅ Allowed:**
|
||||
```berry
|
||||
palette valid_colors = [
|
||||
(0, 0xFF0000) # Hex colors
|
||||
(128, red) # Predefined color names
|
||||
(255, blue) # More predefined colors
|
||||
]
|
||||
```
|
||||
|
||||
**❌ Not Allowed:**
|
||||
```berry
|
||||
color custom_red = 0xFF0000
|
||||
palette invalid_colors = [
|
||||
(0, custom_red) # ERROR: Custom colors not allowed
|
||||
(128, my_color) # ERROR: Undefined color
|
||||
]
|
||||
```
|
||||
|
||||
**Alternative for Dynamic Palettes:**
|
||||
For palettes that need custom or computed colors, use user functions:
|
||||
|
||||
```berry
|
||||
# Define a user function that creates dynamic palettes
|
||||
def create_custom_palette(engine, base_color, intensity)
|
||||
# Create palette with custom logic
|
||||
var palette_data = create_dynamic_palette_bytes(base_color, intensity)
|
||||
return palette_data
|
||||
end
|
||||
|
||||
# Register for DSL use
|
||||
animation.register_user_function("custom_palette", create_custom_palette)
|
||||
```
|
||||
|
||||
```berry
|
||||
# Use in DSL
|
||||
animation dynamic_anim = rich_palette(
|
||||
palette=user.custom_palette(0xFF0000, 200)
|
||||
cycle_period=3s
|
||||
)
|
||||
```
|
||||
|
||||
## Animation Definitions
|
||||
|
||||
The `animation` keyword defines instances of animation classes (subclasses of Animation):
|
||||
@ -582,9 +631,15 @@ sequence cylon_eye repeat forever {
|
||||
red_eye.pos = cosine_val
|
||||
eye_color.next = 1
|
||||
}
|
||||
|
||||
# Option 3: Parametric repeat count
|
||||
sequence rainbow_cycle repeat palette.size times {
|
||||
play animation for 1s
|
||||
palette.next = 1
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Both syntaxes are functionally equivalent. The second syntax creates an outer sequence (runs once) containing an inner repeat sub-sequence.
|
||||
**Note**: All syntaxes are functionally equivalent. The repeat count can be a literal number, variable, or dynamic expression that evaluates at runtime.
|
||||
|
||||
### Sequence Statements
|
||||
|
||||
@ -663,10 +718,22 @@ repeat forever { # Repeat indefinitely until parent sequence s
|
||||
play animation for 1s
|
||||
wait 500ms
|
||||
}
|
||||
|
||||
repeat col1.palette_size times { # Parametric repeat count using property access
|
||||
play animation for 1s
|
||||
col1.next = 1
|
||||
}
|
||||
```
|
||||
|
||||
**Repeat Count Types:**
|
||||
- **Literal numbers**: `repeat 5 times` - fixed repeat count
|
||||
- **Variables**: `repeat count_var times` - using previously defined variables
|
||||
- **Property access**: `repeat color_provider.palette_size times` - dynamic values from object properties
|
||||
- **Computed expressions**: `repeat strip_length() / 2 times` - calculated repeat counts
|
||||
|
||||
**Repeat Behavior:**
|
||||
- **Runtime Execution**: Repeats are executed at runtime, not expanded at compile time
|
||||
- **Dynamic Evaluation**: Parametric repeat counts are evaluated when the sequence starts
|
||||
- **Sub-sequences**: Each repeat block creates a sub-sequence that manages its own iteration state
|
||||
- **Nested Repeats**: Supports nested repeats with multiplication (e.g., `repeat 3 times { repeat 2 times { ... } }` executes 6 times total)
|
||||
- **Forever Loops**: `repeat forever` continues until the parent sequence is stopped
|
||||
@ -716,6 +783,42 @@ sequence cylon_eye {
|
||||
}
|
||||
```
|
||||
|
||||
#### Reset and Restart Statements
|
||||
|
||||
Reset and restart statements allow you to reset value providers and animations to their initial state during sequence execution:
|
||||
|
||||
```berry
|
||||
reset value_provider_name # Reset value provider to initial state
|
||||
restart animation_name # Restart animation from beginning
|
||||
```
|
||||
|
||||
**Reset Statement:**
|
||||
- Resets value providers (oscillators, color cycles, etc.) to their initial state
|
||||
- Calls the `start()` method on the value provider
|
||||
- Useful for synchronizing oscillators or restarting color cycles
|
||||
|
||||
**Restart Statement:**
|
||||
- Restarts animations from their beginning state
|
||||
- Calls the `start()` method on the animation
|
||||
- Useful for restarting complex animations or synchronizing multiple animations
|
||||
|
||||
**Examples:**
|
||||
```berry
|
||||
# Reset oscillators for synchronized movement
|
||||
sequence sync_demo {
|
||||
play wave_anim for 3s
|
||||
reset position_osc # Reset oscillator to start position
|
||||
play wave_anim for 3s
|
||||
}
|
||||
|
||||
# Restart animations for clean transitions
|
||||
sequence clean_transitions {
|
||||
play comet_anim for 5s
|
||||
restart comet_anim # Restart from beginning position
|
||||
play comet_anim for 5s
|
||||
}
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
Templates provide a powerful way to create reusable, parameterized animation patterns. They allow you to define animation blueprints that can be instantiated with different parameters, promoting code reuse and maintainability.
|
||||
@ -860,7 +963,7 @@ def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
engine.add(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
@ -886,6 +989,22 @@ run animation_name # Run an animation
|
||||
run sequence_name # Run a sequence
|
||||
```
|
||||
|
||||
### Debug and Logging
|
||||
|
||||
Log debug messages during animation execution:
|
||||
|
||||
```berry
|
||||
log("Debug message") # Log message at level 3
|
||||
log("Animation started") # Useful for debugging sequences
|
||||
log("Color changed to red") # Track animation state changes
|
||||
```
|
||||
|
||||
**Log Function Behavior:**
|
||||
- Accepts string literals only (no variables or expressions)
|
||||
- Transpiles to Berry `log(f"message", 3)`
|
||||
- Messages are logged at level 3 for debugging purposes
|
||||
- Can be used anywhere in DSL code: standalone, in sequences, etc.
|
||||
|
||||
## Operators and Expressions
|
||||
|
||||
### Arithmetic Operators
|
||||
@ -1170,14 +1289,16 @@ template_def = "template" identifier "{" template_body "}" ;
|
||||
property_assignment = identifier "." identifier "=" expression ;
|
||||
|
||||
(* Sequences *)
|
||||
sequence = "sequence" identifier [ "repeat" ( number "times" | "forever" ) ] "{" sequence_body "}" ;
|
||||
sequence = "sequence" identifier [ "repeat" ( expression "times" | "forever" ) ] "{" sequence_body "}" ;
|
||||
sequence_body = { sequence_statement } ;
|
||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment ;
|
||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment | reset_stmt | restart_stmt ;
|
||||
|
||||
play_stmt = "play" identifier [ "for" time_expression ] ;
|
||||
wait_stmt = "wait" time_expression ;
|
||||
repeat_stmt = "repeat" ( number "times" | "forever" ) "{" sequence_body "}" ;
|
||||
repeat_stmt = "repeat" ( expression "times" | "forever" ) "{" sequence_body "}" ;
|
||||
sequence_assignment = identifier "." identifier "=" expression ;
|
||||
reset_stmt = "reset" identifier ;
|
||||
restart_stmt = "restart" identifier ;
|
||||
|
||||
(* Templates *)
|
||||
template_def = "template" identifier "{" template_body "}" ;
|
||||
|
||||
@ -252,7 +252,7 @@ var test_ = animation.solid(engine)
|
||||
test_.color = 0xFF0000FF
|
||||
test_.opacity = animation.create_closure_value(engine,
|
||||
def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
||||
engine.add_animation(test_)
|
||||
engine.add(test_)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
@ -286,7 +286,7 @@ def pulse_effect(engine, color, speed)
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = color
|
||||
pulse_.period = speed
|
||||
engine.add_animation(pulse_)
|
||||
engine.add(pulse_)
|
||||
engine.start_animation(pulse_)
|
||||
end
|
||||
|
||||
@ -341,9 +341,9 @@ def comet_chase(engine, trail_color, bg_color, chase_speed)
|
||||
var comet_ = animation.comet_animation(engine)
|
||||
comet_.color = trail_color
|
||||
comet_.speed = chase_speed
|
||||
engine.add_animation(background_)
|
||||
engine.add(background_)
|
||||
engine.start_animation(background_)
|
||||
engine.add_animation(comet_)
|
||||
engine.add(comet_)
|
||||
engine.start_animation(comet_)
|
||||
end
|
||||
|
||||
|
||||
@ -89,9 +89,13 @@ animation sunrise = pulsating_animation(color=orange, period=3s)
|
||||
animation day = solid(color=yellow)
|
||||
|
||||
sequence sunrise_show {
|
||||
log("Starting sunrise sequence")
|
||||
play night for 3s
|
||||
log("Night phase complete, starting sunrise")
|
||||
play sunrise for 5s
|
||||
log("Sunrise complete, switching to day")
|
||||
play day for 3s
|
||||
log("Sunrise sequence finished")
|
||||
}
|
||||
run sunrise_show
|
||||
```
|
||||
@ -169,7 +173,23 @@ sequence demo {
|
||||
run demo
|
||||
```
|
||||
|
||||
### 11. Assignments in Repeat Blocks
|
||||
### 11. Reset and Restart in Sequences
|
||||
```berry
|
||||
# Create oscillator and animation
|
||||
set wave_osc = triangle(min_value=0, max_value=29, period=4s)
|
||||
animation wave = beacon_animation(color=blue, pos=wave_osc, beacon_size=5)
|
||||
|
||||
sequence sync_demo {
|
||||
play wave for 3s
|
||||
reset wave_osc # Reset oscillator to start position
|
||||
play wave for 3s # Wave starts from beginning again
|
||||
restart wave # Restart animation from initial state
|
||||
play wave for 3s
|
||||
}
|
||||
run sync_demo
|
||||
```
|
||||
|
||||
### 12. Assignments in Repeat Blocks
|
||||
```berry
|
||||
set brightness = smooth(min_value=50, max_value=255, period=2s)
|
||||
animation pulse = pulsating_animation(color=white, period=1s)
|
||||
@ -187,7 +207,7 @@ run breathing_cycle
|
||||
|
||||
## User Functions in Computed Parameters
|
||||
|
||||
### 12. Simple User Function
|
||||
### 13. Simple User Function
|
||||
```berry
|
||||
# Simple user function in computed parameter
|
||||
animation random_base = solid(color=blue, priority=10)
|
||||
@ -195,7 +215,7 @@ random_base.opacity = rand_demo()
|
||||
run random_base
|
||||
```
|
||||
|
||||
### 13. User Function with Math Operations
|
||||
### 14. User Function with Math Operations
|
||||
```berry
|
||||
# Mix user functions with mathematical functions
|
||||
animation random_bounded = solid(
|
||||
@ -206,7 +226,7 @@ animation random_bounded = solid(
|
||||
run random_bounded
|
||||
```
|
||||
|
||||
### 14. User Function in Arithmetic Expression
|
||||
### 15. User Function in Arithmetic Expression
|
||||
```berry
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -221,7 +241,7 @@ See `anim_examples/user_functions_demo.anim` for a complete working example.
|
||||
|
||||
## New Repeat System Examples
|
||||
|
||||
### 15. Runtime Repeat with Forever Loop
|
||||
### 16. Runtime Repeat with Forever Loop
|
||||
```berry
|
||||
color red = 0xFF0000
|
||||
color blue = 0x0000FF
|
||||
@ -245,7 +265,7 @@ sequence cylon_effect_alt repeat forever {
|
||||
run cylon_effect
|
||||
```
|
||||
|
||||
### 16. Nested Repeats (Multiplication)
|
||||
### 17. Nested Repeats (Multiplication)
|
||||
```berry
|
||||
color green = 0x00FF00
|
||||
color yellow = 0xFFFF00
|
||||
@ -265,7 +285,7 @@ sequence nested_pattern {
|
||||
run nested_pattern
|
||||
```
|
||||
|
||||
### 17. Repeat with Property Assignments
|
||||
### 18. Repeat with Property Assignments
|
||||
```berry
|
||||
set triangle_pos = triangle(min_value=0, max_value=29, period=3s)
|
||||
set cosine_pos = cosine_osc(min_value=0, max_value=29, period=3s)
|
||||
@ -292,7 +312,7 @@ run dynamic_cylon
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### 18. Dynamic Position
|
||||
### 19. Dynamic Position
|
||||
```berry
|
||||
strip length 60
|
||||
|
||||
@ -308,7 +328,7 @@ animation moving_pulse = beacon_animation(
|
||||
run moving_pulse
|
||||
```
|
||||
|
||||
### 19. Multi-Layer Effect
|
||||
### 20. Multi-Layer Effect
|
||||
```berry
|
||||
# Base layer - slow breathing
|
||||
set breathing = smooth(min_value=100, max_value=255, period=4s)
|
||||
|
||||
@ -645,7 +645,7 @@ animation.register_user_function("pulse_effect", create_pulse_effect)
|
||||
```berry
|
||||
# Clear before adding new animations
|
||||
engine.clear()
|
||||
engine.add_animation(new_animation)
|
||||
engine.add(new_animation)
|
||||
```
|
||||
|
||||
2. **Limit Palette Size:**
|
||||
@ -760,7 +760,7 @@ import animation
|
||||
var engine = animation.create_engine(strip)
|
||||
var red_anim = animation.solid(engine)
|
||||
red_anim.color = 0xFFFF0000
|
||||
engine.add_animation(red_anim)
|
||||
engine.add(red_anim)
|
||||
engine.start()
|
||||
|
||||
# If basic strip works but animation doesn't, check framework setup
|
||||
@ -830,7 +830,7 @@ var engine = animation.create_engine(strip, true) # debug=true
|
||||
|
||||
var anim = animation.solid(engine)
|
||||
anim.color = 0xFFFF0000
|
||||
engine.add_animation(anim)
|
||||
engine.add(anim)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
@ -852,7 +852,7 @@ anim.color = 0xFFFF0000
|
||||
print("Animation created:", anim != nil)
|
||||
|
||||
print("4. Adding animation...")
|
||||
engine.add_animation(anim)
|
||||
engine.add(anim)
|
||||
print("Animation count:", engine.size())
|
||||
|
||||
print("5. Starting engine...")
|
||||
|
||||
@ -252,6 +252,52 @@ end
|
||||
animation.register_user_function("rainbow_sparkle", rainbow_sparkle)
|
||||
```
|
||||
|
||||
### Dynamic Palettes
|
||||
|
||||
Since DSL palettes only accept hex colors and predefined color names (not custom colors), use user functions for dynamic palettes with custom colors:
|
||||
|
||||
```berry
|
||||
def create_custom_palette(engine, base_color, variation_count, intensity)
|
||||
# Create a palette with variations of the base color
|
||||
var palette_bytes = bytes()
|
||||
|
||||
# Extract RGB components from base color
|
||||
var r = (base_color >> 16) & 0xFF
|
||||
var g = (base_color >> 8) & 0xFF
|
||||
var b = base_color & 0xFF
|
||||
|
||||
# Create palette entries with color variations
|
||||
for i : 0..(variation_count-1)
|
||||
var position = int(i * 255 / (variation_count - 1))
|
||||
var factor = intensity * i / (variation_count - 1) / 255
|
||||
|
||||
var new_r = int(r * factor)
|
||||
var new_g = int(g * factor)
|
||||
var new_b = int(b * factor)
|
||||
|
||||
# Add VRGB entry (Value, Red, Green, Blue)
|
||||
palette_bytes.add(position, 1) # Position
|
||||
palette_bytes.add(new_r, 1) # Red
|
||||
palette_bytes.add(new_g, 1) # Green
|
||||
palette_bytes.add(new_b, 1) # Blue
|
||||
end
|
||||
|
||||
return palette_bytes
|
||||
end
|
||||
|
||||
animation.register_user_function("custom_palette", create_custom_palette)
|
||||
```
|
||||
|
||||
```berry
|
||||
# Use dynamic palette in DSL
|
||||
animation gradient_effect = rich_palette(
|
||||
palette=user.custom_palette(0xFF6B35, 5, 255)
|
||||
cycle_period=4s
|
||||
)
|
||||
|
||||
run gradient_effect
|
||||
```
|
||||
|
||||
### Preset Configurations
|
||||
|
||||
```berry
|
||||
|
||||
@ -34,7 +34,6 @@ class Animation : animation.parameterized_object
|
||||
# Initialize non-parameter instance variables
|
||||
self.start_time = 0
|
||||
self.current_time = 0
|
||||
self.opacity_frame = nil # Will be created when needed
|
||||
end
|
||||
|
||||
# Start/restart the animation (make it active and reset timing)
|
||||
@ -44,7 +43,7 @@ class Animation : animation.parameterized_object
|
||||
def start(start_time)
|
||||
# Set is_running directly in values map to avoid infinite loop
|
||||
self.values["is_running"] = true
|
||||
var actual_start_time = start_time != nil ? start_time : self.engine.time_ms
|
||||
var actual_start_time = (start_time != nil) ? start_time : self.engine.time_ms
|
||||
self.start_time = actual_start_time
|
||||
self.current_time = self.start_time
|
||||
|
||||
@ -63,11 +62,7 @@ class Animation : animation.parameterized_object
|
||||
# Check if the parameter value is a value provider
|
||||
if animation.is_value_provider(param_value)
|
||||
# Call start method if it exists (acts as restart)
|
||||
try
|
||||
param_value.start(time_ms)
|
||||
except .. as e
|
||||
# Ignore errors if start method doesn't exist or fails
|
||||
end
|
||||
param_value.start(time_ms)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -85,7 +80,7 @@ class Animation : animation.parameterized_object
|
||||
self.current_time = self.start_time
|
||||
# Start/restart all value providers in parameters
|
||||
self._start_value_providers(actual_start_time)
|
||||
elif value == false
|
||||
# elif value == false
|
||||
# Stop the animation - just set the internal state
|
||||
# (is_running is already set to false by the parameter system)
|
||||
end
|
||||
@ -143,9 +138,7 @@ class Animation : animation.parameterized_object
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
time_ms = (time_ms != nil) ? time_ms : self.engine.time_ms
|
||||
|
||||
# Update animation state
|
||||
self.update(time_ms)
|
||||
@ -226,28 +219,6 @@ class Animation : animation.parameterized_object
|
||||
return self.get_color_at(0, time_ms)
|
||||
end
|
||||
|
||||
# Get the normalized progress of the animation (0 to 255)
|
||||
#
|
||||
# @return int - Progress from 0 (start) to 255 (end)
|
||||
def get_progress()
|
||||
var current_duration = self.duration
|
||||
if current_duration <= 0
|
||||
return 0 # Infinite animations always return 0 progress
|
||||
end
|
||||
|
||||
var elapsed = self.current_time - self.start_time
|
||||
var progress = elapsed % current_duration # Handle looping
|
||||
|
||||
# For non-looping animations, if we've reached exactly the duration,
|
||||
# return maximum progress instead of 0 (which would be the modulo result)
|
||||
var current_loop = self.loop
|
||||
if !current_loop && elapsed >= current_duration
|
||||
return 255
|
||||
end
|
||||
|
||||
return tasmota.scale_uint(progress, 0, current_duration, 0, 255)
|
||||
end
|
||||
|
||||
# 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})"
|
||||
|
||||
@ -43,17 +43,19 @@ class AnimationEngine
|
||||
end
|
||||
|
||||
# Start the animation engine
|
||||
#
|
||||
# @return self for method chaining
|
||||
def start()
|
||||
if !self.is_running
|
||||
var now = tasmota.millis()
|
||||
self.is_running = true
|
||||
self.last_update = tasmota.millis() - 10
|
||||
self.last_update = now - 10
|
||||
|
||||
if self.fast_loop_closure == nil
|
||||
self.fast_loop_closure = / -> self.on_tick()
|
||||
end
|
||||
|
||||
var i = 0
|
||||
var now = tasmota.millis()
|
||||
while (i < size(self.animations))
|
||||
self.animations[i].start(now)
|
||||
i += 1
|
||||
@ -71,6 +73,8 @@ class AnimationEngine
|
||||
end
|
||||
|
||||
# Stop the animation engine
|
||||
#
|
||||
# @return self for method chaining
|
||||
def stop()
|
||||
if self.is_running
|
||||
self.is_running = false
|
||||
@ -83,25 +87,23 @@ class AnimationEngine
|
||||
end
|
||||
|
||||
# Add an animation with automatic priority sorting
|
||||
#
|
||||
# @param anim: animation - The animation instance to add (if not already listed)
|
||||
# @return true if succesful (TODO always true)
|
||||
def add_animation(anim)
|
||||
# Check if animation already exists
|
||||
var i = 0
|
||||
while i < size(self.animations)
|
||||
if self.animations[i] == anim
|
||||
return false
|
||||
if (self.animations.find(anim) == nil) # not already in list
|
||||
# Add and sort by priority (higher priority first)
|
||||
self.animations.push(anim)
|
||||
self._sort_animations()
|
||||
# If the engine is already started, auto-start the animation
|
||||
if self.is_running
|
||||
anim.start(self.time_ms)
|
||||
end
|
||||
i += 1
|
||||
self.render_needed = true
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
|
||||
# Add and sort by priority (higher priority first)
|
||||
self.animations.push(anim)
|
||||
self._sort_animations()
|
||||
# If the engine is already started, auto-start the animation
|
||||
if self.is_running
|
||||
anim.start()
|
||||
end
|
||||
self.render_needed = true
|
||||
return true
|
||||
end
|
||||
|
||||
# Remove an animation
|
||||
@ -143,6 +145,27 @@ class AnimationEngine
|
||||
return self
|
||||
end
|
||||
|
||||
# Unified method to add either animations or sequence managers
|
||||
# Detects the class type and calls the appropriate method
|
||||
#
|
||||
# @param obj: Animation or SequenceManager - The object to add
|
||||
# @return self for method chaining
|
||||
def add(obj)
|
||||
# Check if it's a SequenceManager
|
||||
if isinstance(obj, animation.SequenceManager)
|
||||
return self.add_sequence_manager(obj)
|
||||
# Check if it's an Animation (or subclass)
|
||||
elif isinstance(obj, animation.animation)
|
||||
self.add_animation(obj)
|
||||
return self
|
||||
else
|
||||
# Unknown type - provide helpful error message
|
||||
import introspect
|
||||
var class_name = introspect.name(obj)
|
||||
raise "type_error", f"Cannot add object of type '{class_name}' to engine. Expected Animation or SequenceManager."
|
||||
end
|
||||
end
|
||||
|
||||
# Remove a sequence manager
|
||||
def remove_sequence_manager(sequence_manager)
|
||||
var index = -1
|
||||
|
||||
@ -13,11 +13,6 @@
|
||||
# The class is optimized for performance and minimal memory usage.
|
||||
|
||||
class FrameBuffer
|
||||
# Blend modes
|
||||
# Currently only normal blending is implemented, but the structure allows for adding more modes later
|
||||
static BLEND_MODE_NORMAL = 0 # Normal alpha blending
|
||||
# Other blend modes can be added here in the future if needed
|
||||
|
||||
var pixels # Pixel data (bytes object)
|
||||
var width # Number of pixels
|
||||
|
||||
@ -125,16 +120,11 @@ class FrameBuffer
|
||||
end
|
||||
end
|
||||
|
||||
# Blend two colors using their alpha channels and blend mode
|
||||
# Blend two colors using their alpha channels
|
||||
# Returns the blended color as a 32-bit integer (ARGB format - 0xAARRGGBB)
|
||||
# color1: destination color (ARGB format - 0xAARRGGBB)
|
||||
# color2: source color (ARGB format - 0xAARRGGBB)
|
||||
# mode: blending mode (default: BLEND_MODE_NORMAL)
|
||||
def blend(color1, color2, mode)
|
||||
# Default blend mode to normal if not specified
|
||||
if mode == nil
|
||||
mode = self.BLEND_MODE_NORMAL
|
||||
end
|
||||
def blend(color1, color2)
|
||||
|
||||
# Extract components from color1 (ARGB format - 0xAARRGGBB)
|
||||
var a1 = (color1 >> 24) & 0xFF
|
||||
@ -157,7 +147,7 @@ class FrameBuffer
|
||||
# Use the source alpha directly for blending
|
||||
var effective_opacity = a2
|
||||
|
||||
# Normal alpha blending (currently the only supported mode)
|
||||
# Normal alpha blending
|
||||
# Use tasmota.scale_uint for ratio conversion instead of integer arithmetic
|
||||
var r = tasmota.scale_uint(255 - effective_opacity, 0, 255, 0, r1) + tasmota.scale_uint(effective_opacity, 0, 255, 0, r2)
|
||||
var g = tasmota.scale_uint(255 - effective_opacity, 0, 255, 0, g1) + tasmota.scale_uint(effective_opacity, 0, 255, 0, g2)
|
||||
@ -218,16 +208,11 @@ class FrameBuffer
|
||||
|
||||
# Blend this frame buffer with another frame buffer using per-pixel alpha
|
||||
# other_buffer: the other frame buffer to blend with
|
||||
# mode: blending mode (default: BLEND_MODE_NORMAL)
|
||||
# region_start: start index for blending (default: 0)
|
||||
# region_end: end index for blending (default: width-1)
|
||||
def blend_pixels(other_buffer, mode, region_start, region_end)
|
||||
def blend_pixels(other_buffer, region_start, region_end)
|
||||
# Default parameters
|
||||
|
||||
if mode == nil
|
||||
mode = self.BLEND_MODE_NORMAL
|
||||
end
|
||||
|
||||
if region_start == nil
|
||||
region_start = 0
|
||||
end
|
||||
@ -249,43 +234,23 @@ class FrameBuffer
|
||||
raise "index_error", "region_end out of range"
|
||||
end
|
||||
|
||||
# Performance optimization: batch processing for normal blend mode
|
||||
if mode == self.BLEND_MODE_NORMAL
|
||||
# Fast path for normal blending
|
||||
var i = region_start
|
||||
while i <= region_end
|
||||
var color2 = other_buffer.get_pixel_color(i)
|
||||
var a2 = (color2 >> 24) & 0xFF
|
||||
|
||||
# Only blend if the source pixel has some alpha
|
||||
if a2 > 0
|
||||
if a2 == 255
|
||||
# Fully opaque source pixel, just copy it
|
||||
self.pixels.set(i * 4, color2, 4)
|
||||
else
|
||||
# Partially transparent source pixel, need to blend
|
||||
var color1 = self.get_pixel_color(i)
|
||||
var blended = self.blend(color1, color2, mode)
|
||||
self.pixels.set(i * 4, blended, 4)
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# General case: blend each pixel using the blend function
|
||||
# Blend each pixel using the blend function
|
||||
var i = region_start
|
||||
while i <= region_end
|
||||
var color1 = self.get_pixel_color(i)
|
||||
var color2 = other_buffer.get_pixel_color(i)
|
||||
var a2 = (color2 >> 24) & 0xFF
|
||||
|
||||
# Only blend if the source pixel has some alpha
|
||||
var a2 = (color2 >> 24) & 0xFF
|
||||
if a2 > 0
|
||||
var blended = self.blend(color1, color2, mode)
|
||||
self.pixels.set(i * 4, blended, 4)
|
||||
if a2 == 255
|
||||
# Fully opaque source pixel, just copy it
|
||||
self.pixels.set(i * 4, color2, 4)
|
||||
else
|
||||
# Partially transparent source pixel, need to blend
|
||||
var color1 = self.get_pixel_color(i)
|
||||
var blended = self.blend(color1, color2)
|
||||
self.pixels.set(i * 4, blended, 4)
|
||||
end
|
||||
end
|
||||
|
||||
i += 1
|
||||
@ -437,14 +402,9 @@ class FrameBuffer
|
||||
|
||||
# Blend a specific region with a solid color using the color's alpha channel
|
||||
# color: the color to blend (ARGB)
|
||||
# mode: blending mode (default: BLEND_MODE_NORMAL)
|
||||
# start_pos: start position (default: 0)
|
||||
# end_pos: end position (default: width-1)
|
||||
def blend_color(color, mode, start_pos, end_pos)
|
||||
|
||||
if mode == nil
|
||||
mode = self.BLEND_MODE_NORMAL
|
||||
end
|
||||
def blend_color(color, start_pos, end_pos)
|
||||
|
||||
if start_pos == nil
|
||||
start_pos = 0
|
||||
@ -476,7 +436,7 @@ class FrameBuffer
|
||||
|
||||
# Only blend if the color has some alpha
|
||||
if a2 > 0
|
||||
var blended = self.blend(color1, color, mode)
|
||||
var blended = self.blend(color1, color)
|
||||
self.pixels.set(i * 4, blended, 4)
|
||||
end
|
||||
|
||||
|
||||
@ -338,34 +338,6 @@ class ParameterizedObject
|
||||
return self._get_param_def(name)
|
||||
end
|
||||
|
||||
# Get all parameter metadata from class hierarchy
|
||||
#
|
||||
# @return map - Map of all parameter metadata
|
||||
def get_params_metadata()
|
||||
import introspect
|
||||
var all_params = {}
|
||||
|
||||
# Walk up the class hierarchy to collect all parameter definitions
|
||||
var current_class = classof(self)
|
||||
while current_class != nil
|
||||
# Check if this class has PARAMS
|
||||
if introspect.contains(current_class, "PARAMS")
|
||||
var class_params = current_class.PARAMS
|
||||
# Add parameters from this class (child class parameters override parent)
|
||||
for param_name : class_params.keys()
|
||||
if !all_params.contains(param_name) # Don't override child class params
|
||||
all_params[param_name] = class_params[param_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Move to parent class
|
||||
current_class = super(current_class)
|
||||
end
|
||||
|
||||
return all_params
|
||||
end
|
||||
|
||||
# Helper method to get a resolved value from either a static value or a value provider
|
||||
# This is the same as accessing obj.param_name but with explicit time
|
||||
#
|
||||
|
||||
@ -26,7 +26,7 @@ class SequenceManager
|
||||
self.is_running = false
|
||||
|
||||
# Repeat logic
|
||||
self.repeat_count = repeat_count != nil ? repeat_count : 1 # Default: run once
|
||||
self.repeat_count = repeat_count != nil ? repeat_count : 1 # Default: run once (can be function or number)
|
||||
self.current_iteration = 0
|
||||
self.is_repeat_sequence = repeat_count != nil && repeat_count != 1
|
||||
end
|
||||
@ -56,10 +56,10 @@ class SequenceManager
|
||||
return self
|
||||
end
|
||||
|
||||
# Add an assignment step directly
|
||||
def push_assign_step(closure)
|
||||
# Add a closure step directly (used for both assign and log steps)
|
||||
def push_closure_step(closure)
|
||||
self.steps.push({
|
||||
"type": "assign",
|
||||
"type": "closure",
|
||||
"closure": closure
|
||||
})
|
||||
return self
|
||||
@ -150,7 +150,7 @@ class SequenceManager
|
||||
# Sub-sequence finished, advance to next step
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
elif current_step["type"] == "assign"
|
||||
elif current_step["type"] == "closure"
|
||||
# Assign steps are handled in batches by advance_to_next_step
|
||||
# This should not happen in normal flow, but handle it just in case
|
||||
self.execute_assign_steps_batch(current_time)
|
||||
@ -192,18 +192,18 @@ class SequenceManager
|
||||
var anim = step["animation"]
|
||||
self.engine.remove_animation(anim)
|
||||
|
||||
elif step["type"] == "assign"
|
||||
# Assign steps should be handled in batches by execute_assign_steps_batch
|
||||
elif step["type"] == "closure"
|
||||
# Closure steps should be handled in batches by execute_assign_steps_batch
|
||||
# This should not happen in normal flow, but handle it for safety
|
||||
var assign_closure = step["closure"]
|
||||
if assign_closure != nil
|
||||
assign_closure(self.engine)
|
||||
var closure_func = step["closure"]
|
||||
if closure_func != nil
|
||||
closure_func(self.engine)
|
||||
end
|
||||
|
||||
elif step["type"] == "subsequence"
|
||||
# Start sub-sequence (including repeat sequences)
|
||||
var sub_seq = step["sequence_manager"]
|
||||
sub_seq.start()
|
||||
sub_seq.start(current_time)
|
||||
end
|
||||
|
||||
self.step_start_time = current_time
|
||||
@ -228,16 +228,16 @@ class SequenceManager
|
||||
end
|
||||
end
|
||||
|
||||
# Execute all consecutive assign steps in a batch to avoid black frames
|
||||
# Execute all consecutive closure steps in a batch to avoid black frames
|
||||
def execute_assign_steps_batch(current_time)
|
||||
# Execute all consecutive assign steps
|
||||
# Execute all consecutive closure steps (including both assign and log steps)
|
||||
while self.step_index < size(self.steps)
|
||||
var step = self.steps[self.step_index]
|
||||
if step["type"] == "assign"
|
||||
# Execute assignment closure
|
||||
var assign_closure = step["closure"]
|
||||
if assign_closure != nil
|
||||
assign_closure(self.engine)
|
||||
if step["type"] == "closure"
|
||||
# Execute closure function
|
||||
var closure_func = step["closure"]
|
||||
if closure_func != nil
|
||||
closure_func(self.engine)
|
||||
end
|
||||
self.step_index += 1
|
||||
else
|
||||
@ -257,8 +257,11 @@ class SequenceManager
|
||||
def complete_iteration(current_time)
|
||||
self.current_iteration += 1
|
||||
|
||||
# Resolve repeat count (may be a function)
|
||||
var resolved_repeat_count = self.get_resolved_repeat_count()
|
||||
|
||||
# Check if we should continue repeating
|
||||
if self.repeat_count == -1 || self.current_iteration < self.repeat_count
|
||||
if resolved_repeat_count == -1 || self.current_iteration < resolved_repeat_count
|
||||
# Start next iteration
|
||||
self.step_index = 0
|
||||
self.execute_current_step(current_time)
|
||||
@ -268,6 +271,15 @@ class SequenceManager
|
||||
end
|
||||
end
|
||||
|
||||
# Resolve repeat count (handle both functions and numbers)
|
||||
def get_resolved_repeat_count()
|
||||
if type(self.repeat_count) == "function"
|
||||
return self.repeat_count(self.engine)
|
||||
else
|
||||
return self.repeat_count
|
||||
end
|
||||
end
|
||||
|
||||
# Check if sequence is running
|
||||
def is_sequence_running()
|
||||
return self.is_running
|
||||
|
||||
@ -40,7 +40,7 @@ class Token
|
||||
# Control flow keywords
|
||||
"play", "for", "with", "repeat", "times", "forever", "if", "else", "elif",
|
||||
"choose", "random", "on", "run", "wait", "goto", "interrupt", "resume",
|
||||
"while", "from", "to", "return",
|
||||
"while", "from", "to", "return", "reset", "restart",
|
||||
|
||||
# Modifier keywords (only actual DSL syntax keywords)
|
||||
"at", "ease", "sync", "every", "stagger", "across", "pixels",
|
||||
|
||||
@ -120,7 +120,7 @@ class SimpleDSLTranspiler
|
||||
var obj_name = run_stmt["name"]
|
||||
var comment = run_stmt["comment"]
|
||||
# In templates, use underscore suffix for local variables
|
||||
self.add(f"engine.add_animation({obj_name}_){comment}")
|
||||
self.add(f"engine.add({obj_name}_){comment}")
|
||||
end
|
||||
end
|
||||
|
||||
@ -197,8 +197,14 @@ class SimpleDSLTranspiler
|
||||
if !self.strip_initialized
|
||||
self.generate_default_strip_initialization()
|
||||
end
|
||||
# Check if this is a property assignment (identifier.property = value)
|
||||
self.process_property_assignment()
|
||||
|
||||
# Check if this is a log function call
|
||||
if tok.value == "log" && self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
self.process_standalone_log()
|
||||
else
|
||||
# Check if this is a property assignment (identifier.property = value)
|
||||
self.process_property_assignment()
|
||||
end
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
@ -280,6 +286,9 @@ class SimpleDSLTranspiler
|
||||
if type(ref_instance) != "string"
|
||||
self.symbol_table[name] = ref_instance
|
||||
end
|
||||
else
|
||||
# Add simple color to symbol table with a marker
|
||||
self.symbol_table[name] = "color"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -333,7 +342,7 @@ class SimpleDSLTranspiler
|
||||
self.expect_left_paren()
|
||||
var value = self.expect_number()
|
||||
self.expect_comma()
|
||||
var color = self.process_value("color") # Reuse existing color parsing
|
||||
var color = self.process_palette_color() # Use specialized palette color processing
|
||||
self.expect_right_paren()
|
||||
|
||||
# Convert to VRGB format entry and store as integer
|
||||
@ -349,7 +358,7 @@ class SimpleDSLTranspiler
|
||||
return
|
||||
end
|
||||
|
||||
var color = self.process_value("color") # Reuse existing color parsing
|
||||
var color = self.process_palette_color() # Use specialized palette color processing
|
||||
|
||||
# Convert to VRGB format entry and store as integer after setting alpha to 0xFF
|
||||
var vrgb_entry = self.convert_to_vrgb(0xFF, color)
|
||||
@ -474,6 +483,9 @@ class SimpleDSLTranspiler
|
||||
if type(ref_instance) != "string"
|
||||
self.symbol_table[name] = ref_instance
|
||||
end
|
||||
else
|
||||
# Add simple animation to symbol table with a marker
|
||||
self.symbol_table[name] = "animation"
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -503,13 +515,31 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
self.expect_assign()
|
||||
|
||||
# Check if this is a value provider function call
|
||||
var is_value_provider = false
|
||||
var tok = self.current()
|
||||
if (tok.type == animation_dsl.Token.KEYWORD || tok.type == animation_dsl.Token.IDENTIFIER) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
|
||||
var func_name = tok.value
|
||||
# Check if this is a value provider factory function
|
||||
if self._validate_value_provider_factory_exists(func_name)
|
||||
is_value_provider = true
|
||||
end
|
||||
end
|
||||
|
||||
var value = self.process_value("variable")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"var {name}_ = {value}{inline_comment}")
|
||||
|
||||
# Add variable to symbol table for validation
|
||||
# Use a string marker to indicate this is a variable (not an instance)
|
||||
self.symbol_table[name] = "variable"
|
||||
# Add to symbol table with appropriate marker
|
||||
if is_value_provider
|
||||
self.symbol_table[name] = "value_provider"
|
||||
else
|
||||
# Use a string marker to indicate this is a variable (not an instance)
|
||||
self.symbol_table[name] = "variable"
|
||||
end
|
||||
end
|
||||
|
||||
# Process template definition: template name { param ... }
|
||||
@ -569,7 +599,7 @@ class SimpleDSLTranspiler
|
||||
var body_tokens = []
|
||||
var brace_depth = 0
|
||||
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
while !self.at_end()
|
||||
var tok = self.current()
|
||||
|
||||
if tok == nil || tok.type == animation_dsl.Token.EOF
|
||||
@ -643,9 +673,9 @@ class SimpleDSLTranspiler
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
else
|
||||
var count = self.expect_number()
|
||||
var count_expr = self.process_value("repeat_count")
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
repeat_count = count_expr
|
||||
end
|
||||
elif current_tok.value == "forever"
|
||||
# New syntax: sequence name forever { ... } (repeat is optional)
|
||||
@ -656,9 +686,9 @@ class SimpleDSLTranspiler
|
||||
elif current_tok != nil && current_tok.type == animation_dsl.Token.NUMBER
|
||||
# New syntax: sequence name N times { ... } (repeat is optional)
|
||||
is_repeat_syntax = true
|
||||
var count = self.expect_number()
|
||||
var count_expr = self.process_value("repeat_count")
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
repeat_count = count_expr
|
||||
end
|
||||
|
||||
self.expect_left_brace()
|
||||
@ -712,6 +742,12 @@ class SimpleDSLTranspiler
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "wait"
|
||||
self.process_wait_statement_fluent()
|
||||
|
||||
elif tok.type == animation_dsl.Token.IDENTIFIER && tok.value == "log"
|
||||
self.process_log_statement_fluent()
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && (tok.value == "reset" || tok.value == "restart")
|
||||
self.process_reset_restart_statement_fluent()
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "repeat"
|
||||
self.next() # skip 'repeat'
|
||||
|
||||
@ -722,9 +758,9 @@ class SimpleDSLTranspiler
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
else
|
||||
var count = self.expect_number()
|
||||
var count_expr = self.process_value("repeat_count")
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
repeat_count = count_expr
|
||||
end
|
||||
|
||||
self.expect_left_brace()
|
||||
@ -751,9 +787,13 @@ class SimpleDSLTranspiler
|
||||
if self.peek() != nil && self.peek().type == animation_dsl.Token.DOT
|
||||
self.process_sequence_assignment_fluent()
|
||||
else
|
||||
# Unknown identifier in sequence - this is an error
|
||||
self.error(f"Unknown command '{tok.value}' in sequence. Valid sequence commands are: play, wait, repeat, reset, restart, log, or property assignments (object.property = value)")
|
||||
self.skip_statement()
|
||||
end
|
||||
else
|
||||
# Unknown token type in sequence - this is an error
|
||||
self.error(f"Invalid statement in sequence. Expected: play, wait, repeat, reset, restart, log, or property assignments")
|
||||
self.skip_statement()
|
||||
end
|
||||
end
|
||||
@ -769,7 +809,7 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Create assignment step using fluent style
|
||||
var closure_code = f"def (engine) {object_name}_.{property_name} = {value} end"
|
||||
self.add(f"{self.get_indent()}.push_assign_step({closure_code}){inline_comment}")
|
||||
self.add(f"{self.get_indent()}.push_closure_step({closure_code}){inline_comment}")
|
||||
end
|
||||
|
||||
# Process property assignment inside sequences: object.property = value (legacy)
|
||||
@ -870,7 +910,66 @@ class SimpleDSLTranspiler
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"{self.get_indent()}.push_wait_step({duration}){inline_comment}")
|
||||
end
|
||||
|
||||
# Unified log processing method - handles all log contexts
|
||||
def process_log_call(args_str, context_type, inline_comment)
|
||||
# Convert DSL log("message") to Berry log(f"message", 3)
|
||||
if context_type == "fluent"
|
||||
# For sequence context - wrap in closure
|
||||
var closure_code = f"def (engine) log(f\"{args_str}\", 3) end"
|
||||
return f"{self.get_indent()}.push_closure_step({closure_code}){inline_comment}"
|
||||
elif context_type == "expression"
|
||||
# For expression context - return just the call (no inline comment)
|
||||
return f"log(f\"{args_str}\", 3)"
|
||||
else
|
||||
# For standalone context - direct call with comment
|
||||
return f"log(f\"{args_str}\", 3){inline_comment}"
|
||||
end
|
||||
end
|
||||
|
||||
# Helper method to process log statement using fluent style
|
||||
def process_log_statement_fluent()
|
||||
self.next() # skip 'log'
|
||||
self.expect_left_paren()
|
||||
|
||||
# Process the message string
|
||||
var message_tok = self.current()
|
||||
if message_tok == nil || message_tok.type != animation_dsl.Token.STRING
|
||||
self.error("log() function requires a string message")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
var message = message_tok.value
|
||||
self.next() # consume string
|
||||
self.expect_right_paren()
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
# Use unified log processing
|
||||
var log_code = self.process_log_call(message, "fluent", inline_comment)
|
||||
self.add(log_code)
|
||||
end
|
||||
|
||||
# Helper method to process reset/restart statement using fluent style
|
||||
def process_reset_restart_statement_fluent()
|
||||
var keyword = self.current().value # "reset" or "restart"
|
||||
self.next() # skip 'reset' or 'restart'
|
||||
|
||||
# Expect the value provider identifier
|
||||
var val_name = self.expect_identifier()
|
||||
|
||||
# Validate that the value is a value_provider at transpile time
|
||||
if !self._validate_value_provider_reference(val_name, keyword)
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Generate closure step that calls val.start(engine.time_ms)
|
||||
var closure_code = f"def (engine) {val_name}_.start(engine.time_ms) end"
|
||||
self.add(f"{self.get_indent()}.push_closure_step({closure_code}){inline_comment}")
|
||||
end
|
||||
|
||||
# Process import statement: import user_functions or import module_name
|
||||
def process_import()
|
||||
@ -883,6 +982,29 @@ class SimpleDSLTranspiler
|
||||
self.add(f'import {module_name} {inline_comment}')
|
||||
end
|
||||
|
||||
# Process standalone log statement: log("message")
|
||||
def process_standalone_log()
|
||||
self.next() # skip 'log'
|
||||
self.expect_left_paren()
|
||||
|
||||
# Process the message string
|
||||
var message_tok = self.current()
|
||||
if message_tok == nil || message_tok.type != animation_dsl.Token.STRING
|
||||
self.error("log() function requires a string message")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
var message = message_tok.value
|
||||
self.next() # consume string
|
||||
self.expect_right_paren()
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
# Use unified log processing
|
||||
var log_code = self.process_log_call(message, "standalone", inline_comment)
|
||||
self.add(log_code)
|
||||
end
|
||||
|
||||
# Process run statement: run demo
|
||||
def process_run()
|
||||
self.next() # skip 'run'
|
||||
@ -904,8 +1026,18 @@ class SimpleDSLTranspiler
|
||||
def process_property_assignment()
|
||||
var object_name = self.expect_identifier()
|
||||
|
||||
# Check if this is a function call (template call)
|
||||
# Check if this is a function call (template call or special function)
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.LEFT_PAREN
|
||||
# Special case for log function - allow as standalone
|
||||
if object_name == "log"
|
||||
var args = self.process_function_arguments()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
# Use unified log processing
|
||||
var log_code = self.process_log_call(args, "standalone", inline_comment)
|
||||
self.add(log_code)
|
||||
return
|
||||
end
|
||||
|
||||
# This is a standalone function call - check if it's a template
|
||||
if self.template_definitions.contains(object_name)
|
||||
var args = self.process_function_arguments()
|
||||
@ -972,6 +1104,40 @@ class SimpleDSLTranspiler
|
||||
return self.process_additive_expression(context, true) # true = top-level expression
|
||||
end
|
||||
|
||||
# Process palette color with strict validation
|
||||
# Only accepts predefined color names or hex color literals
|
||||
def process_palette_color()
|
||||
var tok = self.current()
|
||||
if tok == nil
|
||||
self.error("Expected color value in palette")
|
||||
return "0xFFFFFFFF"
|
||||
end
|
||||
|
||||
# Handle hex color literals
|
||||
if tok.type == animation_dsl.Token.COLOR
|
||||
self.next()
|
||||
return self.convert_color(tok.value)
|
||||
end
|
||||
|
||||
# Handle identifiers (color names)
|
||||
if tok.type == animation_dsl.Token.IDENTIFIER
|
||||
var name = tok.value
|
||||
self.next()
|
||||
|
||||
# Only accept predefined color names
|
||||
if animation_dsl.is_color_name(name)
|
||||
return self.get_named_color_value(name)
|
||||
end
|
||||
|
||||
# Reject any other identifier
|
||||
self.error(f"Unknown color '{name}'. Palettes only accept hex colors (0xRRGGBB) or predefined color names (like 'red', 'blue', 'green'), but not custom colors defined previously. For dynamic palettes with custom colors, use user functions instead.")
|
||||
return "0xFFFFFFFF"
|
||||
end
|
||||
|
||||
self.error("Expected color value in palette. Use hex colors (0xRRGGBB) or predefined color names (like 'red', 'blue', 'green').")
|
||||
return "0xFFFFFFFF"
|
||||
end
|
||||
|
||||
# Process additive expressions (+ and -)
|
||||
def process_additive_expression(context, is_top_level)
|
||||
var left = self.process_multiplicative_expression(context, is_top_level)
|
||||
@ -989,8 +1155,20 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
# Only create closures at the top level, but not for anonymous functions
|
||||
if is_top_level && self.is_computed_expression_string(left) && !self.is_anonymous_function(left)
|
||||
return self.create_computation_closure_from_string(left)
|
||||
if is_top_level && !self.is_anonymous_function(left)
|
||||
# Special handling for repeat_count context - always create simple function for property access
|
||||
if context == "repeat_count"
|
||||
import string
|
||||
if self.is_computed_expression_string(left) || string.find(left, ".") >= 0
|
||||
return self.create_simple_function_from_string(left)
|
||||
else
|
||||
return left
|
||||
end
|
||||
elif self.is_computed_expression_string(left)
|
||||
return self.create_computation_closure_from_string(left)
|
||||
else
|
||||
return left
|
||||
end
|
||||
else
|
||||
return left
|
||||
end
|
||||
@ -1147,8 +1325,13 @@ class SimpleDSLTranspiler
|
||||
object_ref = f"{name}_"
|
||||
end
|
||||
|
||||
# Return a closure expression that will be wrapped by the caller if needed
|
||||
return f"self.resolve({object_ref}, '{property_name}')"
|
||||
# For repeat_count context, generate simple property access
|
||||
if context == "repeat_count"
|
||||
return f"{object_ref}.{property_name}"
|
||||
else
|
||||
# Return a closure expression that will be wrapped by the caller if needed
|
||||
return f"self.resolve({object_ref}, '{property_name}')"
|
||||
end
|
||||
end
|
||||
|
||||
# Check for palette constants
|
||||
@ -1253,6 +1436,8 @@ class SimpleDSLTranspiler
|
||||
return has_dynamic_content
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Create a closure for computed expressions from a complete expression string
|
||||
def create_computation_closure_from_string(expr_str)
|
||||
import string
|
||||
@ -1296,6 +1481,13 @@ class SimpleDSLTranspiler
|
||||
return f"animation.create_closure_value(engine, {closure_code})"
|
||||
end
|
||||
|
||||
# Create a simple function for repeat counts (no closure wrapper)
|
||||
def create_simple_function_from_string(expr_str)
|
||||
# For repeat counts, create a simple function that takes engine and returns the value
|
||||
# The expression should already be in simple form like "col1_.palette_size"
|
||||
return f"def (engine) return {expr_str} end"
|
||||
end
|
||||
|
||||
# Transform a complete expression for use in a closure, handling ValueProvider instances
|
||||
def transform_expression_for_closure(expr_str)
|
||||
import string
|
||||
@ -1489,6 +1681,13 @@ class SimpleDSLTranspiler
|
||||
return f"{func_name}({args})" # Return as-is for transformation in closure
|
||||
end
|
||||
|
||||
# Special case for log function - call global log function directly
|
||||
if func_name == "log"
|
||||
var args = self.process_function_arguments()
|
||||
# Use unified log processing (return expression for use in contexts)
|
||||
return self.process_log_call(args, "expression", "")
|
||||
end
|
||||
|
||||
var args = self.process_function_arguments()
|
||||
|
||||
# Check if it's a template call first
|
||||
@ -1811,6 +2010,13 @@ class SimpleDSLTranspiler
|
||||
return f"self.{func_name}({args})"
|
||||
end
|
||||
|
||||
# Special case for log function in expressions
|
||||
if func_name == "log"
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
# Use unified log processing
|
||||
return self.process_log_call(args, "expression", "")
|
||||
end
|
||||
|
||||
# Check if this is a template call
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
@ -1927,6 +2133,13 @@ class SimpleDSLTranspiler
|
||||
return f"self.{func_name}({args})" # Prefix with self. for closure context
|
||||
end
|
||||
|
||||
# Special case for log function in nested calls
|
||||
if func_name == "log"
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
# Use unified log processing
|
||||
return self.process_log_call(args, "expression", "")
|
||||
end
|
||||
|
||||
# Check if this is a template call
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
@ -2382,13 +2595,8 @@ class SimpleDSLTranspiler
|
||||
var comment = run_stmt["comment"]
|
||||
|
||||
# Check if this is a sequence or regular animation
|
||||
if self.sequence_names.contains(name)
|
||||
# It's a sequence - the closure returned a SequenceManager
|
||||
self.add(f"engine.add_sequence_manager({name}_){comment}")
|
||||
else
|
||||
# It's a regular animation
|
||||
self.add(f"engine.add_animation({name}_){comment}")
|
||||
end
|
||||
# Use unified add() method - it will detect the type automatically
|
||||
self.add(f"engine.add({name}_){comment}")
|
||||
end
|
||||
|
||||
# Single engine.start() call
|
||||
@ -2430,7 +2638,7 @@ class SimpleDSLTranspiler
|
||||
# Assume it's an animation function call or reference
|
||||
var action = self.process_value("animation")
|
||||
self.add(f" var temp_anim = {action}")
|
||||
self.add(f" engine.add_animation(temp_anim)")
|
||||
self.add(f" engine.add(temp_anim)")
|
||||
end
|
||||
end
|
||||
|
||||
@ -2713,6 +2921,47 @@ class SimpleDSLTranspiler
|
||||
return self._validate_factory_function(func_name, animation.color_provider)
|
||||
end
|
||||
|
||||
# Validate value provider factory exists and creates animation.value_provider instance
|
||||
def _validate_value_provider_factory_exists(func_name)
|
||||
return self._validate_factory_function(func_name, animation.value_provider)
|
||||
end
|
||||
|
||||
# Validate that a referenced object is a value provider or animation
|
||||
def _validate_value_provider_reference(object_name, context)
|
||||
try
|
||||
# Check if object exists in symbol table (user-defined)
|
||||
if self.symbol_table.contains(object_name)
|
||||
var marker = self.symbol_table[object_name]
|
||||
|
||||
# Check if it's marked as a value provider or animation
|
||||
if type(marker) == "string" && (marker == "value_provider" || marker == "animation")
|
||||
return true # Valid value provider or animation
|
||||
elif type(marker) == "string"
|
||||
# It's some other type (variable, color, sequence, etc.)
|
||||
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be reset/restarted.")
|
||||
return false
|
||||
else
|
||||
# It's an actual instance - check if it's a value provider or animation
|
||||
if isinstance(marker, animation.value_provider) || isinstance(marker, animation.animation)
|
||||
return true # Valid value provider or animation
|
||||
else
|
||||
self.error(f"'{object_name}' in {context} statement is not a value provider or animation. Only value providers (like oscillators) and animations can be reset/restarted.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Object not found in symbol table
|
||||
self.error(f"Undefined reference '{object_name}' in {context} statement. Make sure the value provider or animation is defined before use.")
|
||||
return false
|
||||
|
||||
except .. as e, msg
|
||||
# If validation fails for any reason, report error but continue
|
||||
self.error(f"Could not validate '{object_name}' in {context} statement: {msg}")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
# Process named arguments with parameter validation at transpile time
|
||||
def _process_named_arguments_generic(var_name, func_name)
|
||||
self.expect_left_paren()
|
||||
@ -2777,6 +3026,7 @@ class SimpleDSLTranspiler
|
||||
# Check if this is a simple function call that doesn't need anonymous function treatment
|
||||
def _is_simple_function_call(func_name)
|
||||
# Functions that return simple values and don't use named parameters
|
||||
# Note: log is handled by unified process_log_call method
|
||||
var simple_functions = [
|
||||
"strip_length",
|
||||
"static_value"
|
||||
|
||||
@ -27,7 +27,8 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
)
|
||||
},
|
||||
"cycle_period": {"min": 0, "default": 5000}, # 0 = manual only, >0 = auto cycle time in ms
|
||||
"next": {"default": 0} # Write 1 to move to next color
|
||||
"next": {"default": 0}, # Write `<n>` to move to next <n> colors
|
||||
"palette_size": {"type": "int", "default": 3} # Read-only: number of colors in palette
|
||||
}
|
||||
|
||||
# Initialize a new ColorCycleColorProvider
|
||||
@ -40,6 +41,9 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
self.current_color = self._get_color_at_index(0) # Start with first color in palette
|
||||
self.current_index = 0 # Start at first color
|
||||
|
||||
# Initialize palette_size parameter
|
||||
self.values["palette_size"] = self._get_palette_size()
|
||||
end
|
||||
|
||||
# Get palette bytes from parameter with default fallback
|
||||
@ -82,7 +86,11 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
# @param name: string - Name of the parameter that changed
|
||||
# @param value: any - New value of the parameter
|
||||
def on_param_changed(name, value)
|
||||
if name == "palette"
|
||||
if name == "palette_size"
|
||||
# palette_size is read-only - restore the actual value and raise an exception
|
||||
self.values["palette_size"] = self._get_palette_size()
|
||||
raise "value_error", "Parameter 'palette_size' is read-only"
|
||||
elif name == "palette"
|
||||
# When palette changes, update current_color if current_index is valid
|
||||
var palette_size = self._get_palette_size()
|
||||
if palette_size > 0
|
||||
@ -92,6 +100,8 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
end
|
||||
self.current_color = self._get_color_at_index(self.current_index)
|
||||
end
|
||||
# Update palette_size parameter
|
||||
self.values["palette_size"] = palette_size
|
||||
elif name == "next" && value != 0
|
||||
# Add to color index
|
||||
var palette_size = self._get_palette_size()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,7 @@ opacity_engine.start()
|
||||
var opacity_frame = animation.frame_buffer(10)
|
||||
base_anim.start()
|
||||
var render_result = base_anim.render(opacity_frame, opacity_engine.time_ms)
|
||||
base_anim.post_render(opacity_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Animation with numeric opacity should render successfully")
|
||||
assert_equals(base_anim.opacity, 128, "Numeric opacity should be preserved")
|
||||
@ -86,6 +87,7 @@ opacity_mask.start()
|
||||
# Test rendering with animation opacity
|
||||
var masked_frame = animation.frame_buffer(10)
|
||||
render_result = masked_anim.render(masked_frame, opacity_engine.time_ms)
|
||||
masked_anim.post_render(masked_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Animation with animation opacity should render successfully")
|
||||
assert_not_nil(masked_anim.opacity_frame, "Opacity frame buffer should be created")
|
||||
@ -122,6 +124,7 @@ var base_time = 2000
|
||||
pulsing_opacity.color = 0xFF808080 # Gray (50% opacity)
|
||||
|
||||
render_result = rainbow_base.render(test_frame, base_time)
|
||||
rainbow_base.post_render(test_frame, base_time)
|
||||
assert_test(render_result, "Complex opacity animation should render successfully")
|
||||
|
||||
# Test 11f: Opacity animation lifecycle management
|
||||
@ -147,6 +150,7 @@ assert_test(!auto_start_opacity.is_running, "Opacity animation should start stop
|
||||
auto_start_main.start()
|
||||
var auto_frame = animation.frame_buffer(10)
|
||||
render_result = auto_start_main.render(auto_frame, opacity_engine.time_ms)
|
||||
auto_start_main.post_render(auto_frame, opacity_engine.time_ms)
|
||||
|
||||
# Opacity animation should now be running
|
||||
assert_test(auto_start_opacity.is_running, "Opacity animation should auto-start when main animation renders")
|
||||
@ -185,6 +189,8 @@ opacity2.start()
|
||||
|
||||
var nested_frame = animation.frame_buffer(10)
|
||||
render_result = base_nested.render(nested_frame, opacity_engine.time_ms)
|
||||
base_nested.post_render(nested_frame, opacity_engine.time_ms)
|
||||
opacity1.post_render(nested_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Nested animation opacity should render successfully")
|
||||
assert_not_nil(base_nested.opacity_frame, "Base animation should have opacity frame buffer")
|
||||
@ -211,21 +217,25 @@ param_opacity.start()
|
||||
|
||||
var param_frame = animation.frame_buffer(10)
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms)
|
||||
param_base.post_render(param_frame, opacity_engine.time_ms)
|
||||
assert_test(render_result, "Animation should render before opacity parameter change")
|
||||
|
||||
# Change opacity animation color
|
||||
param_opacity.color = 0xFFFFFFFF # White (full opacity)
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 100)
|
||||
param_base.post_render(param_frame, opacity_engine.time_ms + 100)
|
||||
assert_test(render_result, "Animation should render after opacity parameter change")
|
||||
|
||||
# Change opacity animation to numeric value
|
||||
param_base.opacity = 64 # 25% opacity
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 200)
|
||||
param_base.post_render(param_frame, opacity_engine.time_ms + 200)
|
||||
assert_test(render_result, "Animation should render after changing from animation to numeric opacity")
|
||||
|
||||
# Change back to animation opacity
|
||||
param_base.opacity = param_opacity
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 300)
|
||||
param_base.post_render(param_frame, opacity_engine.time_ms + 300)
|
||||
assert_test(render_result, "Animation should render after changing from numeric to animation opacity")
|
||||
|
||||
# Test 11i: Opacity with full transparency and full opacity
|
||||
@ -241,11 +251,13 @@ edge_base.opacity = 0
|
||||
edge_base.start()
|
||||
var edge_frame = animation.frame_buffer(10)
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms)
|
||||
edge_base.post_render(edge_frame, opacity_engine.time_ms)
|
||||
assert_test(render_result, "Animation with 0 opacity should still render")
|
||||
|
||||
# Test full opacity (should render normally)
|
||||
edge_base.opacity = 255
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms + 100)
|
||||
edge_base.post_render(edge_frame, opacity_engine.time_ms + 100)
|
||||
assert_test(render_result, "Animation with full opacity should render normally")
|
||||
|
||||
# Test transparent animation as opacity
|
||||
@ -257,6 +269,7 @@ transparent_opacity.name = "transparent_opacity"
|
||||
edge_base.opacity = transparent_opacity
|
||||
transparent_opacity.start()
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms + 200)
|
||||
edge_base.post_render(edge_frame, opacity_engine.time_ms + 200)
|
||||
assert_test(render_result, "Animation with transparent animation opacity should render")
|
||||
|
||||
# Test 11j: Performance with animation opacity
|
||||
|
||||
@ -104,82 +104,6 @@ assert(result == true, "Update should return true for looping animation")
|
||||
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")
|
||||
|
||||
# Test get_progress
|
||||
engine.time_ms = 4500
|
||||
var non_loop_progress = animation.animation(engine)
|
||||
non_loop_progress.priority = 1
|
||||
non_loop_progress.duration = 1000
|
||||
non_loop_progress.loop = false
|
||||
non_loop_progress.opacity = 255
|
||||
non_loop_progress.name = "progress"
|
||||
non_loop_progress.color = 0xFF0000
|
||||
non_loop_progress.start(4000)
|
||||
non_loop_progress.update(engine.time_ms)
|
||||
assert(non_loop_progress.get_progress() == 128, "Progress should be 128 at midpoint (500ms of 1000ms)")
|
||||
|
||||
# Test progress at start (0ms elapsed)
|
||||
engine.time_ms = 4000
|
||||
var start_progress = animation.animation(engine)
|
||||
start_progress.priority = 1
|
||||
start_progress.duration = 1000
|
||||
start_progress.loop = false
|
||||
start_progress.opacity = 255
|
||||
start_progress.name = "start"
|
||||
start_progress.color = 0xFF0000
|
||||
start_progress.start(4000)
|
||||
start_progress.update(engine.time_ms)
|
||||
assert(start_progress.get_progress() == 0, "Progress should be 0 at start")
|
||||
|
||||
# Test progress at end (1000ms elapsed) - test before update stops the animation
|
||||
engine.time_ms = 5000
|
||||
var end_progress = animation.animation(engine)
|
||||
end_progress.priority = 1
|
||||
end_progress.duration = 1000
|
||||
end_progress.loop = false
|
||||
end_progress.opacity = 255
|
||||
end_progress.name = "end"
|
||||
end_progress.color = 0xFF0000
|
||||
end_progress.start(4000)
|
||||
end_progress.current_time = 5000 # Set current time manually to avoid stopping
|
||||
assert(end_progress.get_progress() == 255, "Progress should be 255 at end")
|
||||
|
||||
# Test progress at quarter point (250ms elapsed)
|
||||
engine.time_ms = 4250
|
||||
var quarter_progress = animation.animation(engine)
|
||||
quarter_progress.priority = 1
|
||||
quarter_progress.duration = 1000
|
||||
quarter_progress.loop = false
|
||||
quarter_progress.opacity = 255
|
||||
quarter_progress.name = "quarter"
|
||||
quarter_progress.color = 0xFF0000
|
||||
quarter_progress.start(4000)
|
||||
quarter_progress.update(engine.time_ms)
|
||||
assert(quarter_progress.get_progress() == 64, "Progress should be 64 at quarter point (250ms of 1000ms)")
|
||||
|
||||
# Test looping animation progress (should wrap around)
|
||||
engine.time_ms = 5500 # 1500ms elapsed = 1.5 loops of 1000ms
|
||||
var loop_progress = animation.animation(engine)
|
||||
loop_progress.priority = 1
|
||||
loop_progress.duration = 1000
|
||||
loop_progress.loop = true
|
||||
loop_progress.opacity = 255
|
||||
loop_progress.name = "loop_progress"
|
||||
loop_progress.color = 0xFF0000
|
||||
loop_progress.start(4000)
|
||||
loop_progress.current_time = 5500 # Set manually to avoid loop adjustment in update()
|
||||
assert(loop_progress.get_progress() == 128, "Looping animation should wrap around (500ms into second loop)")
|
||||
|
||||
# Test infinite animation progress
|
||||
var infinite_anim = animation.animation(engine)
|
||||
infinite_anim.priority = 1
|
||||
infinite_anim.duration = 0 # infinite
|
||||
infinite_anim.loop = false
|
||||
infinite_anim.opacity = 255
|
||||
infinite_anim.name = "infinite"
|
||||
infinite_anim.color = 0xFF0000
|
||||
infinite_anim.start(4000)
|
||||
assert(infinite_anim.get_progress() == 0, "Infinite animation should always return 0 progress")
|
||||
|
||||
# Test direct parameter assignment (no setter methods needed)
|
||||
var setter_anim = animation.animation(engine)
|
||||
setter_anim.priority = 20
|
||||
@ -192,12 +116,6 @@ assert(setter_anim.loop == true, "Loop should be updated")
|
||||
# Test parameter handling with static parameters
|
||||
var param_anim = animation.animation(engine)
|
||||
|
||||
# Test static parameter metadata access
|
||||
var params_metadata = param_anim.get_params_metadata()
|
||||
assert(params_metadata.contains("priority"), "Priority parameter should be defined")
|
||||
assert(params_metadata["priority"]["min"] == 0, "Priority parameter min should be 0")
|
||||
assert(params_metadata["priority"]["default"] == 10, "Priority parameter default should be 10")
|
||||
|
||||
# Test parameter validation and setting (using existing 'priority' parameter)
|
||||
assert(param_anim.set_param("priority", 75) == true, "Valid parameter should be accepted")
|
||||
assert(param_anim.get_param("priority", nil) == 75, "Parameter value should be updated")
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env berry
|
||||
|
||||
# Test for ColorCycleColorProvider palette_size read-only parameter
|
||||
import animation
|
||||
|
||||
# Mock engine for testing
|
||||
class MockEngine
|
||||
var time_ms
|
||||
def init()
|
||||
self.time_ms = 1000
|
||||
end
|
||||
end
|
||||
|
||||
def test_palette_size_parameter_access()
|
||||
print("Testing palette_size parameter access...")
|
||||
|
||||
var engine = MockEngine()
|
||||
var provider = animation.color_cycle(engine)
|
||||
|
||||
# Test 1: Default palette_size should be 3
|
||||
var default_size = provider.palette_size
|
||||
assert(default_size == 3, f"Default palette_size should be 3, got {default_size}")
|
||||
|
||||
# Test 2: palette_size should match _get_palette_size()
|
||||
var internal_size = provider._get_palette_size()
|
||||
assert(default_size == internal_size, f"palette_size ({default_size}) should match _get_palette_size() ({internal_size})")
|
||||
|
||||
print("✓ palette_size parameter access tests passed!")
|
||||
end
|
||||
|
||||
def test_palette_size_read_only()
|
||||
print("Testing palette_size is read-only...")
|
||||
|
||||
var engine = MockEngine()
|
||||
var provider = animation.color_cycle(engine)
|
||||
|
||||
var original_size = provider.palette_size
|
||||
|
||||
# Test 1: Direct assignment should raise exception
|
||||
var caught_exception = false
|
||||
try
|
||||
provider.palette_size = 10
|
||||
except "value_error" as e
|
||||
caught_exception = true
|
||||
end
|
||||
assert(caught_exception, "Direct assignment to palette_size should raise value_error")
|
||||
|
||||
# Test 2: Value should remain unchanged after failed write
|
||||
var size_after_write = provider.palette_size
|
||||
assert(size_after_write == original_size, f"palette_size should remain {original_size} after failed write, got {size_after_write}")
|
||||
|
||||
# Test 3: set_param method should return false and not change value
|
||||
var set_success = provider.set_param("palette_size", 99)
|
||||
assert(set_success == false, "set_param should return false for read-only parameter")
|
||||
|
||||
var size_after_set_param = provider.palette_size
|
||||
assert(size_after_set_param == original_size, f"palette_size should remain {original_size} after set_param, got {size_after_set_param}")
|
||||
|
||||
# Test 4: get_param should return the actual value, not the attempted write
|
||||
var raw_value = provider.get_param("palette_size")
|
||||
assert(raw_value == original_size, f"get_param should return actual value {original_size}, got {raw_value}")
|
||||
|
||||
print("✓ palette_size read-only tests passed!")
|
||||
end
|
||||
|
||||
def test_palette_size_updates_with_palette_changes()
|
||||
print("Testing palette_size updates when palette changes...")
|
||||
|
||||
var engine = MockEngine()
|
||||
var provider = animation.color_cycle(engine)
|
||||
|
||||
# Test 1: 2-color palette
|
||||
var palette_2 = bytes("FFFF0000" "FF00FF00")
|
||||
provider.palette = palette_2
|
||||
var size_2 = provider.palette_size
|
||||
assert(size_2 == 2, f"palette_size should be 2 for 2-color palette, got {size_2}")
|
||||
|
||||
# Test 2: 5-color palette
|
||||
var palette_5 = bytes("FFFF0000" "FF00FF00" "FF0000FF" "FFFFFF00" "FFFF00FF")
|
||||
provider.palette = palette_5
|
||||
var size_5 = provider.palette_size
|
||||
assert(size_5 == 5, f"palette_size should be 5 for 5-color palette, got {size_5}")
|
||||
|
||||
# Test 3: 1-color palette
|
||||
var palette_1 = bytes("FFFF0000")
|
||||
provider.palette = palette_1
|
||||
var size_1 = provider.palette_size
|
||||
assert(size_1 == 1, f"palette_size should be 1 for 1-color palette, got {size_1}")
|
||||
|
||||
# Test 4: Empty palette
|
||||
var empty_palette = bytes()
|
||||
provider.palette = empty_palette
|
||||
var size_0 = provider.palette_size
|
||||
assert(size_0 == 0, f"palette_size should be 0 for empty palette, got {size_0}")
|
||||
|
||||
# Test 5: Large palette (10 colors)
|
||||
var palette_10 = bytes(
|
||||
"FFFF0000" "FF00FF00" "FF0000FF" "FFFFFF00" "FFFF00FF"
|
||||
"FF800000" "FF008000" "FF000080" "FF808000" "FF800080"
|
||||
)
|
||||
provider.palette = palette_10
|
||||
var size_10 = provider.palette_size
|
||||
assert(size_10 == 10, f"palette_size should be 10 for 10-color palette, got {size_10}")
|
||||
|
||||
# Test 6: Verify palette_size is still read-only after palette changes
|
||||
var caught_exception = false
|
||||
try
|
||||
provider.palette_size = 15
|
||||
except "value_error"
|
||||
caught_exception = true
|
||||
end
|
||||
assert(caught_exception, "palette_size should still be read-only after palette changes")
|
||||
|
||||
var final_size = provider.palette_size
|
||||
assert(final_size == 10, f"palette_size should remain 10 after failed write, got {final_size}")
|
||||
|
||||
print("✓ palette_size update tests passed!")
|
||||
end
|
||||
|
||||
def test_palette_size_with_new_instances()
|
||||
print("Testing palette_size with new provider instances...")
|
||||
|
||||
var engine = MockEngine()
|
||||
|
||||
# Test 1: Multiple instances should have correct default palette_size
|
||||
var provider1 = animation.color_cycle(engine)
|
||||
var provider2 = animation.color_cycle(engine)
|
||||
|
||||
assert(provider1.palette_size == 3, "First provider should have default palette_size of 3")
|
||||
assert(provider2.palette_size == 3, "Second provider should have default palette_size of 3")
|
||||
|
||||
# Test 2: Changing one instance shouldn't affect the other
|
||||
var custom_palette = bytes("FFFF0000" "FF00FF00")
|
||||
provider1.palette = custom_palette
|
||||
|
||||
assert(provider1.palette_size == 2, "First provider should have palette_size of 2 after change")
|
||||
assert(provider2.palette_size == 3, "Second provider should still have palette_size of 3")
|
||||
|
||||
# Test 3: Both instances should maintain read-only behavior
|
||||
var caught_exception_1 = false
|
||||
var caught_exception_2 = false
|
||||
|
||||
try
|
||||
provider1.palette_size = 5
|
||||
except "value_error"
|
||||
caught_exception_1 = true
|
||||
end
|
||||
|
||||
try
|
||||
provider2.palette_size = 7
|
||||
except "value_error"
|
||||
caught_exception_2 = true
|
||||
end
|
||||
|
||||
assert(caught_exception_1, "First provider should reject palette_size writes")
|
||||
assert(caught_exception_2, "Second provider should reject palette_size writes")
|
||||
|
||||
assert(provider1.palette_size == 2, "First provider palette_size should remain 2")
|
||||
assert(provider2.palette_size == 3, "Second provider palette_size should remain 3")
|
||||
|
||||
print("✓ Multiple instance tests passed!")
|
||||
end
|
||||
|
||||
def test_palette_size_parameter_metadata()
|
||||
print("Testing palette_size parameter metadata...")
|
||||
|
||||
var engine = MockEngine()
|
||||
var provider = animation.color_cycle(engine)
|
||||
|
||||
# Test 1: Parameter should exist in metadata
|
||||
var metadata = provider.get_param_metadata("palette_size")
|
||||
assert(metadata != nil, "palette_size should have metadata")
|
||||
|
||||
# Test 2: Check parameter definition
|
||||
assert(metadata.contains("type"), "palette_size metadata should have type")
|
||||
assert(metadata["type"] == "int", f"palette_size type should be 'int', got '{metadata['type']}'")
|
||||
|
||||
assert(metadata.contains("default"), "palette_size metadata should have default")
|
||||
assert(metadata["default"] == 3, f"palette_size default should be 3, got {metadata['default']}")
|
||||
|
||||
print("✓ Parameter metadata tests passed!")
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
test_palette_size_parameter_access()
|
||||
test_palette_size_read_only()
|
||||
test_palette_size_updates_with_palette_changes()
|
||||
test_palette_size_with_new_instances()
|
||||
test_palette_size_parameter_metadata()
|
||||
|
||||
print("✓ All ColorCycleColorProvider palette_size tests completed successfully!")
|
||||
@ -203,7 +203,7 @@ def test_sequence_processing()
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should define sequence manager")
|
||||
assert(string.find(berry_code, "red_anim") >= 0, "Should reference animation")
|
||||
assert(string.find(berry_code, ".push_play_step(red_anim_, 2000)") >= 0, "Should create play step")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
|
||||
assert(string.find(berry_code, "engine.add(demo_)") >= 0, "Should add sequence manager")
|
||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
||||
|
||||
# Test repeat in sequence
|
||||
|
||||
397
lib/libesp32/berry_animation/src/tests/dsl_reset_restart_test.be
Normal file
397
lib/libesp32/berry_animation/src/tests/dsl_reset_restart_test.be
Normal file
@ -0,0 +1,397 @@
|
||||
# DSL Reset/Restart Test Suite
|
||||
# Tests for reset and restart functionality in sequences
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation/src -e "import tasmota def log(x) print(x) end" lib/libesp32/berry_animation/src/tests/dsl_reset_restart_test.be
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import string
|
||||
|
||||
# Test basic reset functionality
|
||||
def test_reset_basic()
|
||||
print("Testing basic reset functionality...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s)\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset osc_val\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for reset")
|
||||
assert(string.find(berry_code, "animation.triangle(engine)") >= 0, "Should generate triangle oscillator")
|
||||
assert(string.find(berry_code, "push_closure_step") >= 0, "Should generate closure step for reset")
|
||||
assert(string.find(berry_code, "osc_val_.start(engine.time_ms)") >= 0, "Should call start() method")
|
||||
|
||||
print("✓ Basic reset test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test basic restart functionality
|
||||
def test_restart_basic()
|
||||
print("Testing basic restart functionality...")
|
||||
|
||||
var dsl_source = "set smooth_val = smooth(min_value=5, max_value=15, duration=3s)\n" +
|
||||
"animation test_anim = solid(color=blue)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" restart smooth_val\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for restart")
|
||||
assert(string.find(berry_code, "animation.smooth(engine)") >= 0, "Should generate smooth oscillator")
|
||||
assert(string.find(berry_code, "push_closure_step") >= 0, "Should generate closure step for restart")
|
||||
assert(string.find(berry_code, "smooth_val_.start(engine.time_ms)") >= 0, "Should call start() method")
|
||||
|
||||
print("✓ Basic restart test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart with different value provider types
|
||||
def test_reset_restart_different_providers()
|
||||
print("Testing reset/restart with different value provider types...")
|
||||
|
||||
var dsl_source = "set triangle_val = triangle(min_value=0, max_value=29, duration=5s)\n" +
|
||||
"set cosine_val = cosine_osc(min_value=0, max_value=29, duration=5s)\n" +
|
||||
"set sine_val = sine_osc(min_value=0, max_value=255, duration=2s)\n" +
|
||||
"animation test_anim = solid(color=green)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
" reset triangle_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" restart cosine_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" reset sine_val\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for multiple providers")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should reset triangle")
|
||||
assert(string.find(berry_code, "cosine_val_.start(engine.time_ms)") >= 0, "Should restart cosine")
|
||||
assert(string.find(berry_code, "sine_val_.start(engine.time_ms)") >= 0, "Should reset sine")
|
||||
|
||||
# Count the number of closure steps - should be 3 (one for each reset/restart)
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(berry_code, "push_closure_step", pos)
|
||||
if pos < 0 break end
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 3, f"Should have 3 closure steps for reset/restart, found {closure_count}")
|
||||
|
||||
print("✓ Different providers test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart with animations
|
||||
def test_reset_restart_animations()
|
||||
print("Testing reset/restart with animations...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s)\n" +
|
||||
"animation pulse_anim = pulsating_animation(color=red, period=3s)\n" +
|
||||
"animation solid_anim = solid(color=blue)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play pulse_anim for 1s\n" +
|
||||
" reset pulse_anim\n" +
|
||||
" play solid_anim for 1s\n" +
|
||||
" restart solid_anim\n" +
|
||||
" play pulse_anim for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for animation reset/restart")
|
||||
assert(string.find(berry_code, "pulse_anim_.start(engine.time_ms)") >= 0, "Should reset pulse animation")
|
||||
assert(string.find(berry_code, "solid_anim_.start(engine.time_ms)") >= 0, "Should restart solid animation")
|
||||
|
||||
# Count the number of closure steps - should be 2 (one for each reset/restart)
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(berry_code, "push_closure_step", pos)
|
||||
if pos < 0 break end
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 2, f"Should have 2 closure steps for animation reset/restart, found {closure_count}")
|
||||
|
||||
print("✓ Animation reset/restart test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test reset/restart in repeat blocks
|
||||
def test_reset_restart_in_repeat()
|
||||
print("Testing reset/restart in repeat blocks...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=1s)\n" +
|
||||
"animation test_anim = solid(color=yellow)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" repeat 3 times {\n" +
|
||||
" play test_anim for 500ms\n" +
|
||||
" reset osc_val\n" +
|
||||
" wait 200ms\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code for reset in repeat")
|
||||
assert(string.find(berry_code, "push_repeat_subsequence") >= 0, "Should generate repeat block")
|
||||
assert(string.find(berry_code, "osc_val_.start(engine.time_ms)") >= 0, "Should reset in repeat block")
|
||||
|
||||
print("✓ Reset/restart in repeat test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - undefined value provider
|
||||
def test_error_undefined_provider()
|
||||
print("Testing error handling for undefined value provider...")
|
||||
|
||||
var dsl_source = "animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset undefined_provider\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = nil
|
||||
try
|
||||
berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should fail with undefined provider error")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(string.find(msg, "Undefined reference 'undefined_provider'") >= 0, "Should report undefined reference error")
|
||||
end
|
||||
|
||||
print("✓ Undefined provider error test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - non-value provider
|
||||
def test_error_non_value_provider()
|
||||
print("Testing error handling for non-value provider...")
|
||||
|
||||
var dsl_source = "color my_color = 0xFF0000\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset my_color\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = nil
|
||||
try
|
||||
berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should fail with non-value provider error")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(string.find(msg, "is not a value provider") >= 0, "Should report non-value provider error")
|
||||
end
|
||||
|
||||
print("✓ Non-value provider error test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - animation instead of value provider
|
||||
def test_error_animation_not_provider()
|
||||
print("Testing error handling for animation instead of value provider...")
|
||||
|
||||
var dsl_source = "animation my_anim = solid(color=blue)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play my_anim for 1s\n" +
|
||||
" restart my_anim\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = nil
|
||||
try
|
||||
berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should fail with animation not value provider error")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(string.find(msg, "is not a value provider") >= 0, "Should report animation not value provider error")
|
||||
end
|
||||
|
||||
print("✓ Animation not provider error test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test error handling - variable instead of value provider
|
||||
def test_error_variable_not_provider()
|
||||
print("Testing error handling for variable instead of value provider...")
|
||||
|
||||
var dsl_source = "set my_var = 100\n" +
|
||||
"animation test_anim = solid(color=green)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset my_var\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = nil
|
||||
try
|
||||
berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(false, "Should fail with variable not value provider error")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(string.find(msg, "is not a value provider") >= 0, "Should report variable not value provider error")
|
||||
end
|
||||
|
||||
print("✓ Variable not provider error test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test complex scenario with multiple resets/restarts
|
||||
def test_complex_scenario()
|
||||
print("Testing complex scenario with multiple resets/restarts...")
|
||||
|
||||
var dsl_source = "# Complex cylon eye with reset functionality\n" +
|
||||
"set strip_len = strip_length()\n" +
|
||||
"palette eye_palette = [ red, yellow, green, violet ]\n" +
|
||||
"color eye_color = color_cycle(palette=eye_palette, cycle_period=0)\n" +
|
||||
"set cosine_val = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = 5s)\n" +
|
||||
"set triangle_val = triangle(min_value = 0, max_value = strip_len - 2, duration = 5s)\n" +
|
||||
"\n" +
|
||||
"animation red_eye = beacon_animation(\n" +
|
||||
" color = eye_color\n" +
|
||||
" pos = cosine_val\n" +
|
||||
" beacon_size = 3\n" +
|
||||
" slew_size = 2\n" +
|
||||
" priority = 10\n" +
|
||||
")\n" +
|
||||
"\n" +
|
||||
"sequence cylon_eye {\n" +
|
||||
" play red_eye for 3s\n" +
|
||||
" red_eye.pos = triangle_val\n" +
|
||||
" reset triangle_val\n" +
|
||||
" play red_eye for 3s\n" +
|
||||
" red_eye.pos = cosine_val\n" +
|
||||
" restart cosine_val\n" +
|
||||
" eye_color.next = 1\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run cylon_eye"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should compile complex scenario")
|
||||
assert(string.find(berry_code, "triangle_val_.start(engine.time_ms)") >= 0, "Should reset triangle_val")
|
||||
assert(string.find(berry_code, "cosine_val_.start(engine.time_ms)") >= 0, "Should restart cosine_val")
|
||||
|
||||
# Should have multiple closure steps: 2 assignments + 2 resets + 1 color advance = 5 total
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(berry_code, "push_closure_step", pos)
|
||||
if pos < 0 break end
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 5, f"Should have 5 closure steps in complex scenario, found {closure_count}")
|
||||
|
||||
print("✓ Complex scenario test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test that reset/restart works with comments
|
||||
def test_reset_restart_with_comments()
|
||||
print("Testing reset/restart with comments...")
|
||||
|
||||
var dsl_source = "set osc_val = triangle(min_value=0, max_value=10, duration=2s) # Triangle oscillator\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" reset osc_val # Reset the oscillator\n" +
|
||||
" restart osc_val # Restart it again\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
|
||||
assert(berry_code != nil, "Should generate Berry code with comments")
|
||||
assert(string.find(berry_code, "# Reset the oscillator") >= 0, "Should preserve reset comment")
|
||||
assert(string.find(berry_code, "# Restart it again") >= 0, "Should preserve restart comment")
|
||||
|
||||
# Should have 2 closure steps for reset and restart
|
||||
var closure_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(berry_code, "push_closure_step", pos)
|
||||
if pos < 0 break end
|
||||
closure_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(closure_count == 2, f"Should have 2 closure steps, found {closure_count}")
|
||||
|
||||
print("✓ Reset/restart with comments test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_reset_restart_tests()
|
||||
print("Starting DSL Reset/Restart Tests...")
|
||||
|
||||
test_reset_basic()
|
||||
test_restart_basic()
|
||||
test_reset_restart_different_providers()
|
||||
test_reset_restart_in_repeat()
|
||||
test_error_undefined_provider()
|
||||
test_error_non_value_provider()
|
||||
test_error_animation_not_provider()
|
||||
test_error_variable_not_provider()
|
||||
test_complex_scenario()
|
||||
test_reset_restart_with_comments()
|
||||
|
||||
print("\n🎉 All DSL Reset/Restart tests passed!")
|
||||
return true
|
||||
end
|
||||
|
||||
# Execute tests
|
||||
run_all_reset_restart_tests()
|
||||
|
||||
return {
|
||||
"run_all_reset_restart_tests": run_all_reset_restart_tests,
|
||||
"test_reset_basic": test_reset_basic,
|
||||
"test_restart_basic": test_restart_basic,
|
||||
"test_reset_restart_different_providers": test_reset_restart_different_providers,
|
||||
"test_reset_restart_in_repeat": test_reset_restart_in_repeat,
|
||||
"test_error_undefined_provider": test_error_undefined_provider,
|
||||
"test_error_non_value_provider": test_error_non_value_provider,
|
||||
"test_error_animation_not_provider": test_error_animation_not_provider,
|
||||
"test_error_variable_not_provider": test_error_variable_not_provider,
|
||||
"test_complex_scenario": test_complex_scenario,
|
||||
"test_reset_restart_with_comments": test_reset_restart_with_comments
|
||||
}
|
||||
@ -29,7 +29,7 @@ def test_basic_transpilation()
|
||||
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should generate strip configuration")
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should generate color definition")
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should generate sequence manager")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should add sequence manager")
|
||||
assert(string.find(berry_code, "engine.add(demo_)") >= 0, "Should add sequence manager")
|
||||
|
||||
# print("Generated Berry code:")
|
||||
# print("==================================================")
|
||||
@ -185,7 +185,7 @@ def test_sequence_assignments()
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile sequence with assignments")
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should define sequence manager")
|
||||
assert(string.find(berry_code, ".push_assign_step") >= 0, "Should generate assign step")
|
||||
assert(string.find(berry_code, ".push_closure_step") >= 0, "Should generate closure step")
|
||||
assert(string.find(berry_code, "test_.opacity = brightness_") >= 0, "Should generate assignment")
|
||||
|
||||
# Test multiple assignments in sequence
|
||||
@ -212,7 +212,7 @@ def test_sequence_assignments()
|
||||
var assign_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(multi_berry_code, "push_assign_step", pos)
|
||||
pos = string.find(multi_berry_code, "push_closure_step", pos)
|
||||
if pos < 0 break end
|
||||
assign_count += 1
|
||||
pos += 1
|
||||
@ -235,9 +235,10 @@ def test_sequence_assignments()
|
||||
"run demo"
|
||||
|
||||
var repeat_berry_code = animation_dsl.compile(repeat_assign_dsl)
|
||||
print(repeat_berry_code)
|
||||
assert(repeat_berry_code != nil, "Should compile repeat with assignments")
|
||||
assert(string.find(repeat_berry_code, "push_repeat_subsequence") >= 0, "Should generate repeat loop")
|
||||
assert(string.find(repeat_berry_code, "push_assign_step") >= 0, "Should generate assign step in repeat")
|
||||
assert(string.find(repeat_berry_code, "push_closure_step") >= 0, "Should generate closure step in repeat")
|
||||
|
||||
# Test complex cylon rainbow example
|
||||
var cylon_dsl = "set strip_len = strip_length()\n" +
|
||||
@ -375,9 +376,9 @@ def test_multiple_run_statements()
|
||||
assert(start_count == 1, f"Should have exactly 1 engine.start() call, found {start_count}")
|
||||
|
||||
# Check that all animations are added to the engine
|
||||
assert(string.find(berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add red_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(blue_anim_)") >= 0, "Should add blue_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add_animation(green_anim_)") >= 0, "Should add green_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add(red_anim_)") >= 0, "Should add red_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add(blue_anim_)") >= 0, "Should add blue_anim to engine")
|
||||
assert(string.find(berry_code, "engine.add(green_anim_)") >= 0, "Should add green_anim to engine")
|
||||
|
||||
# Verify the engine.start() comes after all animations are added
|
||||
var start_line_index = -1
|
||||
@ -388,7 +389,7 @@ def test_multiple_run_statements()
|
||||
if string.find(line, "engine.start()") >= 0
|
||||
start_line_index = i
|
||||
end
|
||||
if string.find(line, "engine.add_animation") >= 0 || string.find(line, "engine.add_sequence_manager") >= 0
|
||||
if string.find(line, "engine.add(") >= 0
|
||||
last_add_line_index = i
|
||||
end
|
||||
end
|
||||
@ -425,8 +426,8 @@ def test_multiple_run_statements()
|
||||
assert(mixed_start_count == 1, f"Mixed scenario should have exactly 1 engine.start() call, found {mixed_start_count}")
|
||||
|
||||
# Check that both animation and sequence are handled
|
||||
assert(string.find(mixed_berry_code, "engine.add_animation(red_anim_)") >= 0, "Should add animation to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add_sequence_manager(blue_seq_)") >= 0, "Should add sequence to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add(red_anim_)") >= 0, "Should add animation to engine")
|
||||
assert(string.find(mixed_berry_code, "engine.add(blue_seq_)") >= 0, "Should add sequence to engine")
|
||||
|
||||
print("✓ Multiple run statements test passed")
|
||||
return true
|
||||
@ -685,7 +686,7 @@ def test_complex_dsl()
|
||||
assert(string.find(berry_code, "var engine = animation.init_strip()") >= 0, "Should have default strip initialization")
|
||||
assert(string.find(berry_code, "var custom_red_ = 0xFFFF0000") >= 0, "Should have color definitions")
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should have sequence definition")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(demo_)") >= 0, "Should have execution")
|
||||
assert(string.find(berry_code, "engine.add(demo_)") >= 0, "Should have execution")
|
||||
|
||||
print("Generated code structure looks correct")
|
||||
else
|
||||
@ -1038,6 +1039,81 @@ def test_color_type_checking()
|
||||
return true
|
||||
end
|
||||
|
||||
# Test invalid sequence commands
|
||||
def test_invalid_sequence_commands()
|
||||
print("Testing invalid sequence commands...")
|
||||
|
||||
# Test 1: Invalid command in sequence
|
||||
var invalid_command_dsl =
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"sequence bad {\n" +
|
||||
" do_bad_things anim\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
"}"
|
||||
|
||||
try
|
||||
var result1 = animation_dsl.compile(invalid_command_dsl)
|
||||
assert(false, "Should have thrown an exception for invalid command")
|
||||
except "dsl_compilation_error"
|
||||
# Expected - invalid command should cause compilation error
|
||||
end
|
||||
|
||||
# Test 2: Another invalid command
|
||||
var invalid_command_dsl2 =
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"sequence bad {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" invalid_command\n" +
|
||||
" wait 500ms\n" +
|
||||
"}"
|
||||
|
||||
try
|
||||
var result2 = animation_dsl.compile(invalid_command_dsl2)
|
||||
assert(false, "Should have thrown an exception for invalid command")
|
||||
except "dsl_compilation_error"
|
||||
# Expected - invalid command should cause compilation error
|
||||
end
|
||||
|
||||
# Test 3: Invalid command in repeat block
|
||||
var invalid_repeat_dsl =
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"sequence bad {\n" +
|
||||
" repeat 3 times {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" bad_command_in_repeat\n" +
|
||||
" wait 500ms\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
|
||||
try
|
||||
var result3 = animation_dsl.compile(invalid_repeat_dsl)
|
||||
assert(false, "Should have thrown an exception for invalid command in repeat")
|
||||
except "dsl_compilation_error"
|
||||
# Expected - invalid command should cause compilation error
|
||||
end
|
||||
|
||||
# Test 4: Valid sequence should still work
|
||||
var valid_sequence_dsl =
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"sequence good {\n" +
|
||||
" play test_anim for 1s\n" +
|
||||
" wait 500ms\n" +
|
||||
" log(\"test message\")\n" +
|
||||
" test_anim.opacity = 128\n" +
|
||||
"}"
|
||||
|
||||
var result4 = animation_dsl.compile(valid_sequence_dsl)
|
||||
assert(result4 != nil, "Should compile valid sequence successfully")
|
||||
assert(string.find(result4, "SequenceManager") >= 0, "Should generate sequence manager")
|
||||
assert(string.find(result4, "push_play_step") >= 0, "Should generate play step")
|
||||
assert(string.find(result4, "push_wait_step") >= 0, "Should generate wait step")
|
||||
assert(string.find(result4, "log(f\"test message\", 3)") >= 0, "Should generate log statement")
|
||||
assert(string.find(result4, "push_closure_step") >= 0, "Should generate closure steps")
|
||||
|
||||
print("✓ Invalid sequence commands test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_dsl_transpiler_tests()
|
||||
print("=== DSL Transpiler Test Suite ===")
|
||||
@ -1064,7 +1140,8 @@ def run_dsl_transpiler_tests()
|
||||
test_comment_preservation,
|
||||
test_easing_keywords,
|
||||
test_animation_type_checking,
|
||||
test_color_type_checking
|
||||
test_color_type_checking,
|
||||
test_invalid_sequence_commands
|
||||
]
|
||||
|
||||
var passed = 0
|
||||
|
||||
@ -173,7 +173,7 @@ fb1.fill_pixels(0xFF0000FF) # Fill fb1 with red (fully opaque)
|
||||
fb2.fill_pixels(0x8000FF00) # Fill fb2 with green at 50% alpha
|
||||
|
||||
# Blend fb2 into fb1 using per-pixel alpha, but only for the first half
|
||||
fb1.blend_pixels(fb2, animation.frame_buffer.BLEND_MODE_NORMAL, 0, 4)
|
||||
fb1.blend_pixels(fb2, 0, 4)
|
||||
|
||||
var first_half_blended = true
|
||||
var second_half_original = true
|
||||
|
||||
@ -59,28 +59,6 @@ def test_palette_with_named_colors()
|
||||
print("✓ Palette with named colors test passed")
|
||||
end
|
||||
|
||||
# Test palette with custom colors
|
||||
def test_palette_with_custom_colors()
|
||||
print("Testing palette with custom colors...")
|
||||
|
||||
var dsl_source =
|
||||
"# Define custom colors first\n" +
|
||||
"color aurora_green = 0x00AA44\n" +
|
||||
"color aurora_purple = 0x8800AA\n" +
|
||||
"\n" +
|
||||
"palette aurora_palette = [\n" +
|
||||
" (0, 0x000022), # Dark night sky\n" +
|
||||
" (64, aurora_green), # Custom green\n" +
|
||||
" (192, aurora_purple), # Custom purple\n" +
|
||||
" (255, 0xCCAAFF) # Pale purple\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "DSL compilation with custom colors should succeed")
|
||||
|
||||
print("✓ Palette with custom colors test passed")
|
||||
end
|
||||
|
||||
# Test error handling for invalid palette syntax
|
||||
def test_palette_error_handling()
|
||||
print("Testing palette error handling...")
|
||||
@ -627,6 +605,76 @@ def test_alternative_syntax_integration()
|
||||
print("✓ Alternative syntax integration test passed")
|
||||
end
|
||||
|
||||
# Test that non-predefined colors raise exceptions
|
||||
# Palettes only accept hex colors (0xRRGGBB) or predefined color names,
|
||||
# but not custom colors defined previously. For dynamic palettes, use user functions.
|
||||
def test_non_predefined_color_exceptions()
|
||||
print("Testing non-predefined color exceptions...")
|
||||
|
||||
# Test 1: Custom color identifier in tuple syntax should fail
|
||||
try
|
||||
var custom_color_tuple = "palette test1 = [(0, custom_red)]"
|
||||
var result1 = animation_dsl.compile(custom_color_tuple)
|
||||
assert(result1 == nil, "Should fail with custom color identifier in tuple syntax")
|
||||
print("✗ FAIL: Custom color identifier in tuple syntax was accepted")
|
||||
return false
|
||||
except .. as e, msg
|
||||
# Expected to fail - custom color identifier not allowed
|
||||
print("✓ Custom color identifier in tuple syntax correctly rejected")
|
||||
end
|
||||
|
||||
# Test 2: Custom color identifier in alternative syntax should fail
|
||||
try
|
||||
var custom_color_alt = "palette test2 = [red, custom_blue, green]"
|
||||
var result2 = animation_dsl.compile(custom_color_alt)
|
||||
assert(result2 == nil, "Should fail with custom color identifier in alternative syntax")
|
||||
print("✗ FAIL: Custom color identifier in alternative syntax was accepted")
|
||||
return false
|
||||
except .. as e, msg
|
||||
# Expected to fail - custom color identifier not allowed
|
||||
print("✓ Custom color identifier in alternative syntax correctly rejected")
|
||||
end
|
||||
|
||||
# Test 3: The specific case from the user report - 'grrreen' should fail
|
||||
try
|
||||
var grrreen_case = "palette rainbow_with_white = [red, grrreen]"
|
||||
var result3 = animation_dsl.compile(grrreen_case)
|
||||
assert(result3 == nil, "Should fail with 'grrreen' identifier")
|
||||
print("✗ FAIL: 'grrreen' identifier was accepted")
|
||||
return false
|
||||
except .. as e, msg
|
||||
# Expected to fail - 'grrreen' is not a predefined color
|
||||
print("✓ 'grrreen' identifier correctly rejected")
|
||||
end
|
||||
|
||||
# Test 4: Misspelled predefined color should fail
|
||||
try
|
||||
var misspelled = "palette test4 = [red, bleu, green]" # 'bleu' instead of 'blue'
|
||||
var result4 = animation_dsl.compile(misspelled)
|
||||
assert(result4 == nil, "Should fail with misspelled color 'bleu'")
|
||||
print("✗ FAIL: Misspelled color 'bleu' was accepted")
|
||||
return false
|
||||
except .. as e, msg
|
||||
# Expected to fail - 'bleu' is not a predefined color
|
||||
print("✓ Misspelled color 'bleu' correctly rejected")
|
||||
end
|
||||
|
||||
# Test 5: Random identifier should fail
|
||||
try
|
||||
var random_id = "palette test5 = [red, some_random_name, blue]"
|
||||
var result5 = animation_dsl.compile(random_id)
|
||||
assert(result5 == nil, "Should fail with random identifier")
|
||||
print("✗ FAIL: Random identifier was accepted")
|
||||
return false
|
||||
except .. as e, msg
|
||||
# Expected to fail - random identifier is not a predefined color
|
||||
print("✓ Random identifier correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ Non-predefined color exceptions test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Run all palette tests
|
||||
def run_palette_tests()
|
||||
print("=== Palette DSL Tests ===")
|
||||
@ -635,9 +683,9 @@ def run_palette_tests()
|
||||
test_palette_keyword_recognition()
|
||||
test_palette_definition()
|
||||
test_palette_with_named_colors()
|
||||
test_palette_with_custom_colors()
|
||||
test_palette_error_handling()
|
||||
test_nonexistent_color_names()
|
||||
test_non_predefined_color_exceptions() # New test for strict color validation
|
||||
test_palette_integration()
|
||||
test_vrgb_format_validation()
|
||||
test_complete_workflow()
|
||||
|
||||
@ -216,12 +216,6 @@ def test_parameter_metadata()
|
||||
assert(enum_meta.contains("enum"), "Should have enum constraint")
|
||||
assert(enum_meta["default"] == 1, "Should have default value")
|
||||
|
||||
# Test getting all metadata
|
||||
var all_meta = obj.get_params_metadata()
|
||||
assert(all_meta.contains("range_param"), "Should contain range_param metadata")
|
||||
assert(all_meta.contains("enum_param"), "Should contain enum_param metadata")
|
||||
assert(all_meta.contains("simple_param"), "Should contain simple_param metadata")
|
||||
|
||||
print("✓ Parameter metadata test passed")
|
||||
end
|
||||
|
||||
|
||||
@ -62,12 +62,12 @@ def test_sequence_manager_step_creation()
|
||||
assert(wait_step["type"] == "wait", "Wait step should have correct type")
|
||||
assert(wait_step["duration"] == 2000, "Wait step should have correct duration")
|
||||
|
||||
# Test push_assign_step
|
||||
# Test push_closure_step
|
||||
var test_closure = def (engine) test_anim.opacity = 128 end
|
||||
seq_manager.push_assign_step(test_closure)
|
||||
assert(seq_manager.steps.size() == 3, "Should have three steps after push_assign_step")
|
||||
seq_manager.push_closure_step(test_closure)
|
||||
assert(seq_manager.steps.size() == 3, "Should have three steps after push_closure_step")
|
||||
var assign_step = seq_manager.steps[2]
|
||||
assert(assign_step["type"] == "assign", "Assign step should have correct type")
|
||||
assert(assign_step["type"] == "closure", "Assign step should have correct type")
|
||||
assert(assign_step["closure"] == test_closure, "Assign step should have correct closure")
|
||||
|
||||
print("✓ Step creation tests passed")
|
||||
@ -318,7 +318,7 @@ def test_sequence_manager_assignment_steps()
|
||||
|
||||
# Create sequence with assignment step using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 500) # Play for 0.5s
|
||||
.push_assign_step(assignment_closure) # Assign new opacity
|
||||
.push_closure_step(assignment_closure) # Assign new opacity
|
||||
.push_play_step(test_anim, 500) # Play for another 0.5s
|
||||
|
||||
# Start sequence
|
||||
@ -475,6 +475,247 @@ def test_sequence_manager_integration()
|
||||
print("✓ Integration tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_parametric_repeat_counts()
|
||||
print("=== SequenceManager Parametric Repeat Count Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test 1: Static repeat count (baseline)
|
||||
var static_repeat_count = 3
|
||||
var seq_manager1 = animation.SequenceManager(engine, static_repeat_count)
|
||||
|
||||
# Test get_resolved_repeat_count with static number
|
||||
var resolved_count = seq_manager1.get_resolved_repeat_count()
|
||||
assert(resolved_count == 3, f"Static repeat count should resolve to 3, got {resolved_count}")
|
||||
|
||||
# Test 2: Function-based repeat count (simulating col1.palette_size)
|
||||
var palette_size_function = def (engine) return 5 end # Simulates a palette with 5 colors
|
||||
var seq_manager2 = animation.SequenceManager(engine, palette_size_function)
|
||||
|
||||
# Test get_resolved_repeat_count with function
|
||||
resolved_count = seq_manager2.get_resolved_repeat_count()
|
||||
assert(resolved_count == 5, f"Function repeat count should resolve to 5, got {resolved_count}")
|
||||
|
||||
# Test 3: Dynamic repeat count that changes over time
|
||||
var dynamic_counter = 0
|
||||
var dynamic_function = def (engine)
|
||||
dynamic_counter += 1
|
||||
return dynamic_counter <= 1 ? 2 : 4 # First call returns 2, subsequent calls return 4
|
||||
end
|
||||
|
||||
var seq_manager3 = animation.SequenceManager(engine, dynamic_function)
|
||||
var first_resolved = seq_manager3.get_resolved_repeat_count()
|
||||
var second_resolved = seq_manager3.get_resolved_repeat_count()
|
||||
assert(first_resolved == 2, f"First dynamic call should return 2, got {first_resolved}")
|
||||
assert(second_resolved == 4, f"Second dynamic call should return 4, got {second_resolved}")
|
||||
|
||||
print("✓ Parametric repeat count tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_repeat_execution_with_functions()
|
||||
print("=== SequenceManager Repeat Execution with Functions Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create test animation
|
||||
var color_provider = animation.static_color(engine)
|
||||
color_provider.color = 0xFF00FF00
|
||||
var test_anim = animation.solid(engine)
|
||||
test_anim.color = color_provider
|
||||
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
|
||||
|
||||
# Create sequence manager with function-based repeat count
|
||||
var seq_manager = animation.SequenceManager(engine, repeat_count_func)
|
||||
seq_manager.push_play_step(test_anim, 50) # Short duration for testing
|
||||
|
||||
# Verify repeat count is resolved correctly
|
||||
var resolved_count = seq_manager.get_resolved_repeat_count()
|
||||
assert(resolved_count == 3, f"Repeat count should resolve to 3, got {resolved_count}")
|
||||
|
||||
# Test that the sequence manager accepts function-based repeat counts
|
||||
assert(type(seq_manager.repeat_count) == "function", "Repeat count should be stored as function")
|
||||
|
||||
# Test that multiple calls to get_resolved_repeat_count work
|
||||
var second_resolved = seq_manager.get_resolved_repeat_count()
|
||||
assert(second_resolved == 3, f"Second resolution should also return 3, got {second_resolved}")
|
||||
|
||||
# Test sequence execution with function-based repeat count
|
||||
tasmota.set_millis(90000)
|
||||
seq_manager.start(90000)
|
||||
assert(seq_manager.is_running == true, "Sequence should start running")
|
||||
assert(seq_manager.current_iteration == 0, "Should start at iteration 0")
|
||||
|
||||
print("✓ Repeat execution with functions tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_palette_size_simulation()
|
||||
print("=== SequenceManager Palette Size Simulation Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Simulate a color cycle with palette_size property (like col1.palette_size)
|
||||
var mock_color_cycle = {
|
||||
"palette_size": 5, # Use smaller palette for simpler testing
|
||||
"current_index": 0
|
||||
}
|
||||
|
||||
# Create function that accesses palette_size (simulating col1.palette_size)
|
||||
var palette_size_func = def (engine) return mock_color_cycle["palette_size"] end
|
||||
|
||||
# Create closure that advances color cycle (simulating col1.next = 1)
|
||||
var advance_color_func = def (engine)
|
||||
mock_color_cycle["current_index"] = (mock_color_cycle["current_index"] + 1) % mock_color_cycle["palette_size"]
|
||||
end
|
||||
|
||||
# Create sequence similar to demo_shutter_rainbow.anim:
|
||||
# sequence shutter_seq repeat col1.palette_size times {
|
||||
# play shutter_animation for duration
|
||||
# col1.next = 1
|
||||
# }
|
||||
var seq_manager = animation.SequenceManager(engine, palette_size_func)
|
||||
seq_manager.push_closure_step(advance_color_func) # Just test the closure execution
|
||||
|
||||
# Test that repeat count is resolved correctly
|
||||
var resolved_count = seq_manager.get_resolved_repeat_count()
|
||||
assert(resolved_count == 5, f"Should resolve to palette size 5, got {resolved_count}")
|
||||
|
||||
# Test that the closure function works
|
||||
var initial_index = mock_color_cycle["current_index"]
|
||||
advance_color_func(engine)
|
||||
assert(mock_color_cycle["current_index"] == (initial_index + 1) % 5, "Color should advance when closure is called")
|
||||
|
||||
# Test that the function can be called multiple times
|
||||
var second_resolved = seq_manager.get_resolved_repeat_count()
|
||||
assert(second_resolved == 5, f"Second resolution should also return 5, got {second_resolved}")
|
||||
|
||||
print("✓ Palette size simulation tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_dynamic_repeat_changes()
|
||||
print("=== SequenceManager Dynamic Repeat Changes Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Create test animation
|
||||
var color_provider = animation.static_color(engine)
|
||||
color_provider.color = 0xFF0080FF
|
||||
var test_anim = animation.solid(engine)
|
||||
test_anim.color = color_provider
|
||||
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}
|
||||
var dynamic_repeat_func = def (engine)
|
||||
return external_state["multiplier"] * 2 # Returns 4 initially, can change
|
||||
end
|
||||
|
||||
# Create sequence with dynamic repeat count
|
||||
var seq_manager = animation.SequenceManager(engine, dynamic_repeat_func)
|
||||
seq_manager.push_play_step(test_anim, 250)
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(120000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start()
|
||||
engine.on_tick(120000)
|
||||
seq_manager.start(120000)
|
||||
|
||||
# Test initial repeat count resolution
|
||||
var initial_count = seq_manager.get_resolved_repeat_count()
|
||||
assert(initial_count == 4, f"Initial repeat count should be 4, got {initial_count}")
|
||||
|
||||
# Change external state mid-execution (simulating dynamic conditions)
|
||||
external_state["multiplier"] = 3
|
||||
|
||||
# Test that new calls get updated count
|
||||
var updated_count = seq_manager.get_resolved_repeat_count()
|
||||
assert(updated_count == 6, f"Updated repeat count should be 6, got {updated_count}")
|
||||
|
||||
# Test with function that depends on engine state
|
||||
var engine_dependent_func = def (engine)
|
||||
# Simulating a function that depends on strip length or other engine properties
|
||||
return engine.strip != nil ? 3 : 1
|
||||
end
|
||||
|
||||
var seq_manager2 = animation.SequenceManager(engine, engine_dependent_func)
|
||||
var engine_count = seq_manager2.get_resolved_repeat_count()
|
||||
assert(engine_count == 3, f"Engine-dependent count should be 3, got {engine_count}")
|
||||
|
||||
print("✓ Dynamic repeat changes tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_complex_parametric_scenario()
|
||||
print("=== SequenceManager Complex Parametric Scenario Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Simulate complex scenario with multiple parametric elements
|
||||
# Similar to a more complex version of demo_shutter_rainbow.anim
|
||||
|
||||
# Mock palette and color cycle objects
|
||||
var rainbow_palette = {
|
||||
"colors": [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00], # Smaller palette for testing
|
||||
"size": 3
|
||||
}
|
||||
|
||||
var color_cycle1 = {
|
||||
"palette": rainbow_palette,
|
||||
"current_index": 0,
|
||||
"palette_size": rainbow_palette["size"]
|
||||
}
|
||||
|
||||
# Functions for parametric behavior
|
||||
var palette_size_func = def (engine) return color_cycle1["palette_size"] end
|
||||
var advance_colors_func = def (engine)
|
||||
color_cycle1["current_index"] = (color_cycle1["current_index"] + 1) % color_cycle1["palette_size"]
|
||||
end
|
||||
|
||||
# Create sequence with parametric repeat
|
||||
var seq_manager = animation.SequenceManager(engine, palette_size_func)
|
||||
seq_manager.push_closure_step(advance_colors_func)
|
||||
|
||||
# Verify sequence setup
|
||||
var resolved_count = seq_manager.get_resolved_repeat_count()
|
||||
assert(resolved_count == 3, f"Complex sequence should repeat 3 times, got {resolved_count}")
|
||||
|
||||
# Test that the functions work correctly
|
||||
var initial_color_index = color_cycle1["current_index"]
|
||||
|
||||
# Test closure execution
|
||||
advance_colors_func(engine)
|
||||
assert(color_cycle1["current_index"] == (initial_color_index + 1) % 3, "Color should advance")
|
||||
|
||||
# Test multiple function calls
|
||||
var second_resolved = seq_manager.get_resolved_repeat_count()
|
||||
assert(second_resolved == 3, f"Second resolution should still return 3, got {second_resolved}")
|
||||
|
||||
# Test that palette size function works with different values
|
||||
color_cycle1["palette_size"] = 5
|
||||
var updated_resolved = seq_manager.get_resolved_repeat_count()
|
||||
assert(updated_resolved == 5, f"Updated resolution should return 5, got {updated_resolved}")
|
||||
|
||||
print("✓ Complex parametric scenario tests passed")
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
def run_all_sequence_manager_tests()
|
||||
print("Starting SequenceManager Unit Tests...")
|
||||
@ -489,6 +730,11 @@ def run_all_sequence_manager_tests()
|
||||
test_sequence_manager_assignment_steps()
|
||||
test_sequence_manager_complex_sequence()
|
||||
test_sequence_manager_integration()
|
||||
test_sequence_manager_parametric_repeat_counts()
|
||||
test_sequence_manager_repeat_execution_with_functions()
|
||||
test_sequence_manager_palette_size_simulation()
|
||||
test_sequence_manager_dynamic_repeat_changes()
|
||||
test_sequence_manager_complex_parametric_scenario()
|
||||
|
||||
print("\n🎉 All SequenceManager tests passed!")
|
||||
return true
|
||||
@ -508,5 +754,10 @@ return {
|
||||
"test_sequence_manager_is_running": test_sequence_manager_is_running,
|
||||
"test_sequence_manager_assignment_steps": test_sequence_manager_assignment_steps,
|
||||
"test_sequence_manager_complex_sequence": test_sequence_manager_complex_sequence,
|
||||
"test_sequence_manager_integration": test_sequence_manager_integration
|
||||
"test_sequence_manager_integration": test_sequence_manager_integration,
|
||||
"test_sequence_manager_parametric_repeat_counts": test_sequence_manager_parametric_repeat_counts,
|
||||
"test_sequence_manager_repeat_execution_with_functions": test_sequence_manager_repeat_execution_with_functions,
|
||||
"test_sequence_manager_palette_size_simulation": test_sequence_manager_palette_size_simulation,
|
||||
"test_sequence_manager_dynamic_repeat_changes": test_sequence_manager_dynamic_repeat_changes,
|
||||
"test_sequence_manager_complex_parametric_scenario": test_sequence_manager_complex_parametric_scenario
|
||||
}
|
||||
@ -127,15 +127,9 @@ def test_parameterized_object_integration()
|
||||
assert(provider.engine == engine, "Provider should have correct engine reference")
|
||||
|
||||
# Test parameter system methods exist
|
||||
assert(type(provider.get_params_metadata) == "function", "Should have get_params_metadata method")
|
||||
assert(type(provider.set_param) == "function", "Should have set_param method")
|
||||
assert(type(provider.get_param) == "function", "Should have get_param method")
|
||||
|
||||
# Test parameter metadata
|
||||
var params = provider.get_params_metadata()
|
||||
assert(params.contains("value"), "Should have 'value' parameter defined")
|
||||
assert(params["value"]["default"] == nil, "Default value should be nil")
|
||||
|
||||
# Test parameter setting via parameter system
|
||||
assert(provider.set_param("value", 777) == true, "Should be able to set value via parameter system")
|
||||
assert(provider.get_param("value", nil) == 777, "Should retrieve value via parameter system")
|
||||
|
||||
@ -60,6 +60,7 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/breathe_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_bytes_test.be", # Tests ColorCycleColorProvider with bytes palette
|
||||
"lib/libesp32/berry_animation/src/tests/color_cycle_palette_size_test.be", # Tests ColorCycleColorProvider palette_size read-only parameter
|
||||
"lib/libesp32/berry_animation/src/tests/rich_palette_animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/rich_palette_animation_class_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/comet_animation_test.be",
|
||||
|
||||
@ -98,7 +98,6 @@ def test_parameterized_object_integration()
|
||||
assert(provider.engine == engine, "Provider should have correct engine reference")
|
||||
|
||||
# Test parameter system methods exist
|
||||
assert(type(provider.get_params_metadata) == "function", "Should have get_params_metadata method")
|
||||
assert(type(provider.set_param) == "function", "Should have set_param method")
|
||||
assert(type(provider.get_param) == "function", "Should have get_param method")
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user