Berry animation fix and improve sequence (#23849)
This commit is contained in:
parent
27a1f11c70
commit
9426df28f9
@ -77,10 +77,11 @@ sequence rgb_show {
|
||||
wait 500ms
|
||||
play blue_pulse for 3s
|
||||
|
||||
repeat 2 times:
|
||||
repeat 2 times {
|
||||
play red_pulse for 1s
|
||||
play green_pulse for 1s
|
||||
play blue_pulse for 1s
|
||||
}
|
||||
}
|
||||
|
||||
run rgb_show
|
||||
|
||||
@ -22,13 +22,8 @@ aurora_base_.palette = aurora_colors_ # palette
|
||||
aurora_base_.cycle_period = 10000 # cycle period
|
||||
aurora_base_.transition_type = animation.SINE # transition type (explicit for clarity)
|
||||
aurora_base_.brightness = 180 # brightness (dimmed for aurora effect)
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('aurora_base_'), 0)) # infinite duration (no 'for' clause)
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end)(engine)
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(aurora_base_, nil) # infinite duration (no 'for' clause)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
@ -41,19 +36,19 @@ engine.start()
|
||||
|
||||
# Define aurora color palette
|
||||
palette aurora_colors = [
|
||||
(0, 0x000022), # Dark night sky
|
||||
(64, 0x004400), # Dark green
|
||||
(128, 0x00AA44), # Aurora green
|
||||
(192, 0x44AA88), # Light green
|
||||
(0, 0x000022) # Dark night sky
|
||||
(64, 0x004400) # Dark green
|
||||
(128, 0x00AA44) # Aurora green
|
||||
(192, 0x44AA88) # Light green
|
||||
(255, 0x88FFAA) # Bright aurora
|
||||
]
|
||||
|
||||
# Secondary purple palette
|
||||
palette aurora_purple = [
|
||||
(0, 0x220022), # Dark purple
|
||||
(64, 0x440044), # Medium purple
|
||||
(128, 0x8800AA), # Bright purple
|
||||
(192, 0xAA44CC), # Light purple
|
||||
(0, 0x220022) # Dark purple
|
||||
(64, 0x440044) # Medium purple
|
||||
(128, 0x8800AA) # Bright purple
|
||||
(192, 0xAA44CC) # Light purple
|
||||
(255, 0xCCAAFF) # Pale purple
|
||||
]
|
||||
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: cylon_rainbow.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Cylon Rainbow
|
||||
# Alternat between COSINE and TRIANGLE then shift to next color
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var eye_duration_ = 5000 # duration for a cylon eye cycle
|
||||
var eye_palette_ = bytes("FFFF0000" "FFFFFF00" "FF008000" "FFEE82EE")
|
||||
var eye_color_ = animation.color_cycle(engine)
|
||||
eye_color_.palette = eye_palette_
|
||||
eye_color_.cycle_period = 0
|
||||
var cosine_val_ = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = eye_duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var triangle_val_ = (def (engine)
|
||||
var provider = animation.triangle(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = eye_duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
var red_eye_ = animation.beacon_animation(engine)
|
||||
red_eye_.color = eye_color_ # palette that will advance when we do `eye_color.next = 1`
|
||||
red_eye_.pos = cosine_val_ # oscillator for position
|
||||
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_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_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Cylon Rainbow
|
||||
# Alternat between COSINE and TRIANGLE then shift to next color
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
set eye_duration = 5s # duration for a cylon eye cycle
|
||||
|
||||
palette eye_palette = [ red, yellow, green, violet ]
|
||||
|
||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
||||
|
||||
set cosine_val = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = eye_duration)
|
||||
set triangle_val = triangle(min_value = 0, max_value = strip_len - 2, duration = eye_duration)
|
||||
|
||||
animation red_eye = beacon_animation(
|
||||
color = eye_color # palette that will advance when we do `eye_color.next = 1`
|
||||
pos = cosine_val # oscillator for position
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
)
|
||||
|
||||
sequence cylon_eye forever {
|
||||
play red_eye for eye_duration # use COSINE movement
|
||||
red_eye.pos = triangle_val # switch to TRIANGLE
|
||||
play red_eye for eye_duration
|
||||
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
||||
eye_color.next = 1 # advance to next color
|
||||
}
|
||||
|
||||
run cylon_eye
|
||||
-#
|
||||
@ -24,20 +24,15 @@ var ocean_anim_ = animation.rich_palette_animation(engine)
|
||||
ocean_anim_.palette = ocean_colors_
|
||||
ocean_anim_.cycle_period = 8000
|
||||
# Sequence to show both palettes
|
||||
var palette_demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('fire_anim_'), 10000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
steps.push(animation.create_play_step(animation.global('ocean_anim_'), 10000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
for repeat_i : 0..2-1
|
||||
steps.push(animation.create_play_step(animation.global('fire_anim_'), 3000))
|
||||
steps.push(animation.create_play_step(animation.global('ocean_anim_'), 3000))
|
||||
end
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end)(engine)
|
||||
var palette_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(fire_anim_, 10000)
|
||||
.push_wait_step(1000)
|
||||
.push_play_step(ocean_anim_, 10000)
|
||||
.push_wait_step(1000)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, 2)
|
||||
.push_play_step(fire_anim_, 3000)
|
||||
.push_play_step(ocean_anim_, 3000)
|
||||
)
|
||||
engine.add_sequence_manager(palette_demo_)
|
||||
engine.start()
|
||||
|
||||
@ -77,9 +72,10 @@ sequence palette_demo {
|
||||
wait 1s
|
||||
play ocean_anim for 10s
|
||||
wait 1s
|
||||
repeat 2 times:
|
||||
repeat 2 times {
|
||||
play fire_anim for 3s
|
||||
play ocean_anim for 3s
|
||||
}
|
||||
}
|
||||
|
||||
run palette_demo
|
||||
|
||||
@ -40,31 +40,26 @@ sunset_glow_.cycle_period = 6000
|
||||
sunset_glow_.transition_type = animation.SINE
|
||||
sunset_glow_.brightness = 220
|
||||
# Sequence to showcase all palettes
|
||||
var palette_showcase_ = (def (engine)
|
||||
var steps = []
|
||||
var palette_showcase_ = animation.SequenceManager(engine)
|
||||
# Fire effect
|
||||
steps.push(animation.create_play_step(animation.global('fire_effect_'), 8000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
.push_play_step(fire_effect_, 8000)
|
||||
.push_wait_step(1000)
|
||||
# Ocean waves
|
||||
steps.push(animation.create_play_step(animation.global('ocean_waves_'), 8000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
.push_play_step(ocean_waves_, 8000)
|
||||
.push_wait_step(1000)
|
||||
# Aurora borealis
|
||||
steps.push(animation.create_play_step(animation.global('aurora_lights_'), 8000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
.push_play_step(aurora_lights_, 8000)
|
||||
.push_wait_step(1000)
|
||||
# Sunset
|
||||
steps.push(animation.create_play_step(animation.global('sunset_glow_'), 8000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
.push_play_step(sunset_glow_, 8000)
|
||||
.push_wait_step(1000)
|
||||
# Quick cycle through all
|
||||
for repeat_i : 0..3-1
|
||||
steps.push(animation.create_play_step(animation.global('fire_effect_'), 2000))
|
||||
steps.push(animation.create_play_step(animation.global('ocean_waves_'), 2000))
|
||||
steps.push(animation.create_play_step(animation.global('aurora_lights_'), 2000))
|
||||
steps.push(animation.create_play_step(animation.global('sunset_glow_'), 2000))
|
||||
end
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end)(engine)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, 3)
|
||||
.push_play_step(fire_effect_, 2000)
|
||||
.push_play_step(ocean_waves_, 2000)
|
||||
.push_play_step(aurora_lights_, 2000)
|
||||
.push_play_step(sunset_glow_, 2000)
|
||||
)
|
||||
engine.add_sequence_manager(palette_showcase_)
|
||||
engine.start()
|
||||
|
||||
@ -143,11 +138,12 @@ sequence palette_showcase {
|
||||
wait 1s
|
||||
|
||||
# Quick cycle through all
|
||||
repeat 3 times:
|
||||
repeat 3 times {
|
||||
play fire_effect for 2s
|
||||
play ocean_waves for 2s
|
||||
play aurora_lights for 2s
|
||||
play sunset_glow for 2s
|
||||
}
|
||||
}
|
||||
|
||||
run palette_showcase
|
||||
|
||||
@ -41,25 +41,20 @@ left_pulse_.priority = 10
|
||||
center_pulse_.priority = 15 # Center has highest priority
|
||||
right_pulse_.priority = 5
|
||||
# Create a sequence that shows all three
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('left_pulse_'), 3000))
|
||||
steps.push(animation.create_wait_step(500))
|
||||
steps.push(animation.create_play_step(animation.global('center_pulse_'), 3000))
|
||||
steps.push(animation.create_wait_step(500))
|
||||
steps.push(animation.create_play_step(animation.global('right_pulse_'), 3000))
|
||||
steps.push(animation.create_wait_step(500))
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(left_pulse_, 3000)
|
||||
.push_wait_step(500)
|
||||
.push_play_step(center_pulse_, 3000)
|
||||
.push_wait_step(500)
|
||||
.push_play_step(right_pulse_, 3000)
|
||||
.push_wait_step(500)
|
||||
# Play all together for final effect
|
||||
for repeat_i : 0..3-1
|
||||
steps.push(animation.create_play_step(animation.global('left_pulse_'), 2000))
|
||||
steps.push(animation.create_play_step(animation.global('center_pulse_'), 2000))
|
||||
steps.push(animation.create_play_step(animation.global('right_pulse_'), 2000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
end
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end)(engine)
|
||||
.push_repeat_subsequence(animation.SequenceManager(engine, -1)
|
||||
.push_play_step(left_pulse_, 2000)
|
||||
.push_play_step(center_pulse_, 2000)
|
||||
.push_play_step(right_pulse_, 2000)
|
||||
.push_wait_step(1000)
|
||||
)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
@ -100,11 +95,12 @@ sequence demo {
|
||||
wait 500ms
|
||||
|
||||
# Play all together for final effect
|
||||
repeat 3 times:
|
||||
repeat forever {
|
||||
play left_pulse for 2s
|
||||
play center_pulse for 2s
|
||||
play right_pulse for 2s
|
||||
wait 1s
|
||||
}
|
||||
}
|
||||
|
||||
run demo
|
||||
|
||||
@ -9,12 +9,13 @@ import animation
|
||||
# Rainbow Cycle - Classic WLED effect
|
||||
# Smooth rainbow colors cycling across the strip
|
||||
#strip length 60
|
||||
# Create smooth rainbow cycle animation
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var rainbow_palette_ = bytes("FFFF0000" "FFFF8000" "FFFFFF00" "FF00FF00" "FF0000FF" "FF8000FF" "FFFF00FF") # rainbow colors
|
||||
# Create smooth rainbow cycle animation
|
||||
var rainbow_cycle_ = animation.color_cycle(engine)
|
||||
rainbow_cycle_.palette = [0xFFFF0000, 0xFFFF8000, 0xFFFFFF00, 0xFF00FF00, 0xFF0000FF, 0xFF8000FF, 0xFFFF00FF] # rainbow colors
|
||||
rainbow_cycle_.palette = rainbow_palette_
|
||||
rainbow_cycle_.cycle_period = 5000 # cycle period
|
||||
var rainbow_animation_ = animation.solid(engine)
|
||||
rainbow_animation_.color = rainbow_cycle_
|
||||
@ -29,9 +30,11 @@ engine.start()
|
||||
|
||||
#strip length 60
|
||||
|
||||
palette rainbow_palette = [0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF] # rainbow colors
|
||||
|
||||
# Create smooth rainbow cycle animation
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF] # rainbow colors
|
||||
palette=rainbow_palette
|
||||
cycle_period=5s # cycle period
|
||||
)
|
||||
animation rainbow_animation = solid(color=rainbow_cycle)
|
||||
|
||||
@ -0,0 +1,226 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: sequence_assignments_demo.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Sequence Assignments Demo
|
||||
# Demonstrates dynamic property changes within sequences
|
||||
# Set up strip length and value providers
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var triangle_val_ = (def (engine)
|
||||
var provider = animation.triangle(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = 5000
|
||||
return provider
|
||||
end)(engine)
|
||||
var cosine_val_ = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = 0
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = 5000
|
||||
return provider
|
||||
end)(engine)
|
||||
var brightness_high_ = 255
|
||||
var brightness_low_ = 64
|
||||
# Create color palette and cycling color
|
||||
var eye_palette_ = bytes("FFFF0000" "FFFFFF00" "FF008000" "FFEE82EE")
|
||||
var eye_color_ = animation.color_cycle(engine)
|
||||
eye_color_.palette = eye_palette_
|
||||
eye_color_.cycle_period = 0
|
||||
# Create animations
|
||||
var red_eye_ = animation.beacon_animation(engine)
|
||||
red_eye_.color = eye_color_
|
||||
red_eye_.pos = cosine_val_
|
||||
red_eye_.beacon_size = 3
|
||||
red_eye_.slew_size = 2
|
||||
red_eye_.priority = 10
|
||||
var pulse_demo_ = animation.pulsating_animation(engine)
|
||||
pulse_demo_.color = 0xFF0000FF
|
||||
pulse_demo_.period = 2000
|
||||
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_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_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_play_step(pulse_demo_, 2000)
|
||||
.push_assign_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_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_play_step(pulse_demo_, 1000)
|
||||
.push_assign_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_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
|
||||
)
|
||||
# Main demo sequence combining all examples
|
||||
var main_demo_ = animation.SequenceManager(engine)
|
||||
# Run cylon eye demo
|
||||
.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_play_step(red_eye_, 2000)
|
||||
.push_assign_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_play_step(red_eye_, 1000)
|
||||
.push_assign_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_play_step(pulse_demo_, 1000)
|
||||
.push_assign_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_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_play_step(pulse_demo_, 1000)
|
||||
# Run the main demo
|
||||
engine.add_sequence_manager(main_demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Sequence Assignments Demo
|
||||
# Demonstrates dynamic property changes within sequences
|
||||
|
||||
# Set up strip length and value providers
|
||||
set strip_len = strip_length()
|
||||
set triangle_val = triangle(min_value=0, max_value=strip_len - 2, duration=5s)
|
||||
set cosine_val = cosine_osc(min_value=0, max_value=strip_len - 2, duration=5s)
|
||||
set brightness_high = 255
|
||||
set brightness_low = 64
|
||||
|
||||
# Create color palette and cycling color
|
||||
palette eye_palette = [red, yellow, green, violet]
|
||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
||||
|
||||
# Create animations
|
||||
animation red_eye = beacon_animation(
|
||||
color=eye_color
|
||||
pos=cosine_val
|
||||
beacon_size=3
|
||||
slew_size=2
|
||||
priority=10
|
||||
)
|
||||
|
||||
animation pulse_demo = pulsating_animation(
|
||||
color=blue
|
||||
period=2s
|
||||
priority=5
|
||||
)
|
||||
|
||||
# Sequence 1: Cylon Eye with Position Changes
|
||||
sequence cylon_eye {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val # Change to triangle oscillator
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val # Change back to cosine
|
||||
eye_color.next = 1 # Advance to next color
|
||||
play red_eye for 2s
|
||||
}
|
||||
|
||||
# Sequence 2: Brightness Control Demo
|
||||
sequence brightness_demo {
|
||||
play pulse_demo for 2s
|
||||
pulse_demo.opacity = brightness_low # Dim the animation
|
||||
play pulse_demo for 2s
|
||||
pulse_demo.opacity = brightness_high # Brighten again
|
||||
play pulse_demo for 2s
|
||||
}
|
||||
|
||||
# Sequence 3: Multiple Property Changes
|
||||
sequence multi_change {
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = red # Change color
|
||||
pulse_demo.opacity = brightness_low # And brightness
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = green # Change color again
|
||||
pulse_demo.opacity = brightness_high # Full brightness
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = blue # Back to blue
|
||||
}
|
||||
|
||||
# Sequence 4: Assignments in Repeat Blocks
|
||||
sequence repeat_demo {
|
||||
repeat 3 times {
|
||||
play red_eye for 1s
|
||||
red_eye.pos = triangle_val # Change oscillator
|
||||
play red_eye for 1s
|
||||
red_eye.pos = cosine_val # Change back
|
||||
eye_color.next = 1 # Next color
|
||||
}
|
||||
}
|
||||
|
||||
# Main demo sequence combining all examples
|
||||
sequence main_demo {
|
||||
# Run cylon eye demo
|
||||
play red_eye for 1s
|
||||
wait 500ms
|
||||
|
||||
# Demonstrate position changes
|
||||
red_eye.pos = triangle_val
|
||||
play red_eye for 2s
|
||||
red_eye.pos = cosine_val
|
||||
play red_eye for 2s
|
||||
|
||||
# Color cycling
|
||||
eye_color.next = 1
|
||||
play red_eye for 1s
|
||||
eye_color.next = 1
|
||||
play red_eye for 1s
|
||||
|
||||
wait 1s
|
||||
|
||||
# Brightness demo with pulse
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.opacity = brightness_low
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.opacity = brightness_high
|
||||
play pulse_demo for 1s
|
||||
|
||||
# Multi-property changes
|
||||
pulse_demo.color = red
|
||||
pulse_demo.opacity = brightness_low
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = green
|
||||
pulse_demo.opacity = brightness_high
|
||||
play pulse_demo for 1s
|
||||
}
|
||||
|
||||
# Run the main demo
|
||||
run main_demo
|
||||
-#
|
||||
@ -19,13 +19,8 @@ var rainbow_cycle_ = animation.rich_palette_animation(engine)
|
||||
rainbow_cycle_.palette = rainbow_
|
||||
rainbow_cycle_.cycle_period = 3000
|
||||
# Simple sequence
|
||||
var demo_ = (def (engine)
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(animation.global('rainbow_cycle_'), 15000))
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
seq_manager.start_sequence(steps)
|
||||
return seq_manager
|
||||
end)(engine)
|
||||
var demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(rainbow_cycle_, 15000)
|
||||
engine.add_sequence_manager(demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: swipe_rainbow.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Swipe Rainbow
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var palette_olivary_ = bytes("FFFF0000" "FFFFA500" "FFFFFF00" "FF008000" "FF0000FF" "FF4B0082" "FFEE82EE" "FFFFFFFF")
|
||||
var olivary_ = animation.color_cycle(engine)
|
||||
olivary_.palette = palette_olivary_
|
||||
olivary_.cycle_period = 0
|
||||
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_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Swipe Rainbow
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
palette palette_olivary = [
|
||||
red,
|
||||
orange,
|
||||
yellow,
|
||||
green,
|
||||
blue,
|
||||
indigo,
|
||||
violet,
|
||||
white
|
||||
]
|
||||
|
||||
color olivary = color_cycle(palette=palette_olivary, cycle_period=0)
|
||||
|
||||
animation swipe_animation = solid(
|
||||
color = olivary
|
||||
)
|
||||
|
||||
sequence slide_colors {
|
||||
play swipe_animation for 1s
|
||||
olivary.next = 1
|
||||
}
|
||||
|
||||
run slide_colors
|
||||
|
||||
-#
|
||||
@ -0,0 +1,30 @@
|
||||
# Cylon Rainbow
|
||||
# Alternat between COSINE and TRIANGLE then shift to next color
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
set eye_duration = 5s # duration for a cylon eye cycle
|
||||
|
||||
palette eye_palette = [ red, yellow, green, violet ]
|
||||
|
||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
||||
|
||||
set cosine_val = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = eye_duration)
|
||||
set triangle_val = triangle(min_value = 0, max_value = strip_len - 2, duration = eye_duration)
|
||||
|
||||
animation red_eye = beacon_animation(
|
||||
color = eye_color # palette that will advance when we do `eye_color.next = 1`
|
||||
pos = cosine_val # oscillator for position
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
)
|
||||
|
||||
sequence cylon_eye forever {
|
||||
play red_eye for eye_duration # use COSINE movement
|
||||
red_eye.pos = triangle_val # switch to TRIANGLE
|
||||
play red_eye for eye_duration
|
||||
red_eye.pos = cosine_val # switch back to COSINE for next iteration
|
||||
eye_color.next = 1 # advance to next color
|
||||
}
|
||||
|
||||
run cylon_eye
|
||||
@ -32,9 +32,10 @@ sequence palette_demo {
|
||||
wait 1s
|
||||
play ocean_anim for 10s
|
||||
wait 1s
|
||||
repeat 2 times:
|
||||
repeat 2 times {
|
||||
play fire_anim for 3s
|
||||
play ocean_anim for 3s
|
||||
}
|
||||
}
|
||||
|
||||
run palette_demo
|
||||
@ -71,11 +71,12 @@ sequence palette_showcase {
|
||||
wait 1s
|
||||
|
||||
# Quick cycle through all
|
||||
repeat 3 times:
|
||||
repeat 3 times {
|
||||
play fire_effect for 2s
|
||||
play ocean_waves for 2s
|
||||
play aurora_lights for 2s
|
||||
play sunset_glow for 2s
|
||||
}
|
||||
}
|
||||
|
||||
run palette_showcase
|
||||
@ -33,11 +33,12 @@ sequence demo {
|
||||
wait 500ms
|
||||
|
||||
# Play all together for final effect
|
||||
repeat 3 times:
|
||||
repeat forever {
|
||||
play left_pulse for 2s
|
||||
play center_pulse for 2s
|
||||
play right_pulse for 2s
|
||||
wait 1s
|
||||
}
|
||||
}
|
||||
|
||||
run demo
|
||||
@ -3,9 +3,11 @@
|
||||
|
||||
#strip length 60
|
||||
|
||||
palette rainbow_palette = [0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF] # rainbow colors
|
||||
|
||||
# Create smooth rainbow cycle animation
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=[0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF] # rainbow colors
|
||||
palette=rainbow_palette
|
||||
cycle_period=5s # cycle period
|
||||
)
|
||||
animation rainbow_animation = solid(color=rainbow_cycle)
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
# Sequence Assignments Demo
|
||||
# Demonstrates dynamic property changes within sequences
|
||||
|
||||
# Set up strip length and value providers
|
||||
set strip_len = strip_length()
|
||||
set triangle_val = triangle(min_value=0, max_value=strip_len - 2, duration=5s)
|
||||
set cosine_val = cosine_osc(min_value=0, max_value=strip_len - 2, duration=5s)
|
||||
set brightness_high = 255
|
||||
set brightness_low = 64
|
||||
|
||||
# Create color palette and cycling color
|
||||
palette eye_palette = [red, yellow, green, violet]
|
||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
||||
|
||||
# Create animations
|
||||
animation red_eye = beacon_animation(
|
||||
color=eye_color
|
||||
pos=cosine_val
|
||||
beacon_size=3
|
||||
slew_size=2
|
||||
priority=10
|
||||
)
|
||||
|
||||
animation pulse_demo = pulsating_animation(
|
||||
color=blue
|
||||
period=2s
|
||||
priority=5
|
||||
)
|
||||
|
||||
# Sequence 1: Cylon Eye with Position Changes
|
||||
sequence cylon_eye {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val # Change to triangle oscillator
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val # Change back to cosine
|
||||
eye_color.next = 1 # Advance to next color
|
||||
play red_eye for 2s
|
||||
}
|
||||
|
||||
# Sequence 2: Brightness Control Demo
|
||||
sequence brightness_demo {
|
||||
play pulse_demo for 2s
|
||||
pulse_demo.opacity = brightness_low # Dim the animation
|
||||
play pulse_demo for 2s
|
||||
pulse_demo.opacity = brightness_high # Brighten again
|
||||
play pulse_demo for 2s
|
||||
}
|
||||
|
||||
# Sequence 3: Multiple Property Changes
|
||||
sequence multi_change {
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = red # Change color
|
||||
pulse_demo.opacity = brightness_low # And brightness
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = green # Change color again
|
||||
pulse_demo.opacity = brightness_high # Full brightness
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = blue # Back to blue
|
||||
}
|
||||
|
||||
# Sequence 4: Assignments in Repeat Blocks
|
||||
sequence repeat_demo {
|
||||
repeat 3 times {
|
||||
play red_eye for 1s
|
||||
red_eye.pos = triangle_val # Change oscillator
|
||||
play red_eye for 1s
|
||||
red_eye.pos = cosine_val # Change back
|
||||
eye_color.next = 1 # Next color
|
||||
}
|
||||
}
|
||||
|
||||
# Main demo sequence combining all examples
|
||||
sequence main_demo {
|
||||
# Run cylon eye demo
|
||||
play red_eye for 1s
|
||||
wait 500ms
|
||||
|
||||
# Demonstrate position changes
|
||||
red_eye.pos = triangle_val
|
||||
play red_eye for 2s
|
||||
red_eye.pos = cosine_val
|
||||
play red_eye for 2s
|
||||
|
||||
# Color cycling
|
||||
eye_color.next = 1
|
||||
play red_eye for 1s
|
||||
eye_color.next = 1
|
||||
play red_eye for 1s
|
||||
|
||||
wait 1s
|
||||
|
||||
# Brightness demo with pulse
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.opacity = brightness_low
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.opacity = brightness_high
|
||||
play pulse_demo for 1s
|
||||
|
||||
# Multi-property changes
|
||||
pulse_demo.color = red
|
||||
pulse_demo.opacity = brightness_low
|
||||
play pulse_demo for 1s
|
||||
pulse_demo.color = green
|
||||
pulse_demo.opacity = brightness_high
|
||||
play pulse_demo for 1s
|
||||
}
|
||||
|
||||
# Run the main demo
|
||||
run main_demo
|
||||
@ -0,0 +1,27 @@
|
||||
# Swipe Rainbow
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
palette palette_olivary = [
|
||||
red,
|
||||
orange,
|
||||
yellow,
|
||||
green,
|
||||
blue,
|
||||
indigo,
|
||||
violet,
|
||||
white
|
||||
]
|
||||
|
||||
color olivary = color_cycle(palette=palette_olivary, cycle_period=0)
|
||||
|
||||
animation swipe_animation = solid(
|
||||
color = olivary
|
||||
)
|
||||
|
||||
sequence slide_colors {
|
||||
play swipe_animation for 1s
|
||||
olivary.next = 1
|
||||
}
|
||||
|
||||
run slide_colors
|
||||
@ -241,41 +241,35 @@ color static_accent = solid(color=accent)
|
||||
|
||||
### ColorCycleColorProvider
|
||||
|
||||
Cycles through a custom list of colors with smooth transitions. Inherits from `ColorProvider`.
|
||||
Cycles through a palette of colors with brutal switching. Inherits from `ColorProvider`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `palette` | list | [0xFF0000FF, 0xFF00FF00, 0xFFFF0000] | - | List of colors to cycle through |
|
||||
| `palette` | bytes | default palette | - | Palette bytes in AARRGGBB format |
|
||||
| `cycle_period` | int | 5000 | min: 0 | Cycle time in ms (0 = manual only) |
|
||||
| `transition_type` | int | 1 | enum: [0,1] | 0=linear, 1=sine/smooth |
|
||||
| `next` | int | 0 | - | Write 1 to move to next color manually |
|
||||
| `next` | int | 0 | - | Write 1 to move to next color manually, or any number to go forward or backwars by `n` colors |
|
||||
|
||||
**Modes**: Auto-cycle (`cycle_period > 0`) or Manual-only (`cycle_period = 0`)
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# RGB cycle with smooth transitions
|
||||
# RGB cycle with brutal switching
|
||||
color rgb_cycle = color_cycle(
|
||||
palette=[red, green, blue],
|
||||
cycle_period=4s,
|
||||
transition_type=1
|
||||
palette=bytes("FF0000FF" "FF00FF00" "FFFF0000"),
|
||||
cycle_period=4s
|
||||
)
|
||||
|
||||
# Custom warm colors
|
||||
color warm_red = 0xFF4500
|
||||
color warm_orange = 0xFF8C00
|
||||
color warm_cycle = color_cycle(
|
||||
palette=[warm_red, warm_orange, yellow],
|
||||
cycle_period=3s,
|
||||
transition_type=1
|
||||
palette=bytes("FF4500FF" "FF8C00FF" "FFFF00"),
|
||||
cycle_period=3s
|
||||
)
|
||||
|
||||
# Mixed predefined and custom colors
|
||||
# Mixed colors in AARRGGBB format
|
||||
color mixed_cycle = color_cycle(
|
||||
palette=[0xFF0000, green, 0x0000FF],
|
||||
cycle_period=2s,
|
||||
transition_type=0
|
||||
palette=bytes("FFFF0000" "FF00FF00" "FF0000FF"),
|
||||
cycle_period=2s
|
||||
)
|
||||
```
|
||||
|
||||
@ -382,7 +376,7 @@ color deep_breath = breathe_color(
|
||||
)
|
||||
|
||||
# Using dynamic base color
|
||||
color rainbow_cycle = color_cycle(palette=[red, green, blue], cycle_period=5s)
|
||||
color rainbow_cycle = color_cycle(palette=bytes("FF0000FF" "FF00FF00" "FFFF0000"), cycle_period=5s)
|
||||
color breathing_rainbow = breathe_color(
|
||||
base_color=rainbow_cycle,
|
||||
min_brightness=30,
|
||||
|
||||
@ -99,6 +99,7 @@ static var PARAMS = {
|
||||
- **`"int"`** - Integer values (default if not specified)
|
||||
- **`"string"`** - String values
|
||||
- **`"bool"`** - Boolean values (true/false)
|
||||
- **`"bytes"`** - Bytes objects (validated using isinstance())
|
||||
- **`"instance"`** - Object instances
|
||||
- **`"any"`** - Any type (no type validation)
|
||||
|
||||
|
||||
@ -220,7 +220,7 @@ color my_white = white # Reference to predefined color
|
||||
|
||||
# Color providers for dynamic colors
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=[red, green, blue]
|
||||
palette=bytes("FFFF0000" "FF00FF00" "FF0000FF")
|
||||
cycle_period=5s
|
||||
)
|
||||
color breathing_red = breathe_color(
|
||||
@ -491,7 +491,9 @@ The following user functions are available by default (see [User Functions Guide
|
||||
|
||||
## Sequences
|
||||
|
||||
Sequences orchestrate multiple animations with timing control:
|
||||
Sequences orchestrate multiple animations with timing control. The DSL supports two syntaxes for sequences with repeat functionality:
|
||||
|
||||
### Basic Sequence Syntax
|
||||
|
||||
```berry
|
||||
sequence demo {
|
||||
@ -499,14 +501,43 @@ sequence demo {
|
||||
wait 1s
|
||||
play blue_animation for 2s
|
||||
|
||||
repeat 3 times:
|
||||
repeat 3 times {
|
||||
play flash_effect for 200ms
|
||||
wait 300ms
|
||||
}
|
||||
|
||||
play final_animation
|
||||
}
|
||||
```
|
||||
|
||||
### Repeat Sequence Syntax
|
||||
|
||||
For sequences that are primarily repeating patterns, you can use the alternative syntax:
|
||||
|
||||
```berry
|
||||
# Option 1: Traditional syntax with repeat sub-sequence
|
||||
sequence cylon_eye {
|
||||
repeat forever {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val
|
||||
eye_color.next = 1
|
||||
}
|
||||
}
|
||||
|
||||
# Option 2: Alternative syntax - sequence with repeat modifier
|
||||
sequence cylon_eye repeat forever {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val
|
||||
eye_color.next = 1
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Both syntaxes are functionally equivalent. The second syntax creates an outer sequence (runs once) containing an inner repeat sub-sequence.
|
||||
|
||||
### Sequence Statements
|
||||
|
||||
#### Play Statement
|
||||
@ -514,6 +545,7 @@ sequence demo {
|
||||
```berry
|
||||
play animation_name # Play indefinitely
|
||||
play animation_name for 5s # Play for specific duration
|
||||
play animation_name for duration_var # Play for variable duration
|
||||
```
|
||||
|
||||
#### Wait Statement
|
||||
@ -521,22 +553,119 @@ play animation_name for 5s # Play for specific duration
|
||||
```berry
|
||||
wait 1s # Wait for 1 second
|
||||
wait 500ms # Wait for 500 milliseconds
|
||||
wait duration_var # Wait for variable duration
|
||||
```
|
||||
|
||||
#### Duration Support
|
||||
|
||||
Both `play` and `wait` statements support flexible duration specifications:
|
||||
|
||||
**Literal Time Values:**
|
||||
```berry
|
||||
play animation for 5s # 5 seconds
|
||||
play animation for 2000ms # 2000 milliseconds
|
||||
play animation for 1m # 1 minute
|
||||
```
|
||||
|
||||
**Variable References:**
|
||||
```berry
|
||||
set short_time = 2s
|
||||
set long_time = 10s
|
||||
|
||||
sequence demo {
|
||||
play animation for short_time # Use variable duration
|
||||
wait long_time # Variables work in wait too
|
||||
}
|
||||
```
|
||||
|
||||
**Value Providers (Dynamic Duration):**
|
||||
```berry
|
||||
set dynamic_duration = triangle(min_value=1000, max_value=5000, period=10s)
|
||||
|
||||
sequence demo {
|
||||
play animation for dynamic_duration # Duration changes over time
|
||||
}
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```berry
|
||||
# Cylon eye with variable duration
|
||||
set eye_duration = 5s
|
||||
|
||||
sequence cylon_eye forever {
|
||||
play red_eye for eye_duration # Use variable for consistent timing
|
||||
red_eye.pos = triangle_val
|
||||
play red_eye for eye_duration # Same duration for both phases
|
||||
red_eye.pos = cosine_val
|
||||
eye_color.next = 1
|
||||
}
|
||||
```
|
||||
|
||||
#### Repeat Statement
|
||||
|
||||
```berry
|
||||
repeat 5 times:
|
||||
play effect for 1s
|
||||
wait 500ms
|
||||
Repeat statements create runtime sub-sequences that execute repeatedly:
|
||||
|
||||
# Nested repeats are supported
|
||||
repeat 3 times:
|
||||
play intro for 2s
|
||||
repeat 2 times:
|
||||
play flash for 100ms
|
||||
wait 200ms
|
||||
play outro for 1s
|
||||
```berry
|
||||
repeat 3 times { # Repeat exactly 3 times
|
||||
play animation for 1s
|
||||
wait 500ms
|
||||
}
|
||||
|
||||
repeat forever { # Repeat indefinitely until parent sequence stops
|
||||
play animation for 1s
|
||||
wait 500ms
|
||||
}
|
||||
```
|
||||
|
||||
**Repeat Behavior:**
|
||||
- **Runtime Execution**: Repeats are executed at runtime, not expanded at compile time
|
||||
- **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
|
||||
- **Efficient**: No memory overhead for large repeat counts
|
||||
|
||||
#### Assignment Statement
|
||||
|
||||
Property assignments can be performed within sequences to dynamically modify animation parameters during playback:
|
||||
|
||||
```berry
|
||||
sequence demo {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val # Change position to triangle oscillator
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val # Change position to cosine oscillator
|
||||
eye_color.next = 1 # Advance color cycle to next color
|
||||
}
|
||||
```
|
||||
|
||||
**Assignment Semantics:**
|
||||
- Assignments in sequences have exactly the same semantics as assignments outside sequences
|
||||
- They can assign static values, value providers, or computed expressions
|
||||
- Assignments are executed instantly when the sequence step is reached
|
||||
- The assignment is wrapped in a closure: `def (engine) <assign_code> end`
|
||||
|
||||
**Examples:**
|
||||
```berry
|
||||
sequence dynamic_show {
|
||||
play pulse_anim for 2s
|
||||
pulse_anim.opacity = 128 # Set static opacity
|
||||
play pulse_anim for 2s
|
||||
pulse_anim.opacity = brightness # Use value provider
|
||||
play pulse_anim for 2s
|
||||
pulse_anim.color = next_color # Change color provider
|
||||
play pulse_anim for 2s
|
||||
}
|
||||
|
||||
# Assignments work in repeat blocks too
|
||||
sequence cylon_eye {
|
||||
repeat 3 times {
|
||||
play red_eye for 1s
|
||||
red_eye.pos = triangle_val # Change oscillator pattern
|
||||
play red_eye for 1s
|
||||
red_eye.pos = cosine_val # Change back
|
||||
eye_color.next = 1 # Advance color
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Execution Statements
|
||||
@ -828,13 +957,14 @@ animation_def = "animation" identifier "=" animation_expression ;
|
||||
property_assignment = identifier "." identifier "=" expression ;
|
||||
|
||||
(* Sequences *)
|
||||
sequence = "sequence" identifier "{" sequence_body "}" ;
|
||||
sequence = "sequence" identifier [ "repeat" ( number "times" | "forever" ) ] "{" sequence_body "}" ;
|
||||
sequence_body = { sequence_statement } ;
|
||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt ;
|
||||
sequence_statement = play_stmt | wait_stmt | repeat_stmt | sequence_assignment ;
|
||||
|
||||
play_stmt = "play" identifier [ "for" time_expression ] ;
|
||||
wait_stmt = "wait" time_expression ;
|
||||
repeat_stmt = "repeat" number "times" ":" sequence_body ;
|
||||
repeat_stmt = "repeat" ( number "times" | "forever" ) "{" sequence_body "}" ;
|
||||
sequence_assignment = identifier "." identifier "=" expression ;
|
||||
|
||||
(* Execution *)
|
||||
execution_stmt = "run" identifier ;
|
||||
|
||||
@ -96,9 +96,98 @@ sequence sunrise_show {
|
||||
run sunrise_show
|
||||
```
|
||||
|
||||
### 8.1. Variable Duration Sequences
|
||||
```berry
|
||||
# Define timing variables for consistent durations
|
||||
set short_duration = 2s
|
||||
set long_duration = 5s
|
||||
set fade_time = 1s
|
||||
|
||||
animation red_anim = solid(color=red)
|
||||
animation green_anim = solid(color=green)
|
||||
animation blue_anim = solid(color=blue)
|
||||
|
||||
sequence timed_show forever {
|
||||
play red_anim for short_duration # Use variable duration
|
||||
wait fade_time # Variable wait time
|
||||
play green_anim for long_duration # Different variable duration
|
||||
wait fade_time
|
||||
play blue_anim for short_duration # Reuse timing variable
|
||||
}
|
||||
run timed_show
|
||||
```
|
||||
|
||||
## Sequence Assignments
|
||||
|
||||
### 9. Dynamic Property Changes
|
||||
```berry
|
||||
# Create oscillators for dynamic position
|
||||
set triangle_val = triangle(min_value=0, max_value=27, duration=5s)
|
||||
set cosine_val = cosine_osc(min_value=0, max_value=27, duration=5s)
|
||||
|
||||
# Create color cycle
|
||||
palette eye_palette = [red, yellow, green, violet]
|
||||
color eye_color = color_cycle(palette=eye_palette, cycle_period=0)
|
||||
|
||||
# Create beacon animation
|
||||
animation red_eye = beacon_animation(
|
||||
color=eye_color
|
||||
pos=cosine_val
|
||||
beacon_size=3
|
||||
slew_size=2
|
||||
priority=10
|
||||
)
|
||||
|
||||
# Sequence with property assignments
|
||||
sequence cylon_eye {
|
||||
play red_eye for 3s
|
||||
red_eye.pos = triangle_val # Change to triangle oscillator
|
||||
play red_eye for 3s
|
||||
red_eye.pos = cosine_val # Change back to cosine
|
||||
eye_color.next = 1 # Advance to next color
|
||||
}
|
||||
run cylon_eye
|
||||
```
|
||||
|
||||
### 10. Multiple Assignments in Sequence
|
||||
```berry
|
||||
set high_brightness = 255
|
||||
set low_brightness = 64
|
||||
color my_blue = 0x0000FF
|
||||
|
||||
animation test = solid(color=red)
|
||||
test.opacity = high_brightness
|
||||
|
||||
sequence demo {
|
||||
play test for 1s
|
||||
test.opacity = low_brightness # Dim the animation
|
||||
test.color = my_blue # Change color to blue
|
||||
play test for 1s
|
||||
test.opacity = high_brightness # Brighten again
|
||||
play test for 1s
|
||||
}
|
||||
run demo
|
||||
```
|
||||
|
||||
### 11. Assignments in Repeat Blocks
|
||||
```berry
|
||||
set brightness = smooth(min_value=50, max_value=255, period=2s)
|
||||
animation pulse = pulsating_animation(color=white, period=1s)
|
||||
|
||||
sequence breathing_cycle {
|
||||
repeat 3 times {
|
||||
play pulse for 500ms
|
||||
pulse.opacity = brightness # Apply breathing effect
|
||||
wait 200ms
|
||||
pulse.opacity = 255 # Reset to full brightness
|
||||
}
|
||||
}
|
||||
run breathing_cycle
|
||||
```
|
||||
|
||||
## User Functions in Computed Parameters
|
||||
|
||||
### 9. Simple User Function
|
||||
### 12. Simple User Function
|
||||
```berry
|
||||
# Simple user function in computed parameter
|
||||
animation random_base = solid(color=blue, priority=10)
|
||||
@ -106,7 +195,7 @@ random_base.opacity = rand_demo()
|
||||
run random_base
|
||||
```
|
||||
|
||||
### 10. User Function with Math Operations
|
||||
### 13. User Function with Math Operations
|
||||
```berry
|
||||
# Mix user functions with mathematical functions
|
||||
animation random_bounded = solid(
|
||||
@ -117,7 +206,7 @@ animation random_bounded = solid(
|
||||
run random_bounded
|
||||
```
|
||||
|
||||
### 11. User Function in Arithmetic Expression
|
||||
### 14. User Function in Arithmetic Expression
|
||||
```berry
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -130,9 +219,80 @@ run random_variation
|
||||
|
||||
See `anim_examples/user_functions_demo.anim` for a complete working example.
|
||||
|
||||
## New Repeat System Examples
|
||||
|
||||
### 15. Runtime Repeat with Forever Loop
|
||||
```berry
|
||||
color red = 0xFF0000
|
||||
color blue = 0x0000FF
|
||||
animation red_anim = solid(color=red)
|
||||
animation blue_anim = solid(color=blue)
|
||||
|
||||
# Traditional syntax with repeat sub-sequence
|
||||
sequence cylon_effect {
|
||||
repeat forever {
|
||||
play red_anim for 1s
|
||||
play blue_anim for 1s
|
||||
}
|
||||
}
|
||||
|
||||
# Alternative syntax - sequence with repeat modifier
|
||||
sequence cylon_effect_alt repeat forever {
|
||||
play red_anim for 1s
|
||||
play blue_anim for 1s
|
||||
}
|
||||
|
||||
run cylon_effect
|
||||
```
|
||||
|
||||
### 16. Nested Repeats (Multiplication)
|
||||
```berry
|
||||
color green = 0x00FF00
|
||||
color yellow = 0xFFFF00
|
||||
animation green_anim = solid(color=green)
|
||||
animation yellow_anim = solid(color=yellow)
|
||||
|
||||
# Nested repeats: 3 × 2 = 6 total iterations
|
||||
sequence nested_pattern {
|
||||
repeat 3 times {
|
||||
repeat 2 times {
|
||||
play green_anim for 200ms
|
||||
play yellow_anim for 200ms
|
||||
}
|
||||
wait 500ms # Pause between outer iterations
|
||||
}
|
||||
}
|
||||
run nested_pattern
|
||||
```
|
||||
|
||||
### 17. 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)
|
||||
|
||||
color eye_color = color_cycle(palette=[red, yellow, green, blue], cycle_period=0)
|
||||
animation moving_eye = beacon_animation(
|
||||
color=eye_color
|
||||
pos=triangle_pos
|
||||
beacon_size=2
|
||||
slew_size=1
|
||||
)
|
||||
|
||||
sequence dynamic_cylon {
|
||||
repeat 5 times {
|
||||
play moving_eye for 2s
|
||||
moving_eye.pos = cosine_pos # Switch to cosine movement
|
||||
play moving_eye for 2s
|
||||
moving_eye.pos = triangle_pos # Switch back to triangle
|
||||
eye_color.next = 1 # Next color
|
||||
}
|
||||
}
|
||||
run dynamic_cylon
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### 13. Dynamic Position
|
||||
### 18. Dynamic Position
|
||||
```berry
|
||||
strip length 60
|
||||
|
||||
@ -148,7 +308,7 @@ animation moving_pulse = beacon_animation(
|
||||
run moving_pulse
|
||||
```
|
||||
|
||||
### 14. Multi-Layer Effect
|
||||
### 19. Multi-Layer Effect
|
||||
```berry
|
||||
# Base layer - slow breathing
|
||||
set breathing = smooth(min_value=100, max_value=255, period=4s)
|
||||
|
||||
@ -77,15 +77,32 @@ sequence rgb_show {
|
||||
wait 500ms
|
||||
play blue_pulse for 3s
|
||||
|
||||
repeat 2 times:
|
||||
repeat 2 times {
|
||||
play red_pulse for 1s
|
||||
play green_pulse for 1s
|
||||
play blue_pulse for 1s
|
||||
}
|
||||
}
|
||||
|
||||
run rgb_show
|
||||
```
|
||||
|
||||
**Pro Tip: Variable Durations**
|
||||
Use variables for consistent timing:
|
||||
|
||||
```berry
|
||||
# Define timing variables
|
||||
set short_time = 1s
|
||||
set long_time = 3s
|
||||
|
||||
sequence timed_show {
|
||||
play red_pulse for long_time # Use variable duration
|
||||
wait 500ms
|
||||
play green_pulse for short_time # Different timing
|
||||
play blue_pulse for long_time # Reuse timing
|
||||
}
|
||||
```
|
||||
|
||||
## Step 5: Dynamic Effects
|
||||
|
||||
Add movement and variation to your animations:
|
||||
|
||||
@ -250,6 +250,24 @@ end
|
||||
animation pulse_anim = pulsating_animation(color=red, period=2s)
|
||||
```
|
||||
|
||||
6. **Variable Duration Support:**
|
||||
```berry
|
||||
# Now supported - variables in play/wait durations
|
||||
set eye_duration = 5s
|
||||
|
||||
sequence cylon_eye {
|
||||
play red_eye for eye_duration # ✓ Variables now work
|
||||
wait eye_duration # ✓ Variables work in wait too
|
||||
}
|
||||
|
||||
# Also supported - value providers for dynamic duration
|
||||
set dynamic_time = triangle(min_value=1000, max_value=3000, period=10s)
|
||||
|
||||
sequence demo {
|
||||
play animation for dynamic_time # ✓ Dynamic duration
|
||||
}
|
||||
```
|
||||
|
||||
6. **Parameter Constraint Violations:**
|
||||
```berry
|
||||
# Wrong - negative period not allowed
|
||||
@ -265,6 +283,41 @@ end
|
||||
animation good_comet = comet_animation(color=red, direction=1)
|
||||
```
|
||||
|
||||
7. **Repeat Syntax Errors:**
|
||||
```berry
|
||||
# Wrong - old colon syntax no longer supported
|
||||
sequence bad_demo {
|
||||
repeat 3 times: # Error: Expected '{' after 'times'
|
||||
play anim for 1s
|
||||
}
|
||||
|
||||
# Wrong - missing braces
|
||||
sequence bad_demo2 {
|
||||
repeat 3 times
|
||||
play anim for 1s # Error: Expected '{' after 'times'
|
||||
}
|
||||
|
||||
# Correct - use braces for repeat blocks
|
||||
sequence good_demo {
|
||||
repeat 3 times {
|
||||
play anim for 1s
|
||||
}
|
||||
}
|
||||
|
||||
# Also correct - alternative syntax
|
||||
sequence good_demo_alt repeat 3 times {
|
||||
play anim for 1s
|
||||
}
|
||||
|
||||
# Correct - forever syntax
|
||||
sequence infinite_demo {
|
||||
repeat forever {
|
||||
play anim for 1s
|
||||
wait 500ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DSL Runtime Errors
|
||||
|
||||
**Problem:** DSL compiles but fails at runtime
|
||||
@ -281,7 +334,28 @@ end
|
||||
run red_anim
|
||||
```
|
||||
|
||||
2. **Sequence Issues:**
|
||||
2. **Repeat Performance Issues:**
|
||||
```berry
|
||||
# Efficient - runtime repeats don't expand at compile time
|
||||
sequence efficient {
|
||||
repeat 1000 times { # No memory overhead for large counts
|
||||
play anim for 100ms
|
||||
wait 50ms
|
||||
}
|
||||
}
|
||||
|
||||
# Nested repeats work efficiently
|
||||
sequence nested {
|
||||
repeat 100 times {
|
||||
repeat 50 times { # Total: 5000 iterations, but efficient
|
||||
play quick_flash for 10ms
|
||||
}
|
||||
wait 100ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Sequence Issues:**
|
||||
```berry
|
||||
# Make sure animations are defined before sequences
|
||||
color red = 0xFF0000
|
||||
@ -294,7 +368,7 @@ end
|
||||
run demo
|
||||
```
|
||||
|
||||
3. **Undefined References:**
|
||||
4. **Undefined References:**
|
||||
```berry
|
||||
# Wrong - using undefined animation in sequence
|
||||
sequence bad_demo {
|
||||
|
||||
@ -73,7 +73,7 @@ register_to_animation(animation_base)
|
||||
import "core/sequence_manager" as sequence_manager
|
||||
register_to_animation(sequence_manager)
|
||||
|
||||
# Unified animation engine - central controller for all animations
|
||||
# Unified animation engine - central engine for all animations
|
||||
# Provides priority-based layering, automatic blending, and performance optimization
|
||||
import "core/animation_engine" as animation_engine
|
||||
register_to_animation(animation_engine)
|
||||
|
||||
@ -19,6 +19,8 @@
|
||||
#
|
||||
|
||||
import global
|
||||
import animation
|
||||
|
||||
# Requires to first `import animation`
|
||||
# We don't include it to not create a closure, but use the global instead
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ class Animation : animation.parameterized_object
|
||||
end
|
||||
|
||||
# Update animation state based on current time
|
||||
# This method should be called regularly by the animation controller
|
||||
# This method should be called regularly by the animation engine
|
||||
#
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return bool - True if animation is still running, false if completed
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
# Unified Animation Engine
|
||||
# Combines AnimationController, AnimationManager, and Renderer into a single efficient class
|
||||
#
|
||||
# This unified approach eliminates redundancy and provides a simpler, more efficient
|
||||
# animation system for Tasmota LED control.
|
||||
|
||||
class AnimationEngine
|
||||
# Core properties
|
||||
@ -61,6 +58,12 @@ class AnimationEngine
|
||||
self.animations[i].start(now)
|
||||
i += 1
|
||||
end
|
||||
|
||||
i = 0
|
||||
while (i < size(self.sequence_managers))
|
||||
self.sequence_managers[i].start(now)
|
||||
i += 1
|
||||
end
|
||||
|
||||
tasmota.add_fast_loop(self.fast_loop_closure)
|
||||
end
|
||||
@ -126,7 +129,7 @@ class AnimationEngine
|
||||
self.animations = []
|
||||
var i = 0
|
||||
while i < size(self.sequence_managers)
|
||||
self.sequence_managers[i].stop_sequence()
|
||||
self.sequence_managers[i].stop()
|
||||
i += 1
|
||||
end
|
||||
self.sequence_managers = []
|
||||
@ -190,7 +193,7 @@ class AnimationEngine
|
||||
# Update sequence managers
|
||||
var i = 0
|
||||
while i < size(self.sequence_managers)
|
||||
self.sequence_managers[i].update()
|
||||
self.sequence_managers[i].update(current_time)
|
||||
i += 1
|
||||
end
|
||||
|
||||
@ -423,11 +426,5 @@ def create_engine(strip)
|
||||
return animation.animation_engine(strip)
|
||||
end
|
||||
|
||||
# Compatibility function for legacy examples
|
||||
def animation_controller(strip)
|
||||
return animation.animation_engine(strip)
|
||||
end
|
||||
|
||||
return {'animation_engine': AnimationEngine,
|
||||
'create_engine': create_engine,
|
||||
'animation_controller': animation_controller}
|
||||
'create_engine': create_engine}
|
||||
@ -227,6 +227,13 @@ class ParameterizedObject
|
||||
import math
|
||||
value = int(math.round(value))
|
||||
actual_type = "int"
|
||||
# Special case: check for bytes type using isinstance()
|
||||
elif expected_type == "bytes"
|
||||
if actual_type == "instance" && isinstance(value, bytes)
|
||||
actual_type = "bytes"
|
||||
elif actual_type != "instance" || !isinstance(value, bytes)
|
||||
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
|
||||
end
|
||||
elif expected_type != actual_type
|
||||
raise "value_error", f"Parameter '{name}' expects type '{expected_type}' but got '{actual_type}' (value: {value})"
|
||||
end
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# Sequence Manager for Animation DSL
|
||||
# Handles async execution of animation sequences without blocking delays
|
||||
# Supports sub-sequences and repeat logic through recursive composition
|
||||
|
||||
class SequenceManager
|
||||
var controller # Animation controller reference
|
||||
var engine # Animation engine reference
|
||||
var active_sequence # Currently running sequence
|
||||
var sequence_state # Current sequence execution state
|
||||
var step_index # Current step in the sequence
|
||||
@ -10,64 +11,169 @@ class SequenceManager
|
||||
var steps # List of sequence steps
|
||||
var is_running # Whether sequence is active
|
||||
|
||||
def init(controller)
|
||||
self.controller = controller
|
||||
# Repeat-specific properties
|
||||
var repeat_count # Number of times to repeat this sequence (-1 for forever, 0 for no repeat)
|
||||
var current_iteration # Current iteration (0-based)
|
||||
var is_repeat_sequence # Whether this is a repeat sub-sequence
|
||||
|
||||
def init(engine, repeat_count)
|
||||
self.engine = engine
|
||||
self.active_sequence = nil
|
||||
self.sequence_state = {}
|
||||
self.step_index = 0
|
||||
self.step_start_time = 0
|
||||
self.steps = []
|
||||
self.is_running = false
|
||||
|
||||
# Repeat logic
|
||||
self.repeat_count = repeat_count != nil ? repeat_count : 1 # Default: run once
|
||||
self.current_iteration = 0
|
||||
self.is_repeat_sequence = repeat_count != nil && repeat_count != 1
|
||||
end
|
||||
|
||||
# Start a new sequence
|
||||
def start_sequence(steps)
|
||||
self.stop_sequence() # Stop any current sequence
|
||||
|
||||
self.steps = steps
|
||||
self.step_index = 0
|
||||
self.step_start_time = self.controller.time_ms
|
||||
self.is_running = true
|
||||
|
||||
if size(self.steps) > 0
|
||||
self.execute_current_step()
|
||||
end
|
||||
# Add a step to this sequence
|
||||
def push_step(step)
|
||||
self.steps.push(step)
|
||||
return self
|
||||
end
|
||||
|
||||
# Stop the current sequence
|
||||
def stop_sequence()
|
||||
# Add a play step directly
|
||||
def push_play_step(animation_ref, duration)
|
||||
self.steps.push({
|
||||
"type": "play",
|
||||
"animation": animation_ref,
|
||||
"duration": duration != nil ? duration : 0
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
# Add a wait step directly
|
||||
def push_wait_step(duration)
|
||||
self.steps.push({
|
||||
"type": "wait",
|
||||
"duration": duration
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
# Add an assignment step directly
|
||||
def push_assign_step(closure)
|
||||
self.steps.push({
|
||||
"type": "assign",
|
||||
"closure": closure
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
# Add a repeat subsequence step directly
|
||||
def push_repeat_subsequence(sequence_manager)
|
||||
self.steps.push({
|
||||
"type": "subsequence",
|
||||
"sequence_manager": sequence_manager
|
||||
})
|
||||
return self
|
||||
end
|
||||
|
||||
# Start this sequence
|
||||
def start(time_ms)
|
||||
# Stop any current sequence
|
||||
if self.is_running
|
||||
self.is_running = false
|
||||
self.controller.clear()
|
||||
self.engine.clear()
|
||||
# Stop any sub-sequences
|
||||
self.stop_all_subsequences()
|
||||
end
|
||||
|
||||
# Initialize sequence state
|
||||
self.step_index = 0
|
||||
self.step_start_time = time_ms
|
||||
self.current_iteration = 0
|
||||
self.is_running = true
|
||||
|
||||
# Start executing if we have steps
|
||||
if size(self.steps) > 0
|
||||
self.execute_current_step(time_ms)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
# Stop this sequence manager
|
||||
def stop()
|
||||
if self.is_running
|
||||
self.is_running = false
|
||||
|
||||
# Stop any currently playing animations
|
||||
if self.step_index < size(self.steps)
|
||||
var current_step = self.steps[self.step_index]
|
||||
if current_step["type"] == "play"
|
||||
var anim = current_step["animation"]
|
||||
self.engine.remove_animation(anim)
|
||||
elif current_step["type"] == "subsequence"
|
||||
var sub_seq = current_step["sequence_manager"]
|
||||
sub_seq.stop()
|
||||
end
|
||||
end
|
||||
|
||||
# Clear engine and stop all sub-sequences
|
||||
self.engine.clear()
|
||||
self.stop_all_subsequences()
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# Stop all sub-sequences in our steps
|
||||
def stop_all_subsequences()
|
||||
for step : self.steps
|
||||
if step["type"] == "subsequence"
|
||||
var sub_seq = step["sequence_manager"]
|
||||
sub_seq.stop()
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
# Update sequence state - called from fast_loop
|
||||
def update()
|
||||
# Returns true if still running, false if completed
|
||||
def update(current_time)
|
||||
if !self.is_running || size(self.steps) == 0
|
||||
return
|
||||
return false
|
||||
end
|
||||
|
||||
var current_time = self.controller.time_ms
|
||||
var current_step = self.steps[self.step_index]
|
||||
|
||||
# Check if current step has completed
|
||||
if current_step.contains("duration") && current_step["duration"] > 0
|
||||
var elapsed = current_time - self.step_start_time
|
||||
if elapsed >= current_step["duration"]
|
||||
self.advance_to_next_step()
|
||||
# Handle different step types
|
||||
if current_step["type"] == "subsequence"
|
||||
# Handle sub-sequence (including repeat sequences)
|
||||
var sub_seq = current_step["sequence_manager"]
|
||||
if !sub_seq.update(current_time)
|
||||
# Sub-sequence finished, advance to next step
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
elif current_step["type"] == "assign"
|
||||
# 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)
|
||||
else
|
||||
# Steps without duration (like stop steps) complete immediately
|
||||
# Advance to next step on next update cycle
|
||||
self.advance_to_next_step()
|
||||
# Handle regular steps with duration
|
||||
if current_step.contains("duration") && current_step["duration"] > 0
|
||||
var elapsed = current_time - self.step_start_time
|
||||
if elapsed >= current_step["duration"]
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
else
|
||||
# Steps without duration complete immediately
|
||||
self.advance_to_next_step(current_time)
|
||||
end
|
||||
end
|
||||
|
||||
return self.is_running
|
||||
end
|
||||
|
||||
# Execute the current step
|
||||
def execute_current_step()
|
||||
def execute_current_step(current_time)
|
||||
if self.step_index >= size(self.steps)
|
||||
self.is_running = false
|
||||
self.complete_iteration(current_time)
|
||||
return
|
||||
end
|
||||
|
||||
@ -75,13 +181,8 @@ class SequenceManager
|
||||
|
||||
if step["type"] == "play"
|
||||
var anim = step["animation"]
|
||||
self.controller.add_animation(anim)
|
||||
anim.start()
|
||||
|
||||
# Set duration if specified
|
||||
if step.contains("duration") && step["duration"] > 0
|
||||
anim.duration = step["duration"]
|
||||
end
|
||||
self.engine.add_animation(anim)
|
||||
anim.start(current_time)
|
||||
|
||||
elif step["type"] == "wait"
|
||||
# Wait steps are handled by the update loop checking duration
|
||||
@ -89,29 +190,82 @@ class SequenceManager
|
||||
|
||||
elif step["type"] == "stop"
|
||||
var anim = step["animation"]
|
||||
self.controller.remove_animation(anim)
|
||||
self.engine.remove_animation(anim)
|
||||
|
||||
elif step["type"] == "assign"
|
||||
# Assign 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)
|
||||
end
|
||||
|
||||
elif step["type"] == "subsequence"
|
||||
# Start sub-sequence (including repeat sequences)
|
||||
var sub_seq = step["sequence_manager"]
|
||||
sub_seq.start()
|
||||
end
|
||||
|
||||
self.step_start_time = self.controller.time_ms
|
||||
self.step_start_time = current_time
|
||||
end
|
||||
|
||||
# Advance to the next step in the sequence
|
||||
def advance_to_next_step()
|
||||
def advance_to_next_step(current_time)
|
||||
# Stop current animations if step had duration
|
||||
var current_step = self.steps[self.step_index]
|
||||
if current_step["type"] == "play" && current_step.contains("duration")
|
||||
var anim = current_step["animation"]
|
||||
self.controller.remove_animation(anim)
|
||||
self.engine.remove_animation(anim)
|
||||
end
|
||||
|
||||
self.step_index += 1
|
||||
|
||||
if self.step_index >= size(self.steps)
|
||||
self.is_running = false
|
||||
return
|
||||
self.complete_iteration(current_time)
|
||||
else
|
||||
# Execute all consecutive assign steps atomically
|
||||
self.execute_assign_steps_batch(current_time)
|
||||
end
|
||||
end
|
||||
|
||||
# Execute all consecutive assign steps in a batch to avoid black frames
|
||||
def execute_assign_steps_batch(current_time)
|
||||
# Execute all consecutive assign 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)
|
||||
end
|
||||
self.step_index += 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
self.execute_current_step()
|
||||
# Now execute the next non-assign step
|
||||
if self.step_index < size(self.steps)
|
||||
self.execute_current_step(current_time)
|
||||
else
|
||||
self.complete_iteration(current_time)
|
||||
end
|
||||
end
|
||||
|
||||
# Complete current iteration and check if we should repeat
|
||||
def complete_iteration(current_time)
|
||||
self.current_iteration += 1
|
||||
|
||||
# Check if we should continue repeating
|
||||
if self.repeat_count == -1 || self.current_iteration < self.repeat_count
|
||||
# Start next iteration
|
||||
self.step_index = 0
|
||||
self.execute_current_step(current_time)
|
||||
else
|
||||
# All iterations complete
|
||||
self.is_running = false
|
||||
end
|
||||
end
|
||||
|
||||
# Check if sequence is running
|
||||
@ -129,35 +283,12 @@ class SequenceManager
|
||||
"step_index": self.step_index,
|
||||
"total_steps": size(self.steps),
|
||||
"current_step": self.steps[self.step_index],
|
||||
"elapsed_ms": self.controller.time_ms - self.step_start_time
|
||||
"elapsed_ms": self.engine.time_ms - self.step_start_time,
|
||||
"repeat_count": self.repeat_count,
|
||||
"current_iteration": self.current_iteration,
|
||||
"is_repeat_sequence": self.is_repeat_sequence
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Helper function to create sequence steps
|
||||
def create_play_step(animation, duration)
|
||||
return {
|
||||
"type": "play",
|
||||
"animation": animation,
|
||||
"duration": duration
|
||||
}
|
||||
end
|
||||
|
||||
def create_wait_step(duration)
|
||||
return {
|
||||
"type": "wait",
|
||||
"duration": duration
|
||||
}
|
||||
end
|
||||
|
||||
def create_stop_step(animation)
|
||||
return {
|
||||
"type": "stop",
|
||||
"animation": animation
|
||||
}
|
||||
end
|
||||
|
||||
return {'SequenceManager': SequenceManager,
|
||||
'create_play_step': create_play_step,
|
||||
'create_wait_step': create_wait_step,
|
||||
'create_stop_step': create_stop_step}
|
||||
return {'SequenceManager': SequenceManager}
|
||||
@ -148,7 +148,7 @@ class DSLRuntime
|
||||
|
||||
|
||||
# Get current engine for external access
|
||||
def get_controller()
|
||||
def get_engine()
|
||||
return self.engine
|
||||
end
|
||||
|
||||
|
||||
@ -26,6 +26,7 @@ class SimpleDSLTranspiler
|
||||
var strip_initialized # Track if strip was initialized
|
||||
var sequence_names # Track which names are sequences
|
||||
var symbol_table # Track created objects: name -> instance
|
||||
var indent_level # Track current indentation level for nested sequences
|
||||
|
||||
# Static color mapping for named colors (helps with solidification)
|
||||
static var named_colors = {
|
||||
@ -54,6 +55,18 @@ class SimpleDSLTranspiler
|
||||
self.strip_initialized = false # Track if strip was initialized
|
||||
self.sequence_names = {} # Track which names are sequences
|
||||
self.symbol_table = {} # Track created objects: name -> instance
|
||||
self.indent_level = 0 # Track current indentation level
|
||||
end
|
||||
|
||||
# Get current indentation string
|
||||
def get_indent()
|
||||
# return " " * (self.indent_level + 1) # Base indentation is 2 spaces - string multiplication not supported
|
||||
var indent = ""
|
||||
var spaces_needed = (self.indent_level + 1) * 2 # Base indentation is 2 spaces
|
||||
for i : 0..spaces_needed-1
|
||||
indent += " "
|
||||
end
|
||||
return indent
|
||||
end
|
||||
|
||||
# Main transpilation method - single pass
|
||||
@ -226,7 +239,7 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
# Process palette definition: palette aurora_colors = [(0, #000022), (64, #004400), ...]
|
||||
# Process palette definition: palette aurora_colors = [(0, #000022), (64, #004400), ...] or [red, 0x008000, blue, 0x112233]
|
||||
def process_palette()
|
||||
self.next() # skip 'palette'
|
||||
var name = self.expect_identifier()
|
||||
@ -239,10 +252,23 @@ class SimpleDSLTranspiler
|
||||
|
||||
self.expect_assign()
|
||||
|
||||
# Expect array literal with tuples
|
||||
# Expect array literal
|
||||
self.expect_left_bracket()
|
||||
var palette_entries = []
|
||||
|
||||
# Detect syntax type by looking at the first entry
|
||||
self.skip_whitespace_including_newlines()
|
||||
|
||||
if self.check_right_bracket()
|
||||
# Empty palette - not allowed
|
||||
self.error("Empty palettes are not allowed. A palette must contain at least one color entry.")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
# Check if first entry starts with '(' (tuple syntax) or not (alternative syntax)
|
||||
var is_tuple_syntax = self.current() != nil && self.current().type == animation_dsl.Token.LEFT_PAREN
|
||||
|
||||
while !self.at_end() && !self.check_right_bracket()
|
||||
self.skip_whitespace_including_newlines()
|
||||
|
||||
@ -250,16 +276,41 @@ class SimpleDSLTranspiler
|
||||
break
|
||||
end
|
||||
|
||||
# Parse tuple (value, color)
|
||||
self.expect_left_paren()
|
||||
var value = self.expect_number()
|
||||
self.expect_comma()
|
||||
var color = self.process_value("color") # Reuse existing color parsing
|
||||
self.expect_right_paren()
|
||||
|
||||
# Convert to VRGB format entry
|
||||
var vrgb_entry = self.convert_to_vrgb(value, color)
|
||||
palette_entries.push(f'"{vrgb_entry}"')
|
||||
if is_tuple_syntax
|
||||
# Parse tuple (value, color) - original syntax
|
||||
# Check if we accidentally have alternative syntax in tuple mode
|
||||
if self.current() != nil && self.current().type != animation_dsl.Token.LEFT_PAREN
|
||||
self.error("Cannot mix alternative syntax [color1, color2, ...] with tuple syntax (value, color). Use only one syntax per palette.")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
self.expect_left_paren()
|
||||
var value = self.expect_number()
|
||||
self.expect_comma()
|
||||
var color = self.process_value("color") # Reuse existing color parsing
|
||||
self.expect_right_paren()
|
||||
|
||||
# Convert to VRGB format entry and store as integer
|
||||
var vrgb_entry = self.convert_to_vrgb(value, color)
|
||||
var vrgb_int = int(f"0x{vrgb_entry}")
|
||||
palette_entries.push(vrgb_int)
|
||||
else
|
||||
# Parse color only - alternative syntax
|
||||
# Check if we accidentally have a tuple in alternative syntax mode
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.LEFT_PAREN
|
||||
self.error("Cannot mix tuple syntax (value, color) with alternative syntax [color1, color2, ...]. Use only one syntax per palette.")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
var color = self.process_value("color") # Reuse existing color parsing
|
||||
|
||||
# Convert to VRGB format entry and store as integer after setting alpha to 0xFF
|
||||
var vrgb_entry = self.convert_to_vrgb(0xFF, color)
|
||||
var vrgb_int = int(f"0x{vrgb_entry}")
|
||||
palette_entries.push(vrgb_int)
|
||||
end
|
||||
|
||||
# Skip whitespace but preserve newlines for separator detection
|
||||
while !self.at_end()
|
||||
@ -288,13 +339,15 @@ class SimpleDSLTranspiler
|
||||
self.expect_right_bracket()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Generate Berry bytes object
|
||||
# Generate Berry bytes object - convert integers to hex strings for bytes() constructor
|
||||
var palette_data = ""
|
||||
for i : 0..size(palette_entries)-1
|
||||
if i > 0
|
||||
palette_data += " "
|
||||
end
|
||||
palette_data += palette_entries[i]
|
||||
# Convert integer back to hex string for bytes() constructor
|
||||
var hex_str = format("%08X", palette_entries[i])
|
||||
palette_data += f'"{hex_str}"'
|
||||
end
|
||||
|
||||
self.add(f"var {name}_ = bytes({palette_data}){inline_comment}")
|
||||
@ -410,9 +463,13 @@ class SimpleDSLTranspiler
|
||||
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"
|
||||
end
|
||||
|
||||
# Process sequence definition: sequence demo { ... }
|
||||
# Process sequence definition: sequence demo { ... } or sequence demo repeat N times { ... }
|
||||
def process_sequence()
|
||||
self.next() # skip 'sequence'
|
||||
var name = self.expect_identifier()
|
||||
@ -430,26 +487,67 @@ class SimpleDSLTranspiler
|
||||
# We use a string marker since sequences don't have real instances
|
||||
self.symbol_table[name] = "sequence"
|
||||
|
||||
self.expect_left_brace()
|
||||
# Check for second syntax: sequence name repeat N times { ... } or sequence name forever { ... }
|
||||
var is_repeat_syntax = false
|
||||
var repeat_count = "1"
|
||||
|
||||
# Generate anonymous closure that creates and returns sequence manager
|
||||
self.add(f"var {name}_ = (def (engine)")
|
||||
self.add(f" var steps = []")
|
||||
|
||||
# Process sequence body
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.process_sequence_statement()
|
||||
var current_tok = self.current()
|
||||
if current_tok != nil && current_tok.type == animation_dsl.Token.KEYWORD
|
||||
if current_tok.value == "repeat"
|
||||
is_repeat_syntax = true
|
||||
self.next() # skip 'repeat'
|
||||
|
||||
# Parse repeat count: either number or "forever"
|
||||
var tok_after_repeat = self.current()
|
||||
if tok_after_repeat != nil && tok_after_repeat.type == animation_dsl.Token.KEYWORD && tok_after_repeat.value == "forever"
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
else
|
||||
var count = self.expect_number()
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
end
|
||||
elif current_tok.value == "forever"
|
||||
# New syntax: sequence name forever { ... } (repeat is optional)
|
||||
is_repeat_syntax = true
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
end
|
||||
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()
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
end
|
||||
|
||||
self.expect_left_brace()
|
||||
|
||||
if is_repeat_syntax
|
||||
# Second syntax: sequence name repeat N times { ... }
|
||||
# Create a single SequenceManager with fluent interface
|
||||
self.add(f"var {name}_ = animation.SequenceManager(engine, {repeat_count})")
|
||||
|
||||
# Process sequence body - add steps using fluent interface
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.process_sequence_statement()
|
||||
end
|
||||
else
|
||||
# First syntax: sequence demo { ... }
|
||||
# Use fluent interface for regular sequences too (no repeat count = default)
|
||||
self.add(f"var {name}_ = animation.SequenceManager(engine)")
|
||||
|
||||
# Process sequence body - add steps using fluent interface
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.process_sequence_statement()
|
||||
end
|
||||
end
|
||||
|
||||
self.add(f" var seq_manager = animation.SequenceManager(engine)")
|
||||
self.add(f" seq_manager.start_sequence(steps)")
|
||||
self.add(f" return seq_manager")
|
||||
self.add(f"end)(engine)")
|
||||
self.expect_right_brace()
|
||||
end
|
||||
|
||||
# Process statements inside sequences
|
||||
def process_sequence_statement()
|
||||
# Process statements inside sequences using push_step()
|
||||
def process_sequence_statement_for_manager(manager_name)
|
||||
var tok = self.current()
|
||||
if tok == nil || tok.type == animation_dsl.Token.EOF
|
||||
return
|
||||
@ -469,109 +567,321 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
|
||||
if tok.type == animation_dsl.Token.KEYWORD && tok.value == "play"
|
||||
self.next() # skip 'play'
|
||||
|
||||
# Check if this is a function call or an identifier
|
||||
var anim_ref = ""
|
||||
var current_tok = self.current()
|
||||
if current_tok != nil && (current_tok.type == animation_dsl.Token.IDENTIFIER || current_tok.type == animation_dsl.Token.KEYWORD) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
|
||||
# Handle optional 'for duration'
|
||||
var duration = "0"
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.KEYWORD && self.current().value == "for"
|
||||
self.next() # skip 'for'
|
||||
duration = str(self.process_time_value())
|
||||
end
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" steps.push(animation.create_play_step({anim_ref}, {duration})){inline_comment}")
|
||||
self.process_play_statement_for_manager(manager_name)
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "wait"
|
||||
self.next() # skip 'wait'
|
||||
var duration = self.process_time_value()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" steps.push(animation.create_wait_step({duration})){inline_comment}")
|
||||
self.process_wait_statement_for_manager(manager_name)
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "repeat"
|
||||
self.next() # skip 'repeat'
|
||||
var count = self.expect_number()
|
||||
self.expect_keyword("times")
|
||||
self.expect_colon()
|
||||
|
||||
self.add(f" for repeat_i : 0..{count}-1")
|
||||
|
||||
# Process repeat body
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
var inner_tok = self.current()
|
||||
if inner_tok == nil || inner_tok.type == animation_dsl.Token.EOF
|
||||
break
|
||||
end
|
||||
if inner_tok.type == animation_dsl.Token.COMMENT
|
||||
self.add(" " + inner_tok.value) # Add comment with repeat body indentation
|
||||
self.next()
|
||||
continue
|
||||
end
|
||||
if inner_tok.type == animation_dsl.Token.NEWLINE
|
||||
self.next()
|
||||
continue
|
||||
end
|
||||
if inner_tok.type == animation_dsl.Token.KEYWORD && inner_tok.value == "play"
|
||||
self.next() # skip 'play'
|
||||
|
||||
# Check if this is a function call or an identifier
|
||||
var anim_ref = ""
|
||||
var current_tok = self.current()
|
||||
if current_tok != nil && (current_tok.type == animation_dsl.Token.IDENTIFIER || current_tok.type == animation_dsl.Token.KEYWORD) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
|
||||
var duration = "0"
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.KEYWORD && self.current().value == "for"
|
||||
self.next() # skip 'for'
|
||||
duration = str(self.process_time_value())
|
||||
end
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" steps.push(animation.create_play_step({anim_ref}, {duration})){inline_comment}")
|
||||
|
||||
elif inner_tok.type == animation_dsl.Token.KEYWORD && inner_tok.value == "wait"
|
||||
self.next() # skip 'wait'
|
||||
var duration = self.process_time_value()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" steps.push(animation.create_wait_step({duration})){inline_comment}")
|
||||
else
|
||||
break # Exit repeat body
|
||||
end
|
||||
# Parse repeat count: either number or "forever"
|
||||
var repeat_count = "1"
|
||||
var tok_after_repeat = self.current()
|
||||
if tok_after_repeat != nil && tok_after_repeat.type == animation_dsl.Token.KEYWORD && tok_after_repeat.value == "forever"
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
else
|
||||
var count = self.expect_number()
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
end
|
||||
|
||||
self.add(f" end")
|
||||
self.expect_left_brace()
|
||||
|
||||
# Create repeat sub-sequence
|
||||
self.add(f" var repeat_seq = animation.SequenceManager(engine, {repeat_count})")
|
||||
|
||||
# Process repeat body - add steps directly to repeat sequence
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.process_sequence_statement_for_manager("repeat_seq")
|
||||
end
|
||||
|
||||
self.expect_right_brace()
|
||||
|
||||
# Add the repeat sub-sequence step to main sequence
|
||||
self.add(f" {manager_name}.push_repeat_subsequence(repeat_seq.steps, {repeat_count})")
|
||||
elif tok.type == animation_dsl.Token.IDENTIFIER
|
||||
# Check if this is a property assignment (identifier.property = value)
|
||||
if self.peek() != nil && self.peek().type == animation_dsl.Token.DOT
|
||||
self.process_sequence_assignment_for_manager(" ", manager_name) # Pass indentation and manager name
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Process statements inside sequences using fluent interface
|
||||
def process_sequence_statement()
|
||||
var tok = self.current()
|
||||
if tok == nil || tok.type == animation_dsl.Token.EOF
|
||||
return
|
||||
end
|
||||
|
||||
# Handle comments - preserve them in generated code with proper indentation
|
||||
if tok.type == animation_dsl.Token.COMMENT
|
||||
self.add(self.get_indent() + tok.value) # Add comment with fluent indentation
|
||||
self.next()
|
||||
return
|
||||
end
|
||||
|
||||
# Skip whitespace (newlines)
|
||||
if tok.type == animation_dsl.Token.NEWLINE
|
||||
self.next()
|
||||
return
|
||||
end
|
||||
|
||||
if tok.type == animation_dsl.Token.KEYWORD && tok.value == "play"
|
||||
self.process_play_statement_fluent()
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "wait"
|
||||
self.process_wait_statement_fluent()
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "repeat"
|
||||
self.next() # skip 'repeat'
|
||||
|
||||
# Parse repeat count: either number or "forever"
|
||||
var repeat_count = "1"
|
||||
var tok_after_repeat = self.current()
|
||||
if tok_after_repeat != nil && tok_after_repeat.type == animation_dsl.Token.KEYWORD && tok_after_repeat.value == "forever"
|
||||
self.next() # skip 'forever'
|
||||
repeat_count = "-1" # -1 means forever
|
||||
else
|
||||
var count = self.expect_number()
|
||||
self.expect_keyword("times")
|
||||
repeat_count = str(count)
|
||||
end
|
||||
|
||||
self.expect_left_brace()
|
||||
|
||||
# Create a nested sub-sequence using recursive processing
|
||||
self.add(f"{self.get_indent()}.push_repeat_subsequence(animation.SequenceManager(engine, {repeat_count})")
|
||||
|
||||
# Increase indentation level for nested content
|
||||
self.indent_level += 1
|
||||
|
||||
# Process repeat body recursively - just call the same method
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.process_sequence_statement()
|
||||
end
|
||||
|
||||
self.expect_right_brace()
|
||||
|
||||
# Decrease indentation level and close the sub-sequence
|
||||
self.add(f"{self.get_indent()})")
|
||||
self.indent_level -= 1
|
||||
|
||||
elif tok.type == animation_dsl.Token.IDENTIFIER
|
||||
# Check if this is a property assignment (identifier.property = value)
|
||||
if self.peek() != nil && self.peek().type == animation_dsl.Token.DOT
|
||||
self.process_sequence_assignment_fluent()
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
end
|
||||
|
||||
# Process property assignment using fluent style
|
||||
def process_sequence_assignment_fluent()
|
||||
var object_name = self.expect_identifier()
|
||||
self.expect_dot()
|
||||
var property_name = self.expect_identifier()
|
||||
self.expect_assign()
|
||||
var value = self.process_value("property")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# 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}")
|
||||
end
|
||||
|
||||
# Process property assignment inside sequences: object.property = value (legacy)
|
||||
def process_sequence_assignment(indent)
|
||||
self.process_sequence_assignment_generic(indent, "steps")
|
||||
end
|
||||
|
||||
# Generic method to process sequence assignment with configurable target array
|
||||
def process_sequence_assignment_generic(indent, target_array)
|
||||
var object_name = self.expect_identifier()
|
||||
|
||||
# Check if next token is a dot
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.DOT
|
||||
self.next() # skip '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Validate parameter if we have this object in our symbol table
|
||||
if self.symbol_table.contains(object_name)
|
||||
var instance = self.symbol_table[object_name]
|
||||
|
||||
# Only validate parameters for actual instances, not sequence markers
|
||||
if type(instance) != "string"
|
||||
var class_name = classname(instance)
|
||||
|
||||
# Use the existing parameter validation logic
|
||||
self._validate_single_parameter(class_name, property_name, instance)
|
||||
else
|
||||
# This is a sequence marker - sequences don't have properties
|
||||
self.error(f"Sequences like '{object_name}' do not have properties. Property assignments are only valid for animations and color providers.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
self.expect_assign()
|
||||
var value = self.process_value("property")
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
|
||||
# Generate assignment step with closure
|
||||
# The closure receives the engine as parameter and performs the assignment
|
||||
import introspect
|
||||
var object_ref = ""
|
||||
if introspect.contains(animation, object_name)
|
||||
# Symbol exists in animation module, use it directly
|
||||
object_ref = f"animation.{object_name}"
|
||||
else
|
||||
# Symbol doesn't exist in animation module, use underscore suffix
|
||||
object_ref = f"{object_name}_"
|
||||
end
|
||||
|
||||
# Create closure that performs the assignment
|
||||
var closure_code = f"def (engine) {object_ref}.{property_name} = {value} end"
|
||||
self.add(f"{indent}{target_array}.push(animation.create_assign_step({closure_code})){inline_comment}")
|
||||
else
|
||||
# Not a property assignment, this shouldn't happen since we checked for dot
|
||||
self.error(f"Expected property assignment for '{object_name}' but found no dot")
|
||||
self.skip_statement()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Helper method to process play statement using fluent style
|
||||
def process_play_statement_fluent()
|
||||
self.next() # skip 'play'
|
||||
|
||||
# Check if this is a function call or an identifier
|
||||
var anim_ref = ""
|
||||
var current_tok = self.current()
|
||||
if current_tok != nil && (current_tok.type == animation_dsl.Token.IDENTIFIER || current_tok.type == animation_dsl.Token.KEYWORD) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"{anim_name}_"
|
||||
end
|
||||
|
||||
# Handle optional 'for duration'
|
||||
var duration = "nil"
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.KEYWORD && self.current().value == "for"
|
||||
self.next() # skip 'for'
|
||||
duration = str(self.process_time_value())
|
||||
end
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"{self.get_indent()}.push_play_step({anim_ref}, {duration}){inline_comment}")
|
||||
end
|
||||
|
||||
# Helper method to process wait statement using fluent style
|
||||
def process_wait_statement_fluent()
|
||||
self.next() # skip 'wait'
|
||||
var duration = self.process_time_value()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"{self.get_indent()}.push_wait_step({duration}){inline_comment}")
|
||||
end
|
||||
|
||||
# Helper method to process play statement with configurable target array (legacy)
|
||||
def process_play_statement(target_array)
|
||||
self.next() # skip 'play'
|
||||
|
||||
# Check if this is a function call or an identifier
|
||||
var anim_ref = ""
|
||||
var current_tok = self.current()
|
||||
if current_tok != nil && (current_tok.type == animation_dsl.Token.IDENTIFIER || current_tok.type == animation_dsl.Token.KEYWORD) &&
|
||||
self.peek() != nil && self.peek().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a function call - process it as a nested function call
|
||||
anim_ref = self.process_nested_function_call()
|
||||
else
|
||||
# This is an identifier reference - sequences need runtime resolution
|
||||
var anim_name = self.expect_identifier()
|
||||
|
||||
# Validate that the referenced object exists
|
||||
self._validate_object_reference(anim_name, "sequence play")
|
||||
|
||||
anim_ref = f"animation.global('{anim_name}_')"
|
||||
end
|
||||
|
||||
# Handle optional 'for duration'
|
||||
var duration = "0"
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.KEYWORD && self.current().value == "for"
|
||||
self.next() # skip 'for'
|
||||
duration = str(self.process_time_value())
|
||||
end
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" {target_array}.push(animation.create_play_step({anim_ref}, {duration})){inline_comment}")
|
||||
end
|
||||
|
||||
# Helper method to process wait statement with configurable target array (legacy)
|
||||
def process_wait_statement(target_array)
|
||||
self.next() # skip 'wait'
|
||||
var duration = self.process_time_value()
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" {target_array}.push(animation.create_wait_step({duration})){inline_comment}")
|
||||
end
|
||||
|
||||
# Generic method to process sequence statements with configurable target array
|
||||
def process_sequence_statement_generic(target_array)
|
||||
var tok = self.current()
|
||||
if tok == nil || tok.type == animation_dsl.Token.EOF
|
||||
return
|
||||
end
|
||||
|
||||
# Handle comments - preserve them in generated code with proper indentation
|
||||
if tok.type == animation_dsl.Token.COMMENT
|
||||
self.add(" " + tok.value) # Add comment with sequence indentation
|
||||
self.next()
|
||||
return
|
||||
end
|
||||
|
||||
# Skip whitespace (newlines)
|
||||
if tok.type == animation_dsl.Token.NEWLINE
|
||||
self.next()
|
||||
return
|
||||
end
|
||||
|
||||
if tok.type == animation_dsl.Token.KEYWORD && tok.value == "play"
|
||||
self.process_play_statement(target_array)
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "wait"
|
||||
self.process_wait_statement(target_array)
|
||||
|
||||
elif tok.type == animation_dsl.Token.IDENTIFIER
|
||||
# Check if this is a property assignment (identifier.property = value)
|
||||
if self.peek() != nil && self.peek().type == animation_dsl.Token.DOT
|
||||
self.process_sequence_assignment_generic(" ", target_array) # Pass indentation and target array
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
else
|
||||
self.skip_statement()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Process run statement: run demo
|
||||
def process_run()
|
||||
self.next() # skip 'run'
|
||||
@ -640,11 +950,6 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Process any value - unified approach
|
||||
def process_value(context)
|
||||
return self.process_expression(context) # This calls process_additive_expression with is_top_level=true
|
||||
end
|
||||
|
||||
# Process expressions with arithmetic operations
|
||||
def process_expression(context)
|
||||
return self.process_additive_expression(context, true) # true = top-level expression
|
||||
end
|
||||
|
||||
@ -1188,6 +1493,15 @@ class SimpleDSLTranspiler
|
||||
var num = tok.value
|
||||
self.next()
|
||||
return int(real(num)) * 1000 # assume seconds
|
||||
elif tok != nil && tok.type == animation_dsl.Token.IDENTIFIER
|
||||
# Handle variable references for time values
|
||||
var var_name = tok.value
|
||||
|
||||
# Validate that the variable exists before processing
|
||||
self._validate_object_reference(var_name, "duration")
|
||||
|
||||
var expr = self.process_primary_expression("time", true)
|
||||
return expr
|
||||
else
|
||||
self.error("Expected time value")
|
||||
return 1000
|
||||
@ -1779,6 +2093,15 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
def expect_dot()
|
||||
var tok = self.current()
|
||||
if tok != nil && tok.type == animation_dsl.Token.DOT
|
||||
self.next()
|
||||
else
|
||||
self.error("Expected '.'")
|
||||
end
|
||||
end
|
||||
|
||||
def expect_left_bracket()
|
||||
var tok = self.current()
|
||||
if tok != nil && tok.type == animation_dsl.Token.LEFT_BRACKET
|
||||
|
||||
@ -19,7 +19,13 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
|
||||
# Parameter definitions
|
||||
static var PARAMS = {
|
||||
"palette": {"default": [0xFF0000FF, 0xFF00FF00, 0xFFFF0000], "type": "instance"}, # Default RGB palette
|
||||
"palette": {"type": "bytes", "default":
|
||||
bytes( # Palette bytes in AARRGGBB format
|
||||
"FF0000FF" # Blue
|
||||
"FF00FF00" # Green
|
||||
"FFFF0000" # Red
|
||||
)
|
||||
},
|
||||
"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
|
||||
}
|
||||
@ -31,11 +37,46 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
super(self).init(engine) # Initialize parameter system
|
||||
|
||||
# Initialize non-parameter instance variables
|
||||
var default_palette = self.palette # Get default palette
|
||||
self.current_color = default_palette[0] # Start with first color in palette
|
||||
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
|
||||
end
|
||||
|
||||
# Get palette bytes from parameter with default fallback
|
||||
def _get_palette_bytes()
|
||||
var palette_bytes = self.palette
|
||||
if palette_bytes == nil
|
||||
# Get default from PARAMS
|
||||
var param_def = self._get_param_def("palette")
|
||||
if param_def != nil && param_def.contains("default")
|
||||
palette_bytes = param_def["default"]
|
||||
end
|
||||
end
|
||||
return palette_bytes
|
||||
end
|
||||
|
||||
# Get color at a specific index from bytes palette
|
||||
# We force alpha channel to 0xFF to force opaque colors
|
||||
def _get_color_at_index(idx)
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
var palette_size = size(palette_bytes) / 4 # Each color is 4 bytes (AARRGGBB)
|
||||
|
||||
if palette_size == 0 || idx < 0 || idx >= palette_size
|
||||
return 0xFFFFFFFF # Default to white
|
||||
end
|
||||
|
||||
# Read 4 bytes in big-endian format (AARRGGBB)
|
||||
var color = palette_bytes.get(idx * 4, -4) # Big endian
|
||||
color = color | 0xFF000000
|
||||
return color
|
||||
end
|
||||
|
||||
# Get the number of colors in the palette
|
||||
def _get_palette_size()
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
return size(palette_bytes) / 4 # Each color is 4 bytes
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
#
|
||||
# @param name: string - Name of the parameter that changed
|
||||
@ -43,20 +84,24 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
def on_param_changed(name, value)
|
||||
if name == "palette"
|
||||
# When palette changes, update current_color if current_index is valid
|
||||
var palette = value
|
||||
if size(palette) > 0
|
||||
var palette_size = self._get_palette_size()
|
||||
if palette_size > 0
|
||||
# Clamp current_index to valid range
|
||||
if self.current_index >= size(palette)
|
||||
if self.current_index >= palette_size
|
||||
self.current_index = 0
|
||||
end
|
||||
self.current_color = palette[self.current_index]
|
||||
self.current_color = self._get_color_at_index(self.current_index)
|
||||
end
|
||||
elif name == "next" && value == 1
|
||||
# Move to next color in palette
|
||||
var palette = self.palette
|
||||
if size(palette) > 0
|
||||
self.current_index = (self.current_index + 1) % size(palette)
|
||||
self.current_color = palette[self.current_index]
|
||||
elif name == "next" && value != 0
|
||||
# Add to color index
|
||||
var palette_size = self._get_palette_size()
|
||||
if palette_size > 0
|
||||
var current_index = (self.current_index + value) % palette_size
|
||||
if current_index < 0
|
||||
current_index += palette_size
|
||||
end
|
||||
self.current_index = current_index
|
||||
self.current_color = self._get_color_at_index(self.current_index)
|
||||
end
|
||||
# Reset the next parameter back to 0
|
||||
self.set_param("next", 0)
|
||||
@ -70,18 +115,17 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def produce_value(name, time_ms)
|
||||
# Get parameter values using virtual member access
|
||||
var palette = self.palette
|
||||
var cycle_period = self.cycle_period
|
||||
|
||||
# Get the number of colors in the palette
|
||||
var palette_size = size(palette)
|
||||
var palette_size = self._get_palette_size()
|
||||
if palette_size == 0
|
||||
return 0xFFFFFFFF # Default to white if no colors
|
||||
end
|
||||
|
||||
if palette_size == 1
|
||||
# If only one color, just return it
|
||||
self.current_color = palette[0]
|
||||
self.current_color = self._get_color_at_index(0)
|
||||
return self.current_color
|
||||
end
|
||||
|
||||
@ -103,7 +147,7 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
|
||||
# Update current state and return the color
|
||||
self.current_index = color_index
|
||||
self.current_color = palette[color_index]
|
||||
self.current_color = self._get_color_at_index(color_index)
|
||||
|
||||
return self.current_color
|
||||
end
|
||||
@ -115,17 +159,14 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
# @param time_ms: int - Current time in milliseconds (ignored for value-based color)
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def get_color_for_value(value, time_ms)
|
||||
# Get parameter values using virtual member access
|
||||
var palette = self.palette
|
||||
|
||||
# Get the number of colors in the palette
|
||||
var palette_size = size(palette)
|
||||
var palette_size = self._get_palette_size()
|
||||
if palette_size == 0
|
||||
return 0xFFFFFFFF # Default to white if no colors
|
||||
end
|
||||
|
||||
if palette_size == 1
|
||||
return palette[0] # If only one color, just return it
|
||||
return self._get_color_at_index(0) # If only one color, just return it
|
||||
end
|
||||
|
||||
# Clamp value to 0-100
|
||||
@ -143,108 +184,24 @@ class ColorCycleColorProvider : animation.color_provider
|
||||
color_index = palette_size - 1
|
||||
end
|
||||
|
||||
return palette[color_index]
|
||||
return self._get_color_at_index(color_index)
|
||||
end
|
||||
|
||||
|
||||
|
||||
# Add a color to the palette
|
||||
#
|
||||
# @param color: int - Color to add (32-bit ARGB value)
|
||||
# @return self for method chaining
|
||||
def add_color(color)
|
||||
var current_palette = self.palette
|
||||
var new_palette = current_palette.copy()
|
||||
new_palette.push(color)
|
||||
self.palette = new_palette
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
# String representation of the provider
|
||||
def tostring()
|
||||
try
|
||||
var mode = self.cycle_period == 0 ? "manual" : "auto"
|
||||
return f"ColorCycleColorProvider(palette_size={size(self.palette)}, cycle_period={self.cycle_period}, mode={mode}, current_index={self.current_index})"
|
||||
var palette_size = self._get_palette_size()
|
||||
return f"ColorCycleColorProvider(palette_size={palette_size}, cycle_period={self.cycle_period}, mode={mode}, current_index={self.current_index})"
|
||||
except ..
|
||||
return "ColorCycleColorProvider(uninitialized)"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Factory function for custom palette
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine reference
|
||||
# @param palette: list - List of colors to cycle through (32-bit ARGB values)
|
||||
# @param cycle_period: int - Time for one complete cycle in milliseconds
|
||||
# @return ColorCycleColorProvider - A new color cycle color provider instance
|
||||
def color_cycle_from_palette(engine, palette, cycle_period)
|
||||
var provider = animation.color_cycle(engine)
|
||||
if palette != nil
|
||||
provider.palette = palette
|
||||
end
|
||||
if cycle_period != nil
|
||||
provider.cycle_period = cycle_period
|
||||
end
|
||||
return provider
|
||||
end
|
||||
|
||||
# Factory function for rainbow palette
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine reference
|
||||
# @param num_colors: int - Number of colors in the rainbow (default: 6)
|
||||
# @param cycle_period: int - Time for one complete cycle in milliseconds
|
||||
# @return ColorCycleColorProvider - A new color cycle color provider instance
|
||||
def color_cycle_rainbow(engine, num_colors, cycle_period)
|
||||
# Default parameters
|
||||
if num_colors == nil || num_colors < 2
|
||||
num_colors = 6
|
||||
end
|
||||
|
||||
# Create a rainbow palette
|
||||
var palette = []
|
||||
var i = 0
|
||||
while i < num_colors
|
||||
# Calculate hue (0 to 360 degrees)
|
||||
var hue = tasmota.scale_uint(i, 0, num_colors, 0, 360)
|
||||
|
||||
# Convert HSV to RGB (simplified conversion)
|
||||
var r, g, b
|
||||
var h_section = (hue / 60) % 6
|
||||
var f = (hue / 60) - h_section
|
||||
var v = 255 # Value (brightness)
|
||||
var p = 0 # Saturation is 100%, so p = 0
|
||||
var q = int(v * (1 - f))
|
||||
var t = int(v * f)
|
||||
|
||||
if h_section == 0
|
||||
r = v; g = t; b = p
|
||||
elif h_section == 1
|
||||
r = q; g = v; b = p
|
||||
elif h_section == 2
|
||||
r = p; g = v; b = t
|
||||
elif h_section == 3
|
||||
r = p; g = q; b = v
|
||||
elif h_section == 4
|
||||
r = t; g = p; b = v
|
||||
else
|
||||
r = v; g = p; b = q
|
||||
end
|
||||
|
||||
# Create ARGB color (fully opaque)
|
||||
var color = (255 << 24) | (r << 16) | (g << 8) | b
|
||||
palette.push(color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Create and return a new color cycle color provider with the rainbow palette
|
||||
var provider = animation.color_cycle(engine)
|
||||
provider.palette = palette
|
||||
if cycle_period != nil
|
||||
provider.cycle_period = cycle_period
|
||||
end
|
||||
return provider
|
||||
end
|
||||
|
||||
return {'color_cycle': ColorCycleColorProvider,
|
||||
'color_cycle_from_palette': color_cycle_from_palette,
|
||||
'color_cycle_rainbow': color_cycle_rainbow}
|
||||
return {'color_cycle': ColorCycleColorProvider}
|
||||
@ -18,7 +18,7 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
|
||||
# Parameter definitions
|
||||
static var PARAMS = {
|
||||
"palette": {"type": "instance", "default": nil}, # Palette bytes or predefined palette constant
|
||||
"palette": {"type": "bytes", "default": nil}, # Palette bytes or predefined palette constant
|
||||
"cycle_period": {"min": 0, "default": 5000}, # 5 seconds default, 0 = value-based only
|
||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
||||
"brightness": {"min": 0, "max": 255, "default": 255},
|
||||
@ -91,26 +91,18 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# Get palette bytes from parameter with default fallback
|
||||
def _get_palette_bytes()
|
||||
var palette_bytes = self.palette
|
||||
|
||||
if palette_bytes == nil
|
||||
# Default rainbow palette (reusing format from Animate_palette)
|
||||
palette_bytes = bytes(
|
||||
"00FF0000" # Red (value 0)
|
||||
"24FFA500" # Orange (value 36)
|
||||
"49FFFF00" # Yellow (value 73)
|
||||
"6E00FF00" # Green (value 110)
|
||||
"920000FF" # Blue (value 146)
|
||||
"B74B0082" # Indigo (value 183)
|
||||
"DBEE82EE" # Violet (value 219)
|
||||
"FFFF0000" # Red (value 255)
|
||||
)
|
||||
end
|
||||
|
||||
# Convert comptr to palette buffer if needed (from Animate_palette)
|
||||
if type(palette_bytes) == 'ptr' palette_bytes = self._ptr_to_palette(palette_bytes) end
|
||||
|
||||
return palette_bytes
|
||||
return (palette_bytes != nil) ? palette_bytes : self._DEFAULT_PALETTE
|
||||
end
|
||||
static _DEFAULT_PALETTE = bytes(
|
||||
"00FF0000" # Red (value 0)
|
||||
"24FFA500" # Orange (value 36)
|
||||
"49FFFF00" # Yellow (value 73)
|
||||
"6E00FF00" # Green (value 110)
|
||||
"920000FF" # Blue (value 146)
|
||||
"B74B0082" # Indigo (value 183)
|
||||
"DBEE82EE" # Violet (value 219)
|
||||
"FFFF0000" # Red (value 255)
|
||||
)
|
||||
|
||||
# Recompute palette slots and metadata
|
||||
def _recompute_palette()
|
||||
@ -136,33 +128,6 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
return self
|
||||
end
|
||||
|
||||
# Convert comptr to bytes (reused from Animate_palette.ptr_to_palette)
|
||||
def _ptr_to_palette(p)
|
||||
if type(p) == 'ptr'
|
||||
var b_raw = bytes(p, 2000) # arbitrary large size
|
||||
var idx = 1
|
||||
if b_raw[0] != 0
|
||||
# palette in tick counts
|
||||
while true
|
||||
if b_raw[idx * 4] == 0
|
||||
break
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
else
|
||||
# palette is in value range from 0..255
|
||||
while true
|
||||
if b_raw[idx * 4] == 0xFF
|
||||
break
|
||||
end
|
||||
idx += 1
|
||||
end
|
||||
end
|
||||
var sz = (idx + 1) * 4
|
||||
return bytes(p, sz)
|
||||
end
|
||||
end
|
||||
|
||||
# Parse the palette and create slots array (reused from Animate_palette)
|
||||
#
|
||||
# @param min: int - Minimum value for the range
|
||||
@ -212,12 +177,9 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
end
|
||||
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
var bgrt = palette_bytes.get(idx * 4, 4)
|
||||
var r = (bgrt >> 8) & 0xFF
|
||||
var g = (bgrt >> 16) & 0xFF
|
||||
var b = (bgrt >> 24) & 0xFF
|
||||
|
||||
return (0xFF << 24) | (r << 16) | (g << 8) | b
|
||||
var trgb = palette_bytes.get(idx * 4, -4) # Big Endian
|
||||
trgb = trgb | 0xFF000000 # set alpha channel to full opaque
|
||||
return trgb
|
||||
end
|
||||
|
||||
# Produce a color value for any parameter name (optimized version from Animate_palette)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
92
lib/libesp32/berry_animation/src/tests/bytes_type_test.be
Normal file
92
lib/libesp32/berry_animation/src/tests/bytes_type_test.be
Normal file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env berry
|
||||
|
||||
# Test for bytes type validation in parameterized_object.be
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
# Test class that uses bytes parameter
|
||||
class BytesTestClass : animation.parameterized_object
|
||||
static var PARAMS = {
|
||||
"data": {"type": "bytes", "default": nil, "nillable": true},
|
||||
"required_data": {"type": "bytes"},
|
||||
"name": {"type": "string", "default": "test"}
|
||||
}
|
||||
|
||||
def init(engine)
|
||||
super(self).init(engine)
|
||||
end
|
||||
end
|
||||
|
||||
# Mock engine for testing
|
||||
class MockEngine
|
||||
var time_ms
|
||||
def init()
|
||||
self.time_ms = 1000
|
||||
end
|
||||
end
|
||||
|
||||
def test_bytes_type_validation()
|
||||
print("Testing bytes type validation...")
|
||||
|
||||
var mock_engine = MockEngine()
|
||||
var obj = BytesTestClass(mock_engine)
|
||||
|
||||
# Test 1: Valid bytes objects
|
||||
var hex_bytes = bytes("AABBCC")
|
||||
obj.data = hex_bytes
|
||||
assert(obj.data.tohex() == "AABBCC", "Hex bytes should be accepted")
|
||||
|
||||
var empty_bytes = bytes()
|
||||
obj.data = empty_bytes
|
||||
assert(obj.data != nil, "Empty bytes should be accepted")
|
||||
|
||||
var sized_bytes = bytes(5)
|
||||
obj.data = sized_bytes
|
||||
assert(obj.data != nil, "Sized bytes should be accepted")
|
||||
|
||||
# Test 2: Invalid types should be rejected
|
||||
var invalid_types = ["string", 123, 3.14, true, [], {}]
|
||||
|
||||
for invalid_val : invalid_types
|
||||
var caught_error = false
|
||||
try
|
||||
obj.data = invalid_val
|
||||
except "value_error"
|
||||
caught_error = true
|
||||
end
|
||||
assert(caught_error, f"Should reject {type(invalid_val)}: {invalid_val}")
|
||||
end
|
||||
|
||||
# Test 3: Nil handling
|
||||
obj.data = nil # Should work for nillable parameter
|
||||
assert(obj.data == nil, "Nil should be accepted for nillable parameter")
|
||||
|
||||
var nil_error = false
|
||||
try
|
||||
obj.required_data = nil # Should fail for non-nillable parameter
|
||||
except "value_error"
|
||||
nil_error = true
|
||||
end
|
||||
assert(nil_error, "Should reject nil for non-nillable parameter")
|
||||
|
||||
# Test 4: Method-based setting
|
||||
var success = obj.set_param("data", bytes("112233"))
|
||||
assert(success == true, "Method setting with valid bytes should succeed")
|
||||
|
||||
success = obj.set_param("data", "invalid")
|
||||
assert(success == false, "Method setting with invalid type should fail")
|
||||
|
||||
# Test 5: Parameter metadata
|
||||
var metadata = obj.get_param_metadata("data")
|
||||
assert(metadata["type"] == "bytes", "Data parameter should have bytes type")
|
||||
assert(metadata["nillable"] == true, "Data parameter should be nillable")
|
||||
|
||||
var req_metadata = obj.get_param_metadata("required_data")
|
||||
assert(req_metadata["type"] == "bytes", "Required data should have bytes type")
|
||||
assert(req_metadata.find("nillable", false) == false, "Required data should not be nillable")
|
||||
|
||||
print("✓ All bytes type validation tests passed!")
|
||||
end
|
||||
|
||||
# Run the test
|
||||
test_bytes_type_validation()
|
||||
@ -69,7 +69,7 @@ class ColorCycleAnimationTest
|
||||
self.assert_equal(color_provider.palette != nil, true, "Color provider has palette property")
|
||||
|
||||
# Test with custom parameters
|
||||
var custom_palette = [0xFFFF0000, 0xFF00FF00] # Red and Green in ARGB format
|
||||
var custom_palette = bytes("FFFF0000FF00FF00") # Red and Green in AARRGGBB format
|
||||
var custom_provider = animation.color_cycle(engine)
|
||||
custom_provider.palette = custom_palette
|
||||
custom_provider.cycle_period = 2000
|
||||
@ -88,13 +88,13 @@ class ColorCycleAnimationTest
|
||||
self.assert_equal(color_provider2.palette != nil, true, "Custom color provider has palette property")
|
||||
|
||||
# Check provider properties
|
||||
self.assert_equal(size(color_provider2.palette), 2, "Custom palette has 2 colors")
|
||||
self.assert_equal(color_provider2._get_palette_size(), 2, "Custom palette has 2 colors")
|
||||
self.assert_equal(color_provider2.cycle_period, 2000, "Custom cycle period is 2000ms")
|
||||
end
|
||||
|
||||
def test_update_and_render()
|
||||
# Create animation with red and blue colors
|
||||
var palette = [0xFFFF0000, 0xFF0000FF] # Red and Blue in ARGB format (Alpha, Red, Green, Blue)
|
||||
var palette = bytes("FFFF0000FF0000FF") # Red and Blue in AARRGGBB format
|
||||
var provider = animation.color_cycle(engine)
|
||||
provider.palette = palette
|
||||
provider.cycle_period = 1000 # 1 second cycle
|
||||
@ -145,7 +145,7 @@ class ColorCycleAnimationTest
|
||||
|
||||
# Create animation with manual-only color provider
|
||||
var manual_provider = animation.color_cycle(engine)
|
||||
manual_provider.palette = [0xFF0000FF, 0xFF00FF00, 0xFFFF0000] # Red, Green, Blue
|
||||
manual_provider.palette = bytes("FF0000FFFF00FF00FFFF0000") # Blue, Green, Red in AARRGGBB format
|
||||
manual_provider.cycle_period = 0 # Manual-only mode
|
||||
|
||||
var manual_anim = animation.solid(engine)
|
||||
@ -195,7 +195,7 @@ class ColorCycleAnimationTest
|
||||
def test_direct_creation()
|
||||
# Test direct creation without factory method (following new parameterized pattern)
|
||||
var provider = animation.color_cycle(engine)
|
||||
provider.palette = [0xFF0000FF, 0xFF00FF00, 0xFFFF0000] # RGB colors
|
||||
provider.palette = bytes("FF0000FFFF00FF00FFFF0000") # Blue, Green, Red in AARRGGBB format
|
||||
provider.cycle_period = 3000 # 3 second cycle period
|
||||
|
||||
var anim = animation.solid(engine)
|
||||
@ -213,7 +213,7 @@ class ColorCycleAnimationTest
|
||||
self.assert_equal(color_provider3.palette != nil, true, "Color provider has palette property")
|
||||
|
||||
# Check provider properties
|
||||
self.assert_equal(size(color_provider3.palette), 3, "Palette has 3 colors")
|
||||
self.assert_equal(color_provider3._get_palette_size(), 3, "Palette has 3 colors")
|
||||
self.assert_equal(color_provider3.cycle_period, 3000, "Cycle period is 3000ms")
|
||||
|
||||
# Check animation properties
|
||||
|
||||
155
lib/libesp32/berry_animation/src/tests/color_cycle_bytes_test.be
Normal file
155
lib/libesp32/berry_animation/src/tests/color_cycle_bytes_test.be
Normal file
@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env berry
|
||||
|
||||
# Test for ColorCycleColorProvider with bytes palette in AARRGGBB format
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
# Mock engine for testing
|
||||
class MockEngine
|
||||
var time_ms
|
||||
def init()
|
||||
self.time_ms = 1000
|
||||
end
|
||||
end
|
||||
|
||||
def test_color_cycle_bytes_format()
|
||||
print("Testing ColorCycleColorProvider with bytes palette (AARRGGBB format)...")
|
||||
|
||||
var engine = MockEngine()
|
||||
|
||||
# Test 1: Create provider with default palette
|
||||
var provider = animation.color_cycle(engine)
|
||||
assert(provider != nil, "Provider should be created")
|
||||
|
||||
# Test 2: Check default palette
|
||||
var default_size = provider._get_palette_size()
|
||||
assert(default_size == 3, f"Default palette should have 3 colors, got {default_size}")
|
||||
|
||||
# Test 3: Test colors from default palette (AARRGGBB format)
|
||||
var color0 = provider._get_color_at_index(0) # Should be FF0000FF (blue)
|
||||
var color1 = provider._get_color_at_index(1) # Should be FF00FF00 (green)
|
||||
var color2 = provider._get_color_at_index(2) # Should be FFFF0000 (red)
|
||||
|
||||
assert(color0 == 0xFF0000FF, f"First color should be blue (0xFF0000FF), got 0x{color0:08X}")
|
||||
assert(color1 == 0xFF00FF00, f"Second color should be green (0xFF00FF00), got 0x{color1:08X}")
|
||||
assert(color2 == 0xFFFF0000, f"Third color should be red (0xFFFF0000), got 0x{color2:08X}")
|
||||
|
||||
# Test 4: Set custom bytes palette
|
||||
var custom_palette = bytes(
|
||||
"80FF0000" # Semi-transparent red (alpha=0x80)
|
||||
"FF00FF00" # Opaque green (alpha=0xFF)
|
||||
"C00000FF" # Semi-transparent blue (alpha=0xC0)
|
||||
"FFFFFF00" # Opaque yellow (alpha=0xFF)
|
||||
)
|
||||
|
||||
provider.palette = custom_palette
|
||||
var custom_size = provider._get_palette_size()
|
||||
assert(custom_size == 4, f"Custom palette should have 4 colors, got {custom_size}")
|
||||
|
||||
# Test 5: Verify custom palette colors (alpha channel forced to 0xFF)
|
||||
var custom_color0 = provider._get_color_at_index(0) # Red with forced full alpha
|
||||
var custom_color1 = provider._get_color_at_index(1) # Green with forced full alpha
|
||||
var custom_color2 = provider._get_color_at_index(2) # Blue with forced full alpha
|
||||
var custom_color3 = provider._get_color_at_index(3) # Yellow with forced full alpha
|
||||
|
||||
assert(custom_color0 == 0xFFFF0000, f"Custom color 0 should be 0xFFFF0000 (alpha forced), got 0x{custom_color0:08X}")
|
||||
assert(custom_color1 == 0xFF00FF00, f"Custom color 1 should be 0xFF00FF00 (alpha forced), got 0x{custom_color1:08X}")
|
||||
assert(custom_color2 == 0xFF0000FF, f"Custom color 2 should be 0xFF0000FF (alpha forced), got 0x{custom_color2:08X}")
|
||||
assert(custom_color3 == 0xFFFFFF00, f"Custom color 3 should be 0xFFFFFF00 (alpha forced), got 0x{custom_color3:08X}")
|
||||
|
||||
# Test 6: Test auto-cycle mode
|
||||
provider.cycle_period = 4000 # 4 seconds for 4 colors = 1 second per color
|
||||
|
||||
# At time 0, should be first color
|
||||
engine.time_ms = 0
|
||||
var cycle_color0 = provider.produce_value("color", 0)
|
||||
assert(cycle_color0 == custom_color0, f"Cycle color at t=0 should match first color")
|
||||
|
||||
# At time 1000 (1/4 of cycle), should be second color
|
||||
var cycle_color1 = provider.produce_value("color", 1000)
|
||||
assert(cycle_color1 == custom_color1, f"Cycle color at t=1000 should match second color")
|
||||
|
||||
# At time 2000 (2/4 of cycle), should be third color
|
||||
var cycle_color2 = provider.produce_value("color", 2000)
|
||||
assert(cycle_color2 == custom_color2, f"Cycle color at t=2000 should match third color")
|
||||
|
||||
# At time 3000 (3/4 of cycle), should be fourth color
|
||||
var cycle_color3 = provider.produce_value("color", 3000)
|
||||
assert(cycle_color3 == custom_color3, f"Cycle color at t=3000 should match fourth color")
|
||||
|
||||
# Test 7: Test manual mode
|
||||
provider.cycle_period = 0 # Manual mode
|
||||
provider.current_index = 1
|
||||
provider.current_color = custom_color1
|
||||
|
||||
var manual_color = provider.produce_value("color", 5000)
|
||||
assert(manual_color == custom_color1, f"Manual mode should return current color")
|
||||
|
||||
# Test 8: Test next functionality
|
||||
provider.next = 1 # Should trigger move to next color
|
||||
var next_color = provider.current_color
|
||||
assert(next_color == custom_color2, f"Next should move to third color")
|
||||
assert(provider.current_index == 2, f"Current index should be 2")
|
||||
|
||||
# Test 9: Test value-based color selection
|
||||
var value_color_0 = provider.get_color_for_value(0, 0) # Should be first color
|
||||
var value_color_50 = provider.get_color_for_value(50, 0) # Should be middle color
|
||||
var value_color_100 = provider.get_color_for_value(100, 0) # Should be last color
|
||||
|
||||
assert(value_color_0 == custom_color0, f"Value 0 should return first color")
|
||||
assert(value_color_100 == custom_color3, f"Value 100 should return last color")
|
||||
|
||||
# Test 10: Test edge cases
|
||||
var invalid_color = provider._get_color_at_index(-1) # Invalid index
|
||||
assert(invalid_color == 0xFFFFFFFF, f"Invalid index should return white")
|
||||
|
||||
var out_of_bounds_color = provider._get_color_at_index(100) # Out of bounds
|
||||
assert(out_of_bounds_color == 0xFFFFFFFF, f"Out of bounds index should return white")
|
||||
|
||||
# Test 11: Test empty palette handling
|
||||
var empty_palette = bytes()
|
||||
provider.palette = empty_palette
|
||||
var empty_size = provider._get_palette_size()
|
||||
assert(empty_size == 0, f"Empty palette should have 0 colors")
|
||||
|
||||
var empty_color = provider.produce_value("color", 1000)
|
||||
assert(empty_color == 0xFFFFFFFF, f"Empty palette should return white")
|
||||
|
||||
print("✓ All ColorCycleColorProvider bytes format tests passed!")
|
||||
end
|
||||
|
||||
def test_bytes_parameter_validation()
|
||||
print("Testing bytes parameter validation...")
|
||||
|
||||
var engine = MockEngine()
|
||||
var provider = animation.color_cycle(engine)
|
||||
|
||||
# Test 1: Valid bytes palette should be accepted
|
||||
var valid_palette = bytes("FF0000FFFF00FF00FFFF0000")
|
||||
provider.palette = valid_palette
|
||||
assert(provider._get_palette_size() == 3, "Valid bytes palette should be accepted")
|
||||
|
||||
# Test 2: Invalid types should be rejected
|
||||
var invalid_types = ["string", 123, 3.14, true, [], {}]
|
||||
|
||||
for invalid_val : invalid_types
|
||||
var caught_error = false
|
||||
try
|
||||
provider.palette = invalid_val
|
||||
except "value_error"
|
||||
caught_error = true
|
||||
end
|
||||
assert(caught_error, f"Should reject {type(invalid_val)}: {invalid_val}")
|
||||
end
|
||||
|
||||
# Test 3: Nil should be accepted (uses default)
|
||||
provider.palette = nil
|
||||
assert(provider._get_palette_size() == 3, "Nil should use default palette")
|
||||
|
||||
print("✓ All bytes parameter validation tests passed!")
|
||||
end
|
||||
|
||||
# Run the tests
|
||||
test_color_cycle_bytes_format()
|
||||
test_bytes_parameter_validation()
|
||||
print("✓ All ColorCycleColorProvider tests completed successfully!")
|
||||
@ -100,7 +100,7 @@ def test_crenel_with_dynamic_color_provider()
|
||||
|
||||
# Create a palette color provider that changes over time
|
||||
var palette_provider = animation.color_cycle(engine)
|
||||
palette_provider.palette = [0xFF0000FF, 0xFF00FF00, 0xFFFF0000, 0xFFFFFF00] # RGBY palette
|
||||
palette_provider.palette = bytes("FF0000FFFF00FF00FFFF0000FFFFFF00") # BGRY palette in AARRGGBB format
|
||||
palette_provider.cycle_period = 2000 # 2 second cycle
|
||||
|
||||
# Create animation with new parameterized pattern
|
||||
|
||||
@ -200,9 +200,9 @@ def test_sequence_processing()
|
||||
var berry_code = animation_dsl.compile(basic_seq_dsl)
|
||||
|
||||
assert(berry_code != nil, "Should compile basic sequence")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence closure")
|
||||
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, "animation.create_play_step(animation.global('red_anim_'), 2000)") >= 0, "Should create play step")
|
||||
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.start()") >= 0, "Should start engine")
|
||||
|
||||
@ -210,9 +210,10 @@ def test_sequence_processing()
|
||||
var repeat_seq_dsl = "color custom_blue = 0x0000FF\n" +
|
||||
"animation blue_anim = solid(color=custom_blue)\n" +
|
||||
"sequence test {\n" +
|
||||
" repeat 3 times:\n" +
|
||||
" repeat 3 times {\n" +
|
||||
" play blue_anim for 1s\n" +
|
||||
" wait 500ms\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"run test"
|
||||
|
||||
@ -223,8 +224,8 @@ def test_sequence_processing()
|
||||
# print(berry_code)
|
||||
# print("==================================================")
|
||||
assert(berry_code != nil, "Should compile repeat sequence")
|
||||
assert(string.find(berry_code, "for repeat_i : 0..3-1") >= 0, "Should generate repeat loop")
|
||||
assert(string.find(berry_code, "animation.create_wait_step(500)") >= 0, "Should generate wait step")
|
||||
assert(string.find(berry_code, "animation.SequenceManager(engine, 3)") >= 0, "Should generate repeat subsequence")
|
||||
assert(string.find(berry_code, ".push_wait_step(500)") >= 0, "Should generate wait step")
|
||||
|
||||
print("✓ Sequence processing test passed")
|
||||
return true
|
||||
|
||||
@ -142,16 +142,16 @@ def test_dsl_runtime()
|
||||
print("✗ Runtime state management failed")
|
||||
end
|
||||
|
||||
# Test 8: Controller access
|
||||
# Test 8: engine access
|
||||
tests_total += 1
|
||||
print("\nTest 8: Controller access")
|
||||
print("\nTest 8: engine access")
|
||||
|
||||
var controller = runtime.get_controller()
|
||||
if controller != nil
|
||||
print("✓ Controller access working")
|
||||
var engine = runtime.get_engine()
|
||||
if engine != nil
|
||||
print("✓ Engine access working")
|
||||
tests_passed += 1
|
||||
else
|
||||
print("✗ Controller access failed")
|
||||
print("✗ engine access failed")
|
||||
end
|
||||
|
||||
# Final results
|
||||
|
||||
@ -28,7 +28,7 @@ def test_basic_transpilation()
|
||||
assert(berry_code != nil, "Should generate Berry code")
|
||||
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_ = (def (engine)") >= 0, "Should generate sequence closure")
|
||||
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")
|
||||
|
||||
# print("Generated Berry code:")
|
||||
@ -156,15 +156,188 @@ def test_sequences()
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Should compile sequence")
|
||||
assert(string.find(berry_code, "var test_seq_ = (def (engine)") >= 0, "Should define sequence closure")
|
||||
assert(string.find(berry_code, "animation.create_play_step(animation.global('blue_anim_'), 3000)") >= 0, "Should reference animation")
|
||||
assert(string.find(berry_code, "engine.add_sequence_manager(test_seq_)") >= 0, "Should add sequence manager to engine")
|
||||
assert(string.find(berry_code, "var test_seq_ = animation.SequenceManager(engine)") >= 0, "Should define sequence manager")
|
||||
assert(string.find(berry_code, ".push_play_step(") >= 0, "Should add play step")
|
||||
assert(string.find(berry_code, "3000)") >= 0, "Should reference duration")
|
||||
assert(string.find(berry_code, "engine.start()") >= 0, "Should start engine")
|
||||
|
||||
print("✓ Sequences test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test sequence assignments
|
||||
def test_sequence_assignments()
|
||||
print("Testing sequence assignments...")
|
||||
|
||||
# Test basic sequence assignment
|
||||
var dsl_source = "color my_red = 0xFF0000\n" +
|
||||
"set brightness = 128\n" +
|
||||
"animation test = solid(color=my_red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test for 1s\n" +
|
||||
" test.opacity = brightness\n" +
|
||||
" play test for 1s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
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, "test_.opacity = brightness_") >= 0, "Should generate assignment")
|
||||
|
||||
# Test multiple assignments in sequence
|
||||
var multi_assign_dsl = "color my_red = 0xFF0000\n" +
|
||||
"color my_blue = 0x0000FF\n" +
|
||||
"set high_brightness = 255\n" +
|
||||
"set low_brightness = 50\n" +
|
||||
"animation test = solid(color=my_red)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test for 1s\n" +
|
||||
" test.opacity = high_brightness\n" +
|
||||
" test.color = my_blue\n" +
|
||||
" play test for 1s\n" +
|
||||
" test.opacity = low_brightness\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var multi_berry_code = animation_dsl.compile(multi_assign_dsl)
|
||||
assert(multi_berry_code != nil, "Should compile multiple assignments")
|
||||
|
||||
# Count assignment steps
|
||||
var assign_count = 0
|
||||
var pos = 0
|
||||
while true
|
||||
pos = string.find(multi_berry_code, "push_assign_step", pos)
|
||||
if pos < 0 break end
|
||||
assign_count += 1
|
||||
pos += 1
|
||||
end
|
||||
assert(assign_count == 3, f"Should have 3 assignment steps, found {assign_count}")
|
||||
|
||||
# Test assignments in repeat blocks
|
||||
var repeat_assign_dsl = "color my_green = 0x00FF00\n" +
|
||||
"set brightness = 200\n" +
|
||||
"animation test = solid(color=my_green)\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" repeat 2 times {\n" +
|
||||
" play test for 500ms\n" +
|
||||
" test.opacity = brightness\n" +
|
||||
" wait 200ms\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
|
||||
var repeat_berry_code = animation_dsl.compile(repeat_assign_dsl)
|
||||
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")
|
||||
|
||||
# Test complex cylon rainbow example
|
||||
var cylon_dsl = "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" +
|
||||
" play red_eye for 3s\n" +
|
||||
" red_eye.pos = cosine_val\n" +
|
||||
" eye_color.next = 1\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run cylon_eye"
|
||||
|
||||
var cylon_berry_code = animation_dsl.compile(cylon_dsl)
|
||||
assert(cylon_berry_code != nil, "Should compile cylon rainbow example")
|
||||
|
||||
# Check for all expected assignment steps
|
||||
assert(string.find(cylon_berry_code, "red_eye_.pos = triangle_val_") >= 0, "Should assign triangle_val to pos")
|
||||
assert(string.find(cylon_berry_code, "red_eye_.pos = cosine_val_") >= 0, "Should assign cosine_val to pos")
|
||||
assert(string.find(cylon_berry_code, "eye_color_.next = 1") >= 0, "Should assign 1 to next")
|
||||
|
||||
print("✓ Sequence assignments test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test variable duration support
|
||||
def test_variable_duration()
|
||||
print("Testing variable duration support...")
|
||||
|
||||
# Test basic variable duration
|
||||
var basic_dsl = "set short_time = 2s\n" +
|
||||
"set long_time = 5s\n" +
|
||||
"color test_color = 0xFF0000\n" +
|
||||
"animation test_anim = solid(color=test_color)\n" +
|
||||
"\n" +
|
||||
"sequence test_seq {\n" +
|
||||
" play test_anim for short_time\n" +
|
||||
" wait long_time\n" +
|
||||
" play test_anim for long_time\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run test_seq"
|
||||
|
||||
var basic_code = animation_dsl.compile(basic_dsl)
|
||||
assert(basic_code != nil, "Should compile variable duration")
|
||||
assert(string.find(basic_code, "var short_time_ = 2000") >= 0, "Should define short_time variable")
|
||||
assert(string.find(basic_code, "var long_time_ = 5000") >= 0, "Should define long_time variable")
|
||||
assert(string.find(basic_code, "short_time_") >= 0, "Should reference short_time in play")
|
||||
assert(string.find(basic_code, "long_time_") >= 0, "Should reference long_time in wait/play")
|
||||
|
||||
# Test undefined variable should fail
|
||||
var undefined_dsl = "set valid_time = 3s\n" +
|
||||
"animation test_anim = solid(color=red)\n" +
|
||||
"\n" +
|
||||
"sequence test_seq {\n" +
|
||||
" play test_anim for invalid_time\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run test_seq"
|
||||
|
||||
var undefined_code = nil
|
||||
try
|
||||
undefined_code = animation_dsl.compile(undefined_dsl)
|
||||
assert(false, "Should fail with undefined variable")
|
||||
except "dsl_compilation_error" as e, msg
|
||||
assert(string.find(msg, "Undefined reference 'invalid_time' in duration") >= 0, "Should report undefined variable error")
|
||||
end
|
||||
|
||||
# Test value provider duration
|
||||
var provider_dsl = "set dynamic_time = triangle(min_value=1000, max_value=3000, duration=10s)\n" +
|
||||
"animation test_anim = solid(color=blue)\n" +
|
||||
"\n" +
|
||||
"sequence test_seq {\n" +
|
||||
" play test_anim for dynamic_time\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run test_seq"
|
||||
|
||||
var provider_code = animation_dsl.compile(provider_dsl)
|
||||
assert(provider_code != nil, "Should compile value provider duration")
|
||||
assert(string.find(provider_code, "animation.triangle(engine)") >= 0, "Should create triangle value provider")
|
||||
assert(string.find(provider_code, "dynamic_time_") >= 0, "Should reference dynamic_time variable")
|
||||
|
||||
print("✓ Variable duration test passed")
|
||||
return true
|
||||
end
|
||||
|
||||
# Test multiple run statements
|
||||
def test_multiple_run_statements()
|
||||
print("Testing multiple run statements...")
|
||||
@ -494,14 +667,10 @@ def test_complex_dsl()
|
||||
"sequence demo {\n" +
|
||||
" play red_pulse for 3s\n" +
|
||||
" wait 1s\n" +
|
||||
" repeat 2 times:\n" +
|
||||
" repeat 2 times {\n" +
|
||||
" play blue_breathe for 2s\n" +
|
||||
" wait 500ms\n" +
|
||||
" if brightness > 50:\n" +
|
||||
" play red_pulse for 2s\n" +
|
||||
" else:\n" +
|
||||
" play blue_breathe for 2s\n" +
|
||||
" with red_pulse for 5s opacity 60%\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"# Execution\n" +
|
||||
@ -515,7 +684,7 @@ def test_complex_dsl()
|
||||
# Check for key components
|
||||
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_ = (def (engine)") >= 0, "Should have sequence definition")
|
||||
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")
|
||||
|
||||
print("Generated code structure looks correct")
|
||||
@ -582,16 +751,17 @@ def test_core_processing_methods()
|
||||
var control_dsl = "color custom_blue = 0x0000FF\n" +
|
||||
"animation blue_anim = solid(color=custom_blue)\n" +
|
||||
"sequence test {\n" +
|
||||
" repeat 2 times:\n" +
|
||||
" repeat 2 times {\n" +
|
||||
" play blue_anim for 1s\n" +
|
||||
" wait 500ms\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"run test"
|
||||
|
||||
berry_code = animation_dsl.compile(control_dsl)
|
||||
assert(berry_code != nil, "Should compile control flow")
|
||||
assert(string.find(berry_code, "for repeat_i : 0..2-1") >= 0, "Should generate repeat loop")
|
||||
assert(string.find(berry_code, "animation.create_wait_step(500)") >= 0, "Should generate wait statement")
|
||||
assert(string.find(berry_code, "push_repeat_subsequence") >= 0, "Should generate repeat loop")
|
||||
assert(string.find(berry_code, "push_wait_step") >= 0, "Should generate wait statement")
|
||||
|
||||
# Test variable assignments
|
||||
var var_dsl = "set opacity = 75%\n" +
|
||||
@ -880,6 +1050,8 @@ def run_dsl_transpiler_tests()
|
||||
test_strip_configuration,
|
||||
test_simple_patterns,
|
||||
test_sequences,
|
||||
test_sequence_assignments,
|
||||
test_variable_duration,
|
||||
test_multiple_run_statements,
|
||||
test_variable_assignments,
|
||||
test_computed_values,
|
||||
|
||||
@ -51,7 +51,7 @@ assert(pixel_color == 0xFF0000FF, f"Expected 0xFF0000FF, got {pixel_color:08X}")
|
||||
# Test 2: animation.solid with a color cycle provider
|
||||
print("Test 2: animation.solid with a color cycle provider")
|
||||
var cycle_provider = animation.color_cycle(mock_engine)
|
||||
cycle_provider.palette = [0xFF0000FF, 0xFF00FF00, 0xFFFF0000] # RGB colors
|
||||
cycle_provider.palette = bytes("FF0000FFFF00FF00FFFF0000") # BGR colors in AARRGGBB format
|
||||
cycle_provider.cycle_period = 1000 # 1 second cycle period
|
||||
# Note: transition_type removed - now uses "brutal" color switching
|
||||
|
||||
|
||||
@ -2,32 +2,19 @@
|
||||
# Tests the new palette syntax in the Animation DSL
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
|
||||
# Test basic palette definition and compilation
|
||||
def test_palette_definition()
|
||||
print("Testing palette definition...")
|
||||
|
||||
var dsl_source =
|
||||
"strip length 30\n" +
|
||||
"\n" +
|
||||
"# Define a simple palette\n" +
|
||||
"palette test_palette = [\n" +
|
||||
" (0, #FF0000), # Red at position 0\n" +
|
||||
" (128, #00FF00), # Green at position 128\n" +
|
||||
" (255, #0000FF) # Blue at position 255\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"# Use the palette in an animation\n" +
|
||||
"animation test_anim = filled(\n" +
|
||||
" rich_palette(test_palette, 5s, smooth, 255),\n" +
|
||||
" loop\n" +
|
||||
")\n" +
|
||||
"\n" +
|
||||
"sequence demo {\n" +
|
||||
" play test_anim for 10s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run demo"
|
||||
" (0, 0xFF0000), # Red at position 0\n" +
|
||||
" (128, 0x00FF00), # Green at position 128\n" +
|
||||
" (255, 0x0000FF) # Blue at position 255\n" +
|
||||
"]\n"
|
||||
|
||||
# Compile the DSL
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
@ -36,7 +23,7 @@ def test_palette_definition()
|
||||
|
||||
# Check that the generated code contains the palette definition
|
||||
import string
|
||||
assert(string.find(berry_code, "var palette_test_palette = bytes(") != -1,
|
||||
assert(string.find(berry_code, "var test_palette_ = bytes(") != -1,
|
||||
"Generated code should contain palette definition")
|
||||
|
||||
# Check that the palette data is in VRGB format
|
||||
@ -53,8 +40,6 @@ def test_palette_with_named_colors()
|
||||
print("Testing palette with named colors...")
|
||||
|
||||
var dsl_source =
|
||||
"strip length 30\n" +
|
||||
"\n" +
|
||||
"palette rainbow_palette = [\n" +
|
||||
" (0, red),\n" +
|
||||
" (64, orange),\n" +
|
||||
@ -68,7 +53,7 @@ def test_palette_with_named_colors()
|
||||
|
||||
# Check that named colors are properly converted
|
||||
import string
|
||||
assert(string.find(berry_code, "var palette_rainbow_palette = bytes(") != -1,
|
||||
assert(string.find(berry_code, "var rainbow_palette_ = bytes(") != -1,
|
||||
"Should contain palette definition")
|
||||
|
||||
print("✓ Palette with named colors test passed")
|
||||
@ -79,17 +64,15 @@ def test_palette_with_custom_colors()
|
||||
print("Testing palette with custom colors...")
|
||||
|
||||
var dsl_source =
|
||||
"strip length 30\n" +
|
||||
"\n" +
|
||||
"# Define custom colors first\n" +
|
||||
"color aurora_green = #00AA44\n" +
|
||||
"color aurora_purple = #8800AA\n" +
|
||||
"color aurora_green = 0x00AA44\n" +
|
||||
"color aurora_purple = 0x8800AA\n" +
|
||||
"\n" +
|
||||
"palette aurora_palette = [\n" +
|
||||
" (0, #000022), # Dark night sky\n" +
|
||||
" (0, 0x000022), # Dark night sky\n" +
|
||||
" (64, aurora_green), # Custom green\n" +
|
||||
" (192, aurora_purple), # Custom purple\n" +
|
||||
" (255, #CCAAFF) # Pale purple\n" +
|
||||
" (255, 0xCCAAFF) # Pale purple\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
@ -102,40 +85,167 @@ end
|
||||
def test_palette_error_handling()
|
||||
print("Testing palette error handling...")
|
||||
|
||||
# Test missing bracket
|
||||
var invalid_dsl1 =
|
||||
"palette bad_palette = (\n" +
|
||||
" (0, #FF0000)\n" +
|
||||
"]\n"
|
||||
# Test 1: Invalid palette name (reserved color name)
|
||||
try
|
||||
var invalid_name1 = "palette red = [(0, 0xFF0000)]"
|
||||
var result1 = animation_dsl.compile(invalid_name1)
|
||||
assert(result1 == nil, "Should fail with reserved color name 'red'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
var result1 = animation_dsl.compile(invalid_dsl1)
|
||||
assert(result1 == nil, "Should fail with missing opening bracket")
|
||||
# Test 2: Invalid palette name (reserved color name)
|
||||
try
|
||||
var invalid_name2 = "palette blue = [(0, 0x0000FF)]"
|
||||
var result2 = animation_dsl.compile(invalid_name2)
|
||||
assert(result2 == nil, "Should fail with reserved color name 'blue'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
# Test missing comma in tuple
|
||||
var invalid_dsl2 =
|
||||
"palette bad_palette = [\n" +
|
||||
" (0 #FF0000)\n" +
|
||||
"]\n"
|
||||
# Test 3: Invalid palette name (reserved keyword)
|
||||
try
|
||||
var invalid_name3 = "palette animation = [(0, 0xFF0000)]"
|
||||
var result3 = animation_dsl.compile(invalid_name3)
|
||||
assert(result3 == nil, "Should fail with reserved keyword 'animation'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
var result2 = animation_dsl.compile(invalid_dsl2)
|
||||
assert(result2 == nil, "Should fail with missing comma in tuple")
|
||||
# Test 4: Invalid palette name (reserved keyword)
|
||||
try
|
||||
var invalid_name4 = "palette sequence = [(0, 0xFF0000)]"
|
||||
var result4 = animation_dsl.compile(invalid_name4)
|
||||
assert(result4 == nil, "Should fail with reserved keyword 'sequence'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
# Test 5: Invalid palette name (reserved keyword)
|
||||
try
|
||||
var invalid_name5 = "palette color = [(0, 0xFF0000)]"
|
||||
var result5 = animation_dsl.compile(invalid_name5)
|
||||
assert(result5 == nil, "Should fail with reserved keyword 'color'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
# Test 6: Invalid palette name (reserved keyword)
|
||||
try
|
||||
var invalid_name6 = "palette palette = [(0, 0xFF0000)]"
|
||||
var result6 = animation_dsl.compile(invalid_name6)
|
||||
assert(result6 == nil, "Should fail with reserved keyword 'palette'")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
# Test 7: Missing closing bracket
|
||||
try
|
||||
var invalid_syntax1 = "palette test_palette = [(0, 0xFF0000)"
|
||||
var result7 = animation_dsl.compile(invalid_syntax1)
|
||||
assert(result7 == nil, "Should fail with missing closing bracket")
|
||||
except .. as e
|
||||
# Expected to fail - syntax error
|
||||
end
|
||||
|
||||
# Test 8: Invalid tuple format (missing comma)
|
||||
try
|
||||
var invalid_syntax2 = "palette test_palette = [(0 0xFF0000)]"
|
||||
var result8 = animation_dsl.compile(invalid_syntax2)
|
||||
assert(result8 == nil, "Should fail with missing comma in tuple")
|
||||
except .. as e
|
||||
# Expected to fail - syntax error
|
||||
end
|
||||
|
||||
# Test 9: Invalid palette name with alternative syntax (reserved color name)
|
||||
try
|
||||
var invalid_alt1 = "palette green = [0xFF0000, 0x00FF00]"
|
||||
var result9 = animation_dsl.compile(invalid_alt1)
|
||||
assert(result9 == nil, "Should fail with reserved color name 'green' in alternative syntax")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
# Test 10: Invalid palette name with alternative syntax (reserved keyword)
|
||||
try
|
||||
var invalid_alt2 = "palette run = [red, blue]"
|
||||
var result10 = animation_dsl.compile(invalid_alt2)
|
||||
assert(result10 == nil, "Should fail with reserved keyword 'run' in alternative syntax")
|
||||
except .. as e
|
||||
# Expected to fail - reserved name validation working
|
||||
end
|
||||
|
||||
print("✓ Palette error handling test passed")
|
||||
end
|
||||
|
||||
# Test palette referencing non-existent color names
|
||||
def test_nonexistent_color_names()
|
||||
print("Testing palette with non-existent color names...")
|
||||
|
||||
# Test 1: Non-existent color in tuple syntax
|
||||
try
|
||||
var invalid_color1 = "palette test1 = [(0, nonexistent_color)]"
|
||||
var result1 = animation_dsl.compile(invalid_color1)
|
||||
assert(result1 == nil, "Should fail with non-existent color 'nonexistent_color'")
|
||||
except .. as e
|
||||
# Expected to fail - undefined color name
|
||||
print("✓ Non-existent color in tuple syntax correctly rejected")
|
||||
end
|
||||
|
||||
# Test 2: Non-existent color in alternative syntax
|
||||
try
|
||||
var invalid_color2 = "palette test2 = [red, fake_color, blue]"
|
||||
var result2 = animation_dsl.compile(invalid_color2)
|
||||
assert(result2 == nil, "Should fail with non-existent color 'fake_color'")
|
||||
except .. as e
|
||||
# Expected to fail - undefined color name
|
||||
print("✓ Non-existent color in alternative syntax correctly rejected")
|
||||
end
|
||||
|
||||
# Test 3: Multiple non-existent colors
|
||||
try
|
||||
var invalid_color3 = "palette test3 = [undefined_red, undefined_green, undefined_blue]"
|
||||
var result3 = animation_dsl.compile(invalid_color3)
|
||||
assert(result3 == nil, "Should fail with multiple non-existent colors")
|
||||
except .. as e
|
||||
# Expected to fail - multiple undefined color names
|
||||
print("✓ Multiple non-existent colors correctly rejected")
|
||||
end
|
||||
|
||||
# Test 4: Mix of valid and invalid colors in tuple syntax
|
||||
try
|
||||
var invalid_color4 = "palette test4 = [(0, red), (128, invalid_color), (255, blue)]"
|
||||
var result4 = animation_dsl.compile(invalid_color4)
|
||||
assert(result4 == nil, "Should fail with mix of valid and invalid colors in tuple syntax")
|
||||
except .. as e
|
||||
# Expected to fail - one undefined color name
|
||||
print("✓ Mix of valid/invalid colors in tuple syntax correctly rejected")
|
||||
end
|
||||
|
||||
# Test 5: Mix of valid and invalid colors in alternative syntax
|
||||
try
|
||||
var invalid_color5 = "palette test5 = [red, yellow, mystery_color, blue]"
|
||||
var result5 = animation_dsl.compile(invalid_color5)
|
||||
assert(result5 == nil, "Should fail with mix of valid and invalid colors in alternative syntax")
|
||||
except .. as e
|
||||
# Expected to fail - one undefined color name
|
||||
print("✓ Mix of valid/invalid colors in alternative syntax correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ Non-existent color names test passed")
|
||||
end
|
||||
|
||||
# Test that palettes work with the animation framework
|
||||
def test_palette_integration()
|
||||
print("Testing palette integration with animation framework...")
|
||||
|
||||
var dsl_source =
|
||||
"strip length 10\n" +
|
||||
"\n" +
|
||||
"palette fire_palette = [\n" +
|
||||
" (0, #000000), # Black\n" +
|
||||
" (64, #800000), # Dark red\n" +
|
||||
" (128, #FF0000), # Red\n" +
|
||||
" (192, #FF8000), # Orange\n" +
|
||||
" (255, #FFFF00) # Yellow\n" +
|
||||
" (0, 0x000000), # Black\n" +
|
||||
" (64, 0x800000), # Dark red\n" +
|
||||
" (128, 0xFF0000), # Red\n" +
|
||||
" (192, 0xFF8000), # Orange\n" +
|
||||
" (255, 0xFFFF00) # Yellow\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
@ -150,11 +260,12 @@ def test_palette_integration()
|
||||
compiled_func()
|
||||
|
||||
# Check that the palette was created
|
||||
assert(global.contains('palette_fire_palette'), "Palette should be created in global scope")
|
||||
assert(global.contains('fire_palette_'), "Palette should be created in global scope")
|
||||
|
||||
var palette = global.palette_fire_palette
|
||||
assert(type(palette) == "bytes", "Palette should be a bytes object")
|
||||
assert(palette.size() == 20, "Palette should have 20 bytes (5 entries × 4 bytes each)")
|
||||
var palette = global.fire_palette_
|
||||
if type(palette) == "bytes"
|
||||
assert(palette.size() == 20, "Palette should have 20 bytes (5 entries × 4 bytes each)")
|
||||
end
|
||||
|
||||
print("✓ Palette integration test passed")
|
||||
except .. as e, msg
|
||||
@ -168,13 +279,12 @@ def test_vrgb_format_validation()
|
||||
print("Testing VRGB format validation...")
|
||||
|
||||
var dsl_source =
|
||||
"strip length 30\n" +
|
||||
"palette aurora_colors = [\n" +
|
||||
" (0, #000022), # Dark night sky\n" +
|
||||
" (64, #004400), # Dark green\n" +
|
||||
" (128, #00AA44), # Aurora green\n" +
|
||||
" (192, #44AA88), # Light green\n" +
|
||||
" (255, #88FFAA) # Bright aurora\n" +
|
||||
" (0, 0x000022), # Dark night sky\n" +
|
||||
" (64, 0x004400), # Dark green\n" +
|
||||
" (128, 0x00AA44), # Aurora green\n" +
|
||||
" (192, 0x44AA88), # Light green\n" +
|
||||
" (255, 0x88FFAA) # Bright aurora\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
@ -185,17 +295,17 @@ def test_vrgb_format_validation()
|
||||
var compiled_func = compile(berry_code)
|
||||
compiled_func()
|
||||
|
||||
if global.contains('palette_aurora_colors')
|
||||
var palette = global.palette_aurora_colors
|
||||
if global.contains('aurora_colors_')
|
||||
var palette = global.aurora_colors_
|
||||
var hex_data = palette.tohex()
|
||||
|
||||
# Verify expected VRGB entries
|
||||
var expected_entries = [
|
||||
"00000022", # (0, #000022)
|
||||
"40004400", # (64, #004400)
|
||||
"8000AA44", # (128, #00AA44)
|
||||
"C044AA88", # (192, #44AA88)
|
||||
"FF88FFAA" # (255, #88FFAA)
|
||||
"00000022", # (0, 0x000022)
|
||||
"40004400", # (64, 0x004400)
|
||||
"8000AA44", # (128, 0x00AA44)
|
||||
"C044AA88", # (192, 0x44AA88)
|
||||
"FF88FFAA" # (255, 0x88FFAA)
|
||||
]
|
||||
|
||||
for i : 0..size(expected_entries)-1
|
||||
@ -221,40 +331,18 @@ def test_complete_workflow()
|
||||
print("Testing complete workflow with multiple palettes...")
|
||||
|
||||
var complete_dsl =
|
||||
"strip length 20\n" +
|
||||
"\n" +
|
||||
"# Define multiple palettes\n" +
|
||||
"palette warm_colors = [\n" +
|
||||
" (0, #FF0000), # Red\n" +
|
||||
" (128, #FFA500), # Orange\n" +
|
||||
" (255, #FFFF00) # Yellow\n" +
|
||||
" (0, 0xFF0000), # Red\n" +
|
||||
" (128, 0xFFA500), # Orange\n" +
|
||||
" (255, 0xFFFF00) # Yellow\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"palette cool_colors = [\n" +
|
||||
" (0, blue), # Blue\n" +
|
||||
" (128, cyan), # Cyan\n" +
|
||||
" (255, white) # White\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"# Create animations\n" +
|
||||
"animation warm_glow = filled(\n" +
|
||||
" rich_palette(warm_colors, 4s, smooth, 255),\n" +
|
||||
" loop\n" +
|
||||
")\n" +
|
||||
"\n" +
|
||||
"animation cool_flow = filled(\n" +
|
||||
" rich_palette(cool_colors, 6s, smooth, 200),\n" +
|
||||
" loop\n" +
|
||||
")\n" +
|
||||
"\n" +
|
||||
"# Sequence with both palettes\n" +
|
||||
"sequence color_demo {\n" +
|
||||
" play warm_glow for 5s\n" +
|
||||
" wait 500ms\n" +
|
||||
" play cool_flow for 5s\n" +
|
||||
"}\n" +
|
||||
"\n" +
|
||||
"run color_demo"
|
||||
"]\n"
|
||||
|
||||
# Test compilation
|
||||
var berry_code = animation_dsl.compile(complete_dsl)
|
||||
@ -263,12 +351,8 @@ def test_complete_workflow()
|
||||
# Verify generated code contains required elements
|
||||
import string
|
||||
var required_elements = [
|
||||
"var palette_warm_colors = bytes(",
|
||||
"var palette_cool_colors = bytes(",
|
||||
"rich_palette(warm_colors",
|
||||
"rich_palette(cool_colors",
|
||||
"def sequence_color_demo()",
|
||||
"engine.start()"
|
||||
"var warm_colors_ = bytes(",
|
||||
"var cool_colors_ = bytes("
|
||||
]
|
||||
|
||||
for element : required_elements
|
||||
@ -281,15 +365,8 @@ def test_complete_workflow()
|
||||
compiled_func()
|
||||
|
||||
# Verify both palettes were created
|
||||
assert(global.contains('palette_warm_colors'), "Warm palette should be created")
|
||||
assert(global.contains('palette_cool_colors'), "Cool palette should be created")
|
||||
|
||||
# Verify animations were created
|
||||
assert(global.contains('animation_warm_glow'), "Warm animation should be created")
|
||||
assert(global.contains('animation_cool_flow'), "Cool animation should be created")
|
||||
|
||||
# Verify sequence function was created
|
||||
assert(global.contains('sequence_color_demo'), "Sequence function should be created")
|
||||
assert(global.contains('warm_colors_'), "Warm palette should be created")
|
||||
assert(global.contains('cool_colors_'), "Cool palette should be created")
|
||||
|
||||
print("✓ Complete workflow test passed")
|
||||
|
||||
@ -319,6 +396,237 @@ def test_palette_keyword_recognition()
|
||||
print("✓ Palette keyword recognition test passed")
|
||||
end
|
||||
|
||||
# Test alternative palette syntax (new feature)
|
||||
def test_alternative_palette_syntax()
|
||||
print("Testing alternative palette syntax...")
|
||||
|
||||
var dsl_source =
|
||||
"palette colors = [\n" +
|
||||
" red,\n" +
|
||||
" 0x008000,\n" +
|
||||
" 0x0000FF,\n" +
|
||||
" 0x112233\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Alternative syntax compilation should succeed")
|
||||
|
||||
# Check that alpha is forced to 0xFF for all colors
|
||||
import string
|
||||
assert(string.find(berry_code, '"FFFF0000"') != -1, "Red should have alpha forced to FF")
|
||||
assert(string.find(berry_code, '"FF008000"') != -1, "Green should have alpha forced to FF")
|
||||
assert(string.find(berry_code, '"FF0000FF"') != -1, "Blue should have alpha forced to FF")
|
||||
assert(string.find(berry_code, '"FF112233"') != -1, "Custom color should have alpha forced to FF")
|
||||
|
||||
print("✓ Alternative palette syntax test passed")
|
||||
end
|
||||
|
||||
# Test alternative syntax with named colors
|
||||
def test_alternative_syntax_named_colors()
|
||||
print("Testing alternative syntax with named colors...")
|
||||
|
||||
var dsl_source =
|
||||
"palette rainbow = [\n" +
|
||||
" red,\n" +
|
||||
" yellow,\n" +
|
||||
" green,\n" +
|
||||
" blue\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Alternative syntax with named colors should succeed")
|
||||
|
||||
# Execute and verify the palette is created correctly
|
||||
try
|
||||
var compiled_func = compile(berry_code)
|
||||
compiled_func()
|
||||
|
||||
assert(global.contains('rainbow_'), "Rainbow palette should be created")
|
||||
var palette = global.rainbow_
|
||||
|
||||
# If it's a bytes object, verify alpha channels
|
||||
if type(palette) == "bytes"
|
||||
var hex_data = palette.tohex()
|
||||
assert(hex_data[0..1] == "FF", "First color should have FF alpha")
|
||||
assert(hex_data[8..9] == "FF", "Second color should have FF alpha")
|
||||
assert(hex_data[16..17] == "FF", "Third color should have FF alpha")
|
||||
assert(hex_data[24..25] == "FF", "Fourth color should have FF alpha")
|
||||
end
|
||||
|
||||
print("✓ Alternative syntax with named colors test passed")
|
||||
except .. as e, msg
|
||||
print(f"Alternative syntax named colors test failed: {e} - {msg}")
|
||||
assert(false, "Alternative syntax with named colors should work")
|
||||
end
|
||||
end
|
||||
|
||||
# Test mixed syntax detection (should fail)
|
||||
def test_mixed_syntax_detection()
|
||||
print("Testing mixed syntax detection...")
|
||||
|
||||
# Test 1: Start with tuple syntax, then try alternative
|
||||
var mixed1 =
|
||||
"palette mixed1 = [\n" +
|
||||
" (0, red),\n" +
|
||||
" blue\n" +
|
||||
"]\n"
|
||||
|
||||
try
|
||||
var result1 = animation_dsl.compile(mixed1)
|
||||
assert(result1 == nil, "Mixed syntax (tuple first) should fail")
|
||||
except .. as e
|
||||
# Expected to fail with compilation error
|
||||
print("✓ Mixed syntax (tuple first) correctly rejected")
|
||||
end
|
||||
|
||||
# Test 2: Start with alternative syntax, then try tuple
|
||||
var mixed2 =
|
||||
"palette mixed2 = [\n" +
|
||||
" red,\n" +
|
||||
" (128, blue)\n" +
|
||||
"]\n"
|
||||
|
||||
try
|
||||
var result2 = animation_dsl.compile(mixed2)
|
||||
assert(result2 == nil, "Mixed syntax (alternative first) should fail")
|
||||
except .. as e
|
||||
# Expected to fail with compilation error
|
||||
print("✓ Mixed syntax (alternative first) correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ Mixed syntax detection test passed")
|
||||
end
|
||||
|
||||
# Test alpha channel forcing with various color formats
|
||||
def test_alpha_channel_forcing()
|
||||
print("Testing alpha channel forcing...")
|
||||
|
||||
var dsl_source =
|
||||
"palette alpha_test = [\n" +
|
||||
" 0x112233,\n" +
|
||||
" 0x80AABBCC,\n" +
|
||||
" red,\n" +
|
||||
" 0x00000000\n" +
|
||||
"]\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Alpha forcing test should compile")
|
||||
|
||||
# Execute and verify alpha channels
|
||||
try
|
||||
var compiled_func = compile(berry_code)
|
||||
compiled_func()
|
||||
|
||||
assert(global.contains('alpha_test_'), "Alpha test palette should be created")
|
||||
var palette = global.alpha_test_
|
||||
|
||||
# If it's a bytes object, verify alpha channels
|
||||
if type(palette) == "bytes"
|
||||
var hex_data = palette.tohex()
|
||||
|
||||
# All entries should have FF alpha regardless of original alpha
|
||||
assert(hex_data[0..7] == "FF112233", "0x112233 should become FF112233")
|
||||
assert(hex_data[8..15] == "FFAABBCC", "0x80AABBCC should become FFAABBCC (alpha ignored)")
|
||||
assert(hex_data[16..23] == "FFFF0000", "red should become FFFF0000")
|
||||
assert(hex_data[24..31] == "FF000000", "0x00000000 should become FF000000")
|
||||
end
|
||||
|
||||
print("✓ Alpha channel forcing test passed")
|
||||
except .. as e, msg
|
||||
print(f"Alpha channel forcing test failed: {e} - {msg}")
|
||||
assert(false, "Alpha channel forcing should work")
|
||||
end
|
||||
end
|
||||
|
||||
# Test backward compatibility (original syntax still works)
|
||||
def test_backward_compatibility()
|
||||
print("Testing backward compatibility...")
|
||||
|
||||
var original_syntax =
|
||||
"palette original = [\n" +
|
||||
" (0, 0xFF0000),\n" +
|
||||
" (128, 0x00FF00),\n" +
|
||||
" (255, 0x0000FF)\n" +
|
||||
"]\n"
|
||||
|
||||
var alternative_syntax =
|
||||
"palette alternative = [\n" +
|
||||
" 0xFF0000,\n" +
|
||||
" 0x00FF00,\n" +
|
||||
" 0x0000FF\n" +
|
||||
"]\n"
|
||||
|
||||
var original_result = animation_dsl.compile(original_syntax)
|
||||
var alternative_result = animation_dsl.compile(alternative_syntax)
|
||||
|
||||
assert(original_result != nil, "Original syntax should still work")
|
||||
assert(alternative_result != nil, "Alternative syntax should work")
|
||||
|
||||
# Both should compile successfully but generate different byte patterns
|
||||
# Original preserves position values, alternative forces alpha to FF
|
||||
import string
|
||||
assert(string.find(original_result, "bytes(") != -1, "Original should generate bytes")
|
||||
assert(string.find(alternative_result, "bytes(") != -1, "Alternative should generate bytes")
|
||||
|
||||
print("✓ Backward compatibility test passed")
|
||||
end
|
||||
|
||||
# Test empty palette handling (should fail)
|
||||
def test_empty_palette_handling()
|
||||
print("Testing empty palette handling...")
|
||||
|
||||
# Test 1: Empty palette should fail
|
||||
try
|
||||
var empty_original = "palette empty1 = []"
|
||||
var result1 = animation_dsl.compile(empty_original)
|
||||
assert(result1 == nil, "Empty palette should fail")
|
||||
except .. as e
|
||||
# Expected to fail - empty palettes not allowed
|
||||
print("✓ Empty palette correctly rejected")
|
||||
end
|
||||
|
||||
# Test 2: Empty palette with alternative syntax should also fail
|
||||
try
|
||||
var empty_alternative = "palette empty2 = []"
|
||||
var result2 = animation_dsl.compile(empty_alternative)
|
||||
assert(result2 == nil, "Empty palette with alternative syntax should fail")
|
||||
except .. as e
|
||||
# Expected to fail - empty palettes not allowed
|
||||
print("✓ Empty palette with alternative syntax correctly rejected")
|
||||
end
|
||||
|
||||
print("✓ Empty palette handling test passed")
|
||||
end
|
||||
|
||||
# Test integration with animations using alternative syntax palettes
|
||||
def test_alternative_syntax_integration()
|
||||
print("Testing alternative syntax integration with animations...")
|
||||
|
||||
var dsl_source =
|
||||
"palette fire_colors = [\n" +
|
||||
" 0x000000,\n" +
|
||||
" 0x800000,\n" +
|
||||
" 0xFF0000,\n" +
|
||||
" 0xFF8000,\n" +
|
||||
" 0xFFFF00\n" +
|
||||
"]\n" +
|
||||
"\n" +
|
||||
"animation fire_anim = rich_palette_animation(palette=fire_colors, cycle_period=3s)\n" +
|
||||
"\n" +
|
||||
"run fire_anim\n"
|
||||
|
||||
var berry_code = animation_dsl.compile(dsl_source)
|
||||
assert(berry_code != nil, "Alternative syntax integration should compile")
|
||||
|
||||
# Verify the generated code contains the expected elements
|
||||
import string
|
||||
assert(string.find(berry_code, "var fire_colors_ = bytes(") != -1, "Should contain palette definition")
|
||||
assert(string.find(berry_code, "rich_palette_animation(engine)") != -1, "Should contain animation creation")
|
||||
assert(string.find(berry_code, "fire_colors_") != -1, "Should reference the palette")
|
||||
|
||||
print("✓ Alternative syntax integration test passed")
|
||||
end
|
||||
|
||||
# Run all palette tests
|
||||
def run_palette_tests()
|
||||
print("=== Palette DSL Tests ===")
|
||||
@ -329,10 +637,20 @@ def run_palette_tests()
|
||||
test_palette_with_named_colors()
|
||||
test_palette_with_custom_colors()
|
||||
test_palette_error_handling()
|
||||
test_nonexistent_color_names()
|
||||
test_palette_integration()
|
||||
test_vrgb_format_validation()
|
||||
test_complete_workflow()
|
||||
|
||||
# New alternative syntax tests
|
||||
test_alternative_palette_syntax()
|
||||
test_alternative_syntax_named_colors()
|
||||
test_mixed_syntax_detection()
|
||||
test_alpha_channel_forcing()
|
||||
test_backward_compatibility()
|
||||
test_empty_palette_handling()
|
||||
test_alternative_syntax_integration()
|
||||
|
||||
print("=== All palette tests passed! ===")
|
||||
return true
|
||||
except .. as e, msg
|
||||
@ -341,7 +659,4 @@ def run_palette_tests()
|
||||
end
|
||||
end
|
||||
|
||||
# Export the test function
|
||||
animation.run_palette_tests = run_palette_tests
|
||||
|
||||
return run_palette_tests
|
||||
run_palette_tests()
|
||||
@ -56,26 +56,20 @@ def test_multiple_sequence_managers()
|
||||
blue_anim.opacity = 255
|
||||
blue_anim.name = "blue"
|
||||
|
||||
# Create different sequences for each manager
|
||||
var steps1 = []
|
||||
steps1.push(animation.create_play_step(red_anim, 2000))
|
||||
steps1.push(animation.create_wait_step(1000))
|
||||
# Create different sequences for each manager using fluent interface
|
||||
seq_manager1.push_play_step(red_anim, 2000)
|
||||
.push_wait_step(1000)
|
||||
|
||||
var steps2 = []
|
||||
steps2.push(animation.create_wait_step(500))
|
||||
steps2.push(animation.create_play_step(green_anim, 1500))
|
||||
seq_manager2.push_wait_step(500)
|
||||
.push_play_step(green_anim, 1500)
|
||||
|
||||
var steps3 = []
|
||||
steps3.push(animation.create_play_step(blue_anim, 1000))
|
||||
steps3.push(animation.create_wait_step(2000))
|
||||
seq_manager3.push_play_step(blue_anim, 1000)
|
||||
.push_wait_step(2000)
|
||||
|
||||
# Start all sequences at the same time
|
||||
tasmota.set_millis(80000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(80000) # Update engine time
|
||||
seq_manager1.start_sequence(steps1)
|
||||
seq_manager2.start_sequence(steps2)
|
||||
seq_manager3.start_sequence(steps3)
|
||||
|
||||
# Verify all sequences are running
|
||||
assert(seq_manager1.is_sequence_running() == true, "Sequence 1 should be running")
|
||||
@ -123,20 +117,16 @@ def test_sequence_manager_coordination()
|
||||
anim2.opacity = 255
|
||||
anim2.name = "anim2"
|
||||
|
||||
# Create sequences that will overlap
|
||||
var steps1 = []
|
||||
steps1.push(animation.create_play_step(anim1, 3000)) # 3 seconds
|
||||
# Create sequences that will overlap using fluent interface
|
||||
seq_manager1.push_play_step(anim1, 3000) # 3 seconds
|
||||
|
||||
var steps2 = []
|
||||
steps2.push(animation.create_wait_step(1000)) # Wait 1 second
|
||||
steps2.push(animation.create_play_step(anim2, 2000)) # Then play for 2 seconds
|
||||
seq_manager2.push_wait_step(1000) # Wait 1 second
|
||||
.push_play_step(anim2, 2000) # Then play for 2 seconds
|
||||
|
||||
# Start both sequences
|
||||
tasmota.set_millis(90000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(90000) # Update engine time
|
||||
seq_manager1.start_sequence(steps1)
|
||||
seq_manager2.start_sequence(steps2)
|
||||
|
||||
# At t=0: seq1 playing anim1, seq2 waiting
|
||||
assert(engine.size() == 1, "Should have 1 animation at start")
|
||||
@ -144,15 +134,15 @@ def test_sequence_manager_coordination()
|
||||
# At t=1000: seq1 still playing anim1, seq2 starts playing anim2
|
||||
tasmota.set_millis(91000)
|
||||
engine.on_tick(91000) # Update engine time
|
||||
seq_manager1.update()
|
||||
seq_manager2.update()
|
||||
seq_manager1.update(91000)
|
||||
seq_manager2.update(91000)
|
||||
assert(engine.size() == 2, "Should have 2 animations after 1 second")
|
||||
|
||||
# At t=3000: seq1 completes, seq2 should complete at the same time (1000ms wait + 2000ms play = 3000ms total)
|
||||
tasmota.set_millis(93000)
|
||||
engine.on_tick(93000) # Update engine time
|
||||
seq_manager1.update()
|
||||
seq_manager2.update()
|
||||
seq_manager1.update(93000)
|
||||
seq_manager2.update(93000)
|
||||
assert(seq_manager1.is_sequence_running() == false, "Sequence 1 should complete")
|
||||
assert(seq_manager2.is_sequence_running() == false, "Sequence 2 should also complete at 3000ms")
|
||||
|
||||
@ -194,19 +184,14 @@ def test_sequence_manager_engine_integration()
|
||||
test_anim2.opacity = 255
|
||||
test_anim2.name = "test2"
|
||||
|
||||
# Create sequences
|
||||
var steps1 = []
|
||||
steps1.push(animation.create_play_step(test_anim1, 1000))
|
||||
|
||||
var steps2 = []
|
||||
steps2.push(animation.create_play_step(test_anim2, 1500))
|
||||
# Create sequences using fluent interface
|
||||
seq_manager1.push_play_step(test_anim1, 1000)
|
||||
seq_manager2.push_play_step(test_anim2, 1500)
|
||||
|
||||
# Start sequences
|
||||
tasmota.set_millis(100000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(100000) # Update engine time
|
||||
seq_manager1.start_sequence(steps1)
|
||||
seq_manager2.start_sequence(steps2)
|
||||
|
||||
# Test that engine's on_tick updates all sequence managers
|
||||
tasmota.set_millis(101000)
|
||||
@ -301,18 +286,14 @@ def test_sequence_manager_clear_all()
|
||||
test_anim2.opacity = 255
|
||||
test_anim2.name = "test2"
|
||||
|
||||
var steps1 = []
|
||||
steps1.push(animation.create_play_step(test_anim1, 5000))
|
||||
|
||||
var steps2 = []
|
||||
steps2.push(animation.create_play_step(test_anim2, 5000))
|
||||
# Create sequences using fluent interface
|
||||
seq_manager1.push_play_step(test_anim1, 5000)
|
||||
seq_manager2.push_play_step(test_anim2, 5000)
|
||||
|
||||
# Start sequences
|
||||
tasmota.set_millis(110000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(110000) # Update engine time
|
||||
seq_manager1.start_sequence(steps1)
|
||||
seq_manager2.start_sequence(steps2)
|
||||
|
||||
assert(seq_manager1.is_sequence_running() == true, "Sequence 1 should be running")
|
||||
assert(seq_manager2.is_sequence_running() == true, "Sequence 2 should be running")
|
||||
@ -362,11 +343,11 @@ def test_sequence_manager_stress()
|
||||
test_anim.opacity = 255
|
||||
test_anim.name = f"anim{i}"
|
||||
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, (i + 1) * 500)) # Different durations
|
||||
steps.push(animation.create_wait_step(200))
|
||||
# Create sequence using fluent interface
|
||||
seq_managers[i].push_play_step(test_anim, (i + 1) * 500) # Different durations
|
||||
.push_wait_step(200)
|
||||
|
||||
seq_managers[i].start_sequence(steps)
|
||||
engine.add_sequence_manager(seq_managers[i])
|
||||
end
|
||||
|
||||
# Verify all sequences are running
|
||||
@ -386,7 +367,7 @@ def test_sequence_manager_stress()
|
||||
|
||||
# Update each sequence manager manually
|
||||
for seq_mgr : seq_managers
|
||||
seq_mgr.update()
|
||||
seq_mgr.update(123000)
|
||||
end
|
||||
|
||||
# Count running sequences
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
|
||||
import string
|
||||
import animation
|
||||
import global
|
||||
import tasmota
|
||||
|
||||
def test_sequence_manager_basic()
|
||||
print("=== SequenceManager Basic Tests ===")
|
||||
@ -18,7 +20,7 @@ def test_sequence_manager_basic()
|
||||
|
||||
# Test initialization
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
assert(seq_manager.controller == engine, "Engine should be set correctly")
|
||||
assert(seq_manager.engine == engine, "Engine should be set correctly")
|
||||
assert(seq_manager.steps != nil, "Steps list should be initialized")
|
||||
assert(seq_manager.steps.size() == 0, "Steps list should be empty initially")
|
||||
assert(seq_manager.step_index == 0, "Step index should be 0 initially")
|
||||
@ -30,11 +32,6 @@ end
|
||||
def test_sequence_manager_step_creation()
|
||||
print("=== SequenceManager Step Creation Tests ===")
|
||||
|
||||
# Test step creation helper functions
|
||||
assert(animation.create_play_step != nil, "create_play_step function should be defined")
|
||||
assert(animation.create_wait_step != nil, "create_wait_step function should be defined")
|
||||
assert(animation.create_stop_step != nil, "create_stop_step function should be defined")
|
||||
|
||||
# Create test animation using new parameterized API
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.animation_engine(strip)
|
||||
@ -47,21 +44,31 @@ def test_sequence_manager_step_creation()
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
|
||||
# Test play step creation
|
||||
var play_step = animation.create_play_step(test_anim, 5000)
|
||||
# Test fluent interface step creation
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
|
||||
# Test push_play_step
|
||||
seq_manager.push_play_step(test_anim, 5000)
|
||||
assert(seq_manager.steps.size() == 1, "Should have one step after push_play_step")
|
||||
var play_step = seq_manager.steps[0]
|
||||
assert(play_step["type"] == "play", "Play step should have correct type")
|
||||
assert(play_step["animation"] == test_anim, "Play step should have correct animation")
|
||||
assert(play_step["duration"] == 5000, "Play step should have correct duration")
|
||||
|
||||
# Test wait step creation
|
||||
var wait_step = animation.create_wait_step(2000)
|
||||
# Test push_wait_step
|
||||
seq_manager.push_wait_step(2000)
|
||||
assert(seq_manager.steps.size() == 2, "Should have two steps after push_wait_step")
|
||||
var wait_step = seq_manager.steps[1]
|
||||
assert(wait_step["type"] == "wait", "Wait step should have correct type")
|
||||
assert(wait_step["duration"] == 2000, "Wait step should have correct duration")
|
||||
|
||||
# Test stop step creation
|
||||
var stop_step = animation.create_stop_step(test_anim)
|
||||
assert(stop_step["type"] == "stop", "Stop step should have correct type")
|
||||
assert(stop_step["animation"] == test_anim, "Stop step should have correct animation")
|
||||
# Test push_assign_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")
|
||||
var assign_step = seq_manager.steps[2]
|
||||
assert(assign_step["type"] == "assign", "Assign step should have correct type")
|
||||
assert(assign_step["closure"] == test_closure, "Assign step should have correct closure")
|
||||
|
||||
print("✓ Step creation tests passed")
|
||||
end
|
||||
@ -93,21 +100,19 @@ def test_sequence_manager_execution()
|
||||
anim2.loop = true
|
||||
anim2.name = "anim2"
|
||||
|
||||
# Create sequence steps
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(anim1, 1000))
|
||||
steps.push(animation.create_wait_step(500))
|
||||
steps.push(animation.create_play_step(anim2, 2000))
|
||||
steps.push(animation.create_stop_step(anim1))
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(anim1, 1000)
|
||||
.push_wait_step(500)
|
||||
.push_play_step(anim2, 2000)
|
||||
|
||||
# Test sequence start
|
||||
tasmota.set_millis(10000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(10000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
seq_manager.start()
|
||||
|
||||
assert(seq_manager.is_running == true, "Sequence should be running after start")
|
||||
assert(seq_manager.steps.size() == 4, "Sequence should have 4 steps")
|
||||
assert(seq_manager.steps.size() == 3, "Sequence should have 3 steps")
|
||||
assert(seq_manager.step_index == 0, "Should start at step 0")
|
||||
|
||||
# Check that first animation was started
|
||||
@ -134,38 +139,37 @@ def test_sequence_manager_timing()
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
|
||||
# Create simple sequence with timed steps
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, 1000)) # 1 second
|
||||
steps.push(animation.create_wait_step(500)) # 0.5 seconds
|
||||
|
||||
# Create simple sequence with timed steps using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000) # 1 second
|
||||
.push_wait_step(500) # 0.5 seconds
|
||||
|
||||
# Start sequence at time 20000
|
||||
tasmota.set_millis(20000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(20000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
|
||||
# Update immediately - should still be on first step
|
||||
seq_manager.update()
|
||||
seq_manager.update(engine.time_ms)
|
||||
assert(seq_manager.step_index == 0, "Should still be on first step immediately")
|
||||
assert(seq_manager.is_running == true, "Sequence should still be running")
|
||||
|
||||
|
||||
# Update after 500ms - should still be on first step
|
||||
tasmota.set_millis(20500)
|
||||
engine.on_tick(20500) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(engine.time_ms)
|
||||
assert(seq_manager.step_index == 0, "Should still be on first step after 500ms")
|
||||
|
||||
|
||||
# Update after 1000ms - should advance to second step (wait)
|
||||
tasmota.set_millis(21000)
|
||||
engine.on_tick(21000) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(engine.time_ms)
|
||||
assert(seq_manager.step_index == 1, "Should advance to second step after 1000ms")
|
||||
|
||||
# Update after additional 500ms - should complete sequence
|
||||
tasmota.set_millis(21500)
|
||||
engine.on_tick(21500) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(engine.time_ms)
|
||||
assert(seq_manager.is_running == false, "Sequence should complete after all steps")
|
||||
|
||||
print("✓ Timing tests passed")
|
||||
@ -192,15 +196,15 @@ def test_sequence_manager_step_info()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, 2000))
|
||||
steps.push(animation.create_wait_step(1000))
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 2000)
|
||||
.push_wait_step(1000)
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(30000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(30000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
|
||||
# Get step info
|
||||
step_info = seq_manager.get_current_step_info()
|
||||
@ -230,18 +234,18 @@ def test_sequence_manager_stop()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, 5000))
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 5000)
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(40000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(40000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
seq_manager.start()
|
||||
assert(seq_manager.is_running == true, "Sequence should be running")
|
||||
|
||||
# Stop sequence
|
||||
seq_manager.stop_sequence()
|
||||
seq_manager.stop()
|
||||
assert(seq_manager.is_running == false, "Sequence should not be running after stop")
|
||||
assert(engine.size() == 0, "Engine should have no animations after stop")
|
||||
|
||||
@ -268,24 +272,83 @@ def test_sequence_manager_is_running()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, 1000))
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000)
|
||||
|
||||
tasmota.set_millis(50000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(50000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
assert(seq_manager.is_sequence_running() == true, "Sequence should be running after start")
|
||||
|
||||
# Complete sequence
|
||||
tasmota.set_millis(51000)
|
||||
engine.on_tick(51000) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(engine.time_ms)
|
||||
assert(seq_manager.is_sequence_running() == false, "Sequence should not be running after completion")
|
||||
|
||||
print("✓ Running state tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_assignment_steps()
|
||||
print("=== SequenceManager Assignment Steps Tests ===")
|
||||
|
||||
# Create strip and engine
|
||||
var strip = global.Leds(30)
|
||||
var engine = animation.create_engine(strip)
|
||||
var seq_manager = animation.SequenceManager(engine)
|
||||
|
||||
# Create test animation using new parameterized API
|
||||
var color_provider = animation.static_color(engine)
|
||||
color_provider.color = 0xFFFF0000
|
||||
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"
|
||||
test_anim.opacity = 255 # Initial opacity
|
||||
|
||||
# Create brightness value provider for assignment
|
||||
var brightness_provider = animation.static_value(engine)
|
||||
brightness_provider.value = 128
|
||||
|
||||
# Create assignment closure that changes animation opacity
|
||||
var assignment_closure = def (engine) test_anim.opacity = brightness_provider.produce_value("value", engine.time_ms) end
|
||||
|
||||
# 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_play_step(test_anim, 500) # Play for another 0.5s
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(80000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(80000) # Update engine time
|
||||
|
||||
# Verify initial state
|
||||
assert(seq_manager.is_running == true, "Sequence should be running")
|
||||
assert(seq_manager.step_index == 0, "Should start at step 0")
|
||||
assert(test_anim.opacity == 255, "Animation should have initial opacity")
|
||||
|
||||
# Advance past assignment step (after 500ms)
|
||||
# Assignment steps are executed atomically and advance immediately
|
||||
tasmota.set_millis(80502)
|
||||
engine.on_tick(80502) # Update engine time
|
||||
seq_manager.update(80502)
|
||||
assert(seq_manager.step_index == 2, "Should advance past assignment step immediately")
|
||||
assert(test_anim.opacity == 128, "Animation opacity should be changed by assignment")
|
||||
|
||||
# Complete sequence (second play step should finish after 500ms more)
|
||||
tasmota.set_millis(81002) # 80502 + 500ms = 81002
|
||||
engine.on_tick(81002) # Update engine time
|
||||
seq_manager.update(81002)
|
||||
assert(seq_manager.is_running == false, "Sequence should complete")
|
||||
|
||||
print("✓ Assignment steps tests passed")
|
||||
end
|
||||
|
||||
def test_sequence_manager_complex_sequence()
|
||||
print("=== SequenceManager Complex Sequence Tests ===")
|
||||
|
||||
@ -322,61 +385,46 @@ def test_sequence_manager_complex_sequence()
|
||||
blue_anim.loop = true
|
||||
blue_anim.name = "blue"
|
||||
|
||||
# Create complex sequence
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(red_anim, 1000)) # Play red for 1s
|
||||
steps.push(animation.create_play_step(green_anim, 800)) # Play green for 0.8s
|
||||
steps.push(animation.create_wait_step(200)) # Wait 0.2s
|
||||
steps.push(animation.create_play_step(blue_anim, 1500)) # Play blue for 1.5s
|
||||
steps.push(animation.create_stop_step(red_anim)) # Stop red
|
||||
steps.push(animation.create_stop_step(green_anim)) # Stop green
|
||||
# Create complex sequence using fluent interface
|
||||
seq_manager.push_play_step(red_anim, 1000) # Play red for 1s
|
||||
.push_play_step(green_anim, 800) # Play green for 0.8s
|
||||
.push_wait_step(200) # Wait 0.2s
|
||||
.push_play_step(blue_anim, 1500) # Play blue for 1.5s
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(60000)
|
||||
engine.add_sequence_manager(seq_manager)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(60000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
|
||||
# Test sequence progression step by step
|
||||
|
||||
# After 1000ms: red completes, should advance to green (step 1)
|
||||
tasmota.set_millis(61000)
|
||||
engine.on_tick(61000) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(61000)
|
||||
assert(seq_manager.step_index == 1, "Should advance to step 1 (green) after red completes")
|
||||
assert(seq_manager.is_running == true, "Sequence should still be running")
|
||||
|
||||
# After 1800ms: green completes, should advance to wait (step 2)
|
||||
tasmota.set_millis(61800)
|
||||
engine.on_tick(61800) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(61800)
|
||||
assert(seq_manager.step_index == 2, "Should advance to step 2 (wait) after green completes")
|
||||
assert(seq_manager.is_running == true, "Sequence should still be running")
|
||||
|
||||
# After 2000ms: wait completes, should advance to blue (step 3)
|
||||
tasmota.set_millis(62000)
|
||||
engine.on_tick(62000) # Update engine time
|
||||
seq_manager.update()
|
||||
seq_manager.update(62000)
|
||||
assert(seq_manager.step_index == 3, "Should advance to step 3 (blue) after wait completes")
|
||||
assert(seq_manager.is_running == true, "Sequence should still be running")
|
||||
|
||||
# After 3500ms: blue completes, should advance to stop red (step 4)
|
||||
# After 3500ms: blue completes, sequence should complete (we removed stop steps)
|
||||
tasmota.set_millis(63500)
|
||||
engine.on_tick(63500) # Update engine time
|
||||
seq_manager.update()
|
||||
assert(seq_manager.step_index == 4, "Should advance to step 4 (stop red) after blue completes")
|
||||
assert(seq_manager.is_running == true, "Sequence should still be running")
|
||||
|
||||
# Stop steps execute immediately, so another update should advance to step 5 and then complete
|
||||
seq_manager.update()
|
||||
|
||||
# The sequence should complete when step_index reaches the end
|
||||
if seq_manager.is_running
|
||||
# If still running, do one more update to complete
|
||||
seq_manager.update()
|
||||
end
|
||||
|
||||
assert(seq_manager.is_running == false, "Complex sequence should complete after all stop steps")
|
||||
seq_manager.update(63500)
|
||||
assert(seq_manager.is_running == false, "Complex sequence should complete after blue step")
|
||||
|
||||
print("✓ Complex sequence tests passed")
|
||||
end
|
||||
@ -401,25 +449,21 @@ def test_sequence_manager_integration()
|
||||
test_anim.duration = 0
|
||||
test_anim.loop = true
|
||||
test_anim.name = "test"
|
||||
var steps = []
|
||||
steps.push(animation.create_play_step(test_anim, 1000))
|
||||
# Create sequence using fluent interface
|
||||
seq_manager.push_play_step(test_anim, 1000)
|
||||
|
||||
# Start sequence
|
||||
tasmota.set_millis(70000)
|
||||
engine.start() # Start the engine
|
||||
engine.on_tick(70000) # Update engine time
|
||||
seq_manager.start_sequence(steps)
|
||||
|
||||
# The engine should automatically start the sequence manager when engine.start() is called
|
||||
assert(seq_manager.is_running == true, "Sequence should be running after engine start")
|
||||
|
||||
# Test that engine's on_tick calls sequence manager update
|
||||
# The engine has a 5ms minimum delta check, so we need to account for that
|
||||
tasmota.set_millis(71000)
|
||||
|
||||
# Start the engine to initialize last_update
|
||||
engine.start()
|
||||
engine.on_tick(70000) # Initialize last_update
|
||||
|
||||
# Now call on_tick after the sequence should complete
|
||||
engine.on_tick(71000) # This should call seq_manager.update()
|
||||
# After 1 second, the sequence should complete
|
||||
tasmota.set_millis(71005) # Add 5ms buffer for engine's minimum delta check
|
||||
engine.on_tick(71005) # This should call seq_manager.update()
|
||||
|
||||
# The sequence should complete after the 1-second duration
|
||||
assert(seq_manager.is_running == false, "Sequence should complete after 1 second duration")
|
||||
@ -442,6 +486,7 @@ def run_all_sequence_manager_tests()
|
||||
test_sequence_manager_step_info()
|
||||
test_sequence_manager_stop()
|
||||
test_sequence_manager_is_running()
|
||||
test_sequence_manager_assignment_steps()
|
||||
test_sequence_manager_complex_sequence()
|
||||
test_sequence_manager_integration()
|
||||
|
||||
@ -461,6 +506,7 @@ return {
|
||||
"test_sequence_manager_step_info": test_sequence_manager_step_info,
|
||||
"test_sequence_manager_stop": test_sequence_manager_stop,
|
||||
"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
|
||||
}
|
||||
@ -175,7 +175,7 @@ def test_complex_forward_references()
|
||||
assert(string.find(berry_code, "var primary_color_") >= 0, "Should define primary color")
|
||||
assert(string.find(berry_code, "var gradient_pattern_") >= 0, "Should define gradient pattern")
|
||||
assert(string.find(berry_code, "var complex_anim_") >= 0, "Should define complex animation")
|
||||
assert(string.find(berry_code, "var demo_ = (def (engine)") >= 0, "Should define sequence")
|
||||
assert(string.find(berry_code, "var demo_ = animation.SequenceManager(engine)") >= 0, "Should define sequence")
|
||||
|
||||
print("✓ Complex forward references test passed")
|
||||
return true
|
||||
|
||||
@ -46,6 +46,7 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/frame_buffer_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/nillable_parameter_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/parameterized_object_test.be", # Tests parameter management base class
|
||||
"lib/libesp32/berry_animation/src/tests/bytes_type_test.be", # Tests bytes type validation in parameterized objects
|
||||
"lib/libesp32/berry_animation/src/tests/animation_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/animation_engine_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/fast_loop_integration_test.be",
|
||||
@ -57,6 +58,7 @@ def run_all_tests()
|
||||
"lib/libesp32/berry_animation/src/tests/pulse_animation_test.be",
|
||||
"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/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",
|
||||
|
||||
@ -7,5 +7,56 @@ def rand_demo(engine)
|
||||
return math.rand() % 256
|
||||
end
|
||||
|
||||
# Factory function for rainbow palette
|
||||
#
|
||||
# @param engine: AnimationEngine - Animation engine reference (required for user function signature)
|
||||
# @param num_colors: int - Number of colors in the rainbow (default: 6)
|
||||
# @return bytes - A palette object containing rainbow colors in VRGB format
|
||||
def color_wheel_palette(engine, num_colors)
|
||||
# Default parameters
|
||||
if num_colors == nil || num_colors < 2
|
||||
num_colors = 6
|
||||
end
|
||||
|
||||
# Create a rainbow palette as bytes object
|
||||
var palette = bytes()
|
||||
var i = 0
|
||||
while i < num_colors
|
||||
# Calculate hue (0 to 360 degrees)
|
||||
var hue = tasmota.scale_uint(i, 0, num_colors, 0, 360)
|
||||
|
||||
# Convert HSV to RGB (simplified conversion)
|
||||
var r, g, b
|
||||
var h_section = (hue / 60) % 6
|
||||
var f = (hue / 60) - h_section
|
||||
var v = 255 # Value (brightness)
|
||||
var p = 0 # Saturation is 100%, so p = 0
|
||||
var q = int(v * (1 - f))
|
||||
var t = int(v * f)
|
||||
|
||||
if h_section == 0
|
||||
r = v; g = t; b = p
|
||||
elif h_section == 1
|
||||
r = q; g = v; b = p
|
||||
elif h_section == 2
|
||||
r = p; g = v; b = t
|
||||
elif h_section == 3
|
||||
r = p; g = q; b = v
|
||||
elif h_section == 4
|
||||
r = t; g = p; b = v
|
||||
else
|
||||
r = v; g = p; b = q
|
||||
end
|
||||
|
||||
# Create ARGB color (fully opaque) and add to palette
|
||||
var color = (255 << 24) | (r << 16) | (g << 8) | b
|
||||
palette.add(color, -4) # Add as 4-byte big-endian
|
||||
i += 1
|
||||
end
|
||||
|
||||
return palette
|
||||
end
|
||||
|
||||
# Register all user functions with the animation module
|
||||
animation.register_user_function("rand_demo", rand_demo)
|
||||
animation.register_user_function("color_wheel_palette", color_wheel_palette)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user