Berry animation template (#23851)
This commit is contained in:
parent
5a08bc11e3
commit
d5114c32c5
@ -63,6 +63,32 @@ animation sunset_glow = rich_palette(
|
||||
run sunset_glow
|
||||
```
|
||||
|
||||
### Reusable Templates
|
||||
|
||||
Create parameterized animation patterns that can be reused with different settings:
|
||||
|
||||
```berry
|
||||
# Define a reusable template
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
opacity=brightness
|
||||
)
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template with different parameters
|
||||
pulse_effect(red, 2s, 255) # Bright red pulse
|
||||
pulse_effect(blue, 1s, 150) # Dimmer blue pulse
|
||||
pulse_effect(0xFF69B4, 3s, 200) # Hot pink pulse
|
||||
```
|
||||
|
||||
### Animation Sequences
|
||||
|
||||
```berry
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: cylon_generic.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: cylon_effect
|
||||
def cylon_effect_template(engine, eye_color_, back_color_, duration_)
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var eye_animation_ = animation.beacon_animation(engine)
|
||||
eye_animation_.color = eye_color_
|
||||
eye_animation_.back_color = back_color_
|
||||
eye_animation_.pos = (def (engine)
|
||||
var provider = animation.cosine_osc(engine)
|
||||
provider.min_value = (-1)
|
||||
provider.max_value = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) - 2 end)
|
||||
provider.duration = duration_
|
||||
return provider
|
||||
end)(engine)
|
||||
eye_animation_.beacon_size = 3 # small 3 pixels eye
|
||||
eye_animation_.slew_size = 2 # with 2 pixel shading around
|
||||
eye_animation_.priority = 5
|
||||
engine.add_animation(eye_animation_)
|
||||
end
|
||||
|
||||
animation.register_user_function('cylon_effect', cylon_effect_template)
|
||||
|
||||
cylon_effect_template(engine, 0xFFFF0000, 0x00000000, 3000)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
template cylon_effect {
|
||||
param eye_color type color
|
||||
param back_color type color
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
animation eye_animation = beacon_animation(
|
||||
color = eye_color
|
||||
back_color = back_color
|
||||
pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = duration)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 5
|
||||
)
|
||||
|
||||
run eye_animation
|
||||
}
|
||||
|
||||
cylon_effect(red, transparent, 3s)
|
||||
|
||||
-#
|
||||
@ -0,0 +1,62 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: import_demo.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Import Demo - Demonstrates DSL import functionality
|
||||
# This example shows how to import user functions and use them in animations
|
||||
# Import user functions module
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
import user_functions
|
||||
# Create animations that use imported user functions
|
||||
var random_red_ = animation.solid(engine)
|
||||
random_red_.color = 0xFFFF0000
|
||||
random_red_.opacity = animation.create_closure_value(engine, def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
||||
var breathing_blue_ = animation.solid(engine)
|
||||
breathing_blue_.color = 0xFF0000FF
|
||||
breathing_blue_.opacity = animation.create_closure_value(engine, def (self) return self.max(50, self.min(255, animation.get_user_function('rand_demo')(self.engine) + 100)) end)
|
||||
var dynamic_green_ = animation.solid(engine)
|
||||
dynamic_green_.color = 0xFF008000
|
||||
dynamic_green_.opacity = animation.create_closure_value(engine, def (self) return self.abs(animation.get_user_function('rand_demo')(self.engine) - 128) + 64 end)
|
||||
# Create a sequence that cycles through the animations
|
||||
var import_demo_ = animation.SequenceManager(engine)
|
||||
.push_play_step(random_red_, 3000)
|
||||
.push_play_step(breathing_blue_, 3000)
|
||||
.push_play_step(dynamic_green_, 3000)
|
||||
# Run the demo
|
||||
engine.add_sequence_manager(import_demo_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Import Demo - Demonstrates DSL import functionality
|
||||
# This example shows how to import user functions and use them in animations
|
||||
|
||||
# Import user functions module
|
||||
import user_functions
|
||||
|
||||
# Create animations that use imported user functions
|
||||
animation random_red = solid(color=red)
|
||||
random_red.opacity = user.rand_demo()
|
||||
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
|
||||
animation dynamic_green = solid(color=green)
|
||||
dynamic_green.opacity = abs(user.rand_demo() - 128) + 64
|
||||
|
||||
# Create a sequence that cycles through the animations
|
||||
sequence import_demo {
|
||||
play random_red for 3s
|
||||
play breathing_blue for 3s
|
||||
play dynamic_green for 3s
|
||||
}
|
||||
|
||||
# Run the demo
|
||||
run import_demo
|
||||
-#
|
||||
@ -0,0 +1,41 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: pattern_fire.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Pattern fire.anim
|
||||
# Define fire palette from black to yellow
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
var fire_colors_ = bytes("FF800000" "FFFF0000" "FFFF4500" "FFFFFF00")
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
var fire_color_ = animation.rich_palette(engine)
|
||||
fire_color_.palette = fire_colors_
|
||||
var fire_pattern_ = animation.palette_gradient_animation(engine)
|
||||
fire_pattern_.color_source = fire_color_
|
||||
fire_pattern_.spatial_period = animation.create_closure_value(engine, def (self) return self.resolve(strip_len_) / 2 end)
|
||||
engine.add_animation(fire_pattern_)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Pattern fire.anim
|
||||
|
||||
# Define fire palette from black to yellow
|
||||
palette fire_colors = [
|
||||
0x800000 # Dark red
|
||||
0xFF0000 # Red
|
||||
0xFF4500 # Orange red
|
||||
0xFFFF00 # Yellow
|
||||
]
|
||||
|
||||
set strip_len = strip_length()
|
||||
color fire_color = rich_palette(palette=fire_colors)
|
||||
animation fire_pattern = palette_gradient_animation(color_source=fire_color, spatial_period=strip_len/2)
|
||||
|
||||
run fire_pattern
|
||||
-#
|
||||
@ -0,0 +1,88 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_complex_template.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Complex template test
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: rainbow_pulse
|
||||
def rainbow_pulse_template(engine, pal1_, pal2_, duration_, back_color_)
|
||||
var cycle_color_ = animation.color_cycle(engine)
|
||||
cycle_color_.palette = pal1_
|
||||
cycle_color_.cycle_period = duration_
|
||||
# Create pulsing animation
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = cycle_color_
|
||||
pulse_.period = duration_
|
||||
# Create background
|
||||
var background_ = animation.solid(engine)
|
||||
background_.color = back_color_
|
||||
background_.priority = 1
|
||||
# Set pulse priority higher
|
||||
pulse_.priority = 10
|
||||
# Run both animations
|
||||
engine.add_animation(background_)
|
||||
engine.add_animation(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('rainbow_pulse', rainbow_pulse_template)
|
||||
|
||||
# Create palettes
|
||||
var fire_palette_ = bytes("00000000" "80FF0000" "FFFFFF00")
|
||||
var ocean_palette_ = bytes("00000080" "800080FF" "FF00FFFF")
|
||||
# Use the template
|
||||
rainbow_pulse_template(engine, fire_palette_, ocean_palette_, 3000, 0xFF001100)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Complex template test
|
||||
|
||||
template rainbow_pulse {
|
||||
param pal1 type palette
|
||||
param pal2 type palette
|
||||
param duration
|
||||
param back_color type color
|
||||
|
||||
# Create color cycle using first palette
|
||||
color cycle_color = color_cycle(palette=pal1, cycle_period=duration)
|
||||
|
||||
# Create pulsing animation
|
||||
animation pulse = pulsating_animation(
|
||||
color=cycle_color
|
||||
period=duration
|
||||
)
|
||||
|
||||
# Create background
|
||||
animation background = solid(color=back_color)
|
||||
background.priority = 1
|
||||
|
||||
# Set pulse priority higher
|
||||
pulse.priority = 10
|
||||
|
||||
# Run both animations
|
||||
run background
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Create palettes
|
||||
palette fire_palette = [
|
||||
(0, 0x000000)
|
||||
(128, 0xFF0000)
|
||||
(255, 0xFFFF00)
|
||||
]
|
||||
|
||||
palette ocean_palette = [
|
||||
(0, 0x000080)
|
||||
(128, 0x0080FF)
|
||||
(255, 0x00FFFF)
|
||||
]
|
||||
|
||||
# Use the template
|
||||
rainbow_pulse(fire_palette, ocean_palette, 3s, 0x001100)
|
||||
-#
|
||||
@ -0,0 +1,50 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_template_simple.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Test template functionality
|
||||
# Define a simple template
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: pulse_effect
|
||||
def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Test template functionality
|
||||
|
||||
# Define a simple template
|
||||
template pulse_effect {
|
||||
param base_color type color
|
||||
param duration
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=base_color
|
||||
period=duration
|
||||
)
|
||||
pulse.opacity = brightness
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect(red, 2s, 80%)
|
||||
-#
|
||||
@ -0,0 +1,50 @@
|
||||
# Generated Berry code from Animation DSL
|
||||
# Source: test_template_simple_reusable.anim
|
||||
#
|
||||
# This file was automatically generated by compile_all_examples.sh
|
||||
# Do not edit manually - changes will be overwritten
|
||||
|
||||
import animation
|
||||
|
||||
# Test template functionality
|
||||
# Define a simple template
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
# Template function: pulse_effect
|
||||
def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect_template(engine, 0xFFFF0000, 2000, 204)
|
||||
engine.start()
|
||||
|
||||
|
||||
#- Original DSL source:
|
||||
# Test template functionality
|
||||
|
||||
# Define a simple template
|
||||
template pulse_effect {
|
||||
param base_color type color
|
||||
param duration
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=base_color
|
||||
period=duration
|
||||
)
|
||||
pulse.opacity = brightness
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect(red, 2s, 80%)
|
||||
-#
|
||||
@ -8,10 +8,11 @@ import animation
|
||||
|
||||
# User Functions Demo - Advanced Computed Parameters
|
||||
# Shows how to use user functions in computed parameters via property assignments
|
||||
# Get the current strip length for calculations
|
||||
# Auto-generated strip initialization (using Tasmota configuration)
|
||||
var engine = animation.init_strip()
|
||||
|
||||
import user_functions
|
||||
# Get the current strip length for calculations
|
||||
var strip_len_ = animation.strip_length(engine)
|
||||
# Example 1: Simple user function in computed parameter
|
||||
var random_base_ = animation.solid(engine)
|
||||
@ -56,6 +57,8 @@ engine.start()
|
||||
# User Functions Demo - Advanced Computed Parameters
|
||||
# Shows how to use user functions in computed parameters via property assignments
|
||||
|
||||
import user_functions
|
||||
|
||||
# Get the current strip length for calculations
|
||||
set strip_len = strip_length()
|
||||
|
||||
@ -65,7 +68,7 @@ animation random_base = solid(
|
||||
priority=10
|
||||
)
|
||||
# Use user function in property assignment
|
||||
random_base.opacity = rand_demo()
|
||||
random_base.opacity = user.rand_demo()
|
||||
|
||||
# Example 2: User function with mathematical operations
|
||||
animation random_bounded = solid(
|
||||
@ -73,7 +76,7 @@ animation random_bounded = solid(
|
||||
priority=8
|
||||
)
|
||||
# User function with bounds using math functions
|
||||
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
|
||||
# Example 3: User function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -81,7 +84,7 @@ animation random_variation = solid(
|
||||
priority=15
|
||||
)
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
random_variation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
|
||||
# Example 4: User function affecting different properties
|
||||
animation random_multi = solid(
|
||||
@ -89,7 +92,7 @@ animation random_multi = solid(
|
||||
priority=12
|
||||
)
|
||||
# Use user function for multiple properties
|
||||
random_multi.opacity = max(100, rand_demo())
|
||||
random_multi.opacity = max(100, user.rand_demo())
|
||||
|
||||
# Example 5: Complex expression with user function
|
||||
animation random_complex = solid(
|
||||
@ -97,7 +100,7 @@ animation random_complex = solid(
|
||||
priority=20
|
||||
)
|
||||
# Complex expression with user function and math operations
|
||||
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))
|
||||
|
||||
# Run all animations to demonstrate the effects
|
||||
run random_base
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
template cylon_effect {
|
||||
param eye_color type color
|
||||
param back_color type color
|
||||
param duration
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
animation eye_animation = beacon_animation(
|
||||
color = eye_color
|
||||
back_color = back_color
|
||||
pos = cosine_osc(min_value = -1, max_value = strip_len - 2, duration = duration)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 5
|
||||
)
|
||||
|
||||
run eye_animation
|
||||
}
|
||||
|
||||
cylon_effect(red, transparent, 3s)
|
||||
@ -1,23 +0,0 @@
|
||||
# Cylon Red Eye
|
||||
# Automatically adapts to the length of the strip
|
||||
|
||||
set strip_len = strip_length()
|
||||
|
||||
animation red_eye = beacon_animation(
|
||||
color = red
|
||||
pos = cosine_osc(min_value = 0, max_value = strip_len - 2, duration = 5s)
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 10
|
||||
)
|
||||
|
||||
animation green_eye = beacon_animation(
|
||||
color = green
|
||||
pos = strip_len - red_eye.pos
|
||||
beacon_size = 3 # small 3 pixels eye
|
||||
slew_size = 2 # with 2 pixel shading around
|
||||
priority = 15 # behind red eye
|
||||
)
|
||||
|
||||
run red_eye
|
||||
run green_eye
|
||||
25
lib/libesp32/berry_animation/anim_examples/import_demo.anim
Normal file
25
lib/libesp32/berry_animation/anim_examples/import_demo.anim
Normal file
@ -0,0 +1,25 @@
|
||||
# Import Demo - Demonstrates DSL import functionality
|
||||
# This example shows how to import user functions and use them in animations
|
||||
|
||||
# Import user functions module
|
||||
import user_functions
|
||||
|
||||
# Create animations that use imported user functions
|
||||
animation random_red = solid(color=red)
|
||||
random_red.opacity = user.rand_demo()
|
||||
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
|
||||
animation dynamic_green = solid(color=green)
|
||||
dynamic_green.opacity = abs(user.rand_demo() - 128) + 64
|
||||
|
||||
# Create a sequence that cycles through the animations
|
||||
sequence import_demo {
|
||||
play random_red for 3s
|
||||
play breathing_blue for 3s
|
||||
play dynamic_green for 3s
|
||||
}
|
||||
|
||||
# Run the demo
|
||||
run import_demo
|
||||
15
lib/libesp32/berry_animation/anim_examples/pattern_fire.anim
Normal file
15
lib/libesp32/berry_animation/anim_examples/pattern_fire.anim
Normal file
@ -0,0 +1,15 @@
|
||||
# Pattern fire.anim
|
||||
|
||||
# Define fire palette from black to yellow
|
||||
palette fire_colors = [
|
||||
0x800000 # Dark red
|
||||
0xFF0000 # Red
|
||||
0xFF4500 # Orange red
|
||||
0xFFFF00 # Yellow
|
||||
]
|
||||
|
||||
set strip_len = strip_length()
|
||||
color fire_color = rich_palette(palette=fire_colors)
|
||||
animation fire_pattern = palette_gradient_animation(color_source=fire_color, spatial_period=strip_len/2)
|
||||
|
||||
run fire_pattern
|
||||
@ -0,0 +1,44 @@
|
||||
# Complex template test
|
||||
|
||||
template rainbow_pulse {
|
||||
param pal1 type palette
|
||||
param pal2 type palette
|
||||
param duration
|
||||
param back_color type color
|
||||
|
||||
# Create color cycle using first palette
|
||||
color cycle_color = color_cycle(palette=pal1, cycle_period=duration)
|
||||
|
||||
# Create pulsing animation
|
||||
animation pulse = pulsating_animation(
|
||||
color=cycle_color
|
||||
period=duration
|
||||
)
|
||||
|
||||
# Create background
|
||||
animation background = solid(color=back_color)
|
||||
background.priority = 1
|
||||
|
||||
# Set pulse priority higher
|
||||
pulse.priority = 10
|
||||
|
||||
# Run both animations
|
||||
run background
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Create palettes
|
||||
palette fire_palette = [
|
||||
(0, 0x000000)
|
||||
(128, 0xFF0000)
|
||||
(255, 0xFFFF00)
|
||||
]
|
||||
|
||||
palette ocean_palette = [
|
||||
(0, 0x000080)
|
||||
(128, 0x0080FF)
|
||||
(255, 0x00FFFF)
|
||||
]
|
||||
|
||||
# Use the template
|
||||
rainbow_pulse(fire_palette, ocean_palette, 3s, 0x001100)
|
||||
@ -0,0 +1,19 @@
|
||||
# Test template functionality
|
||||
|
||||
# Define a simple template
|
||||
template pulse_effect {
|
||||
param base_color type color
|
||||
param duration
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=base_color
|
||||
period=duration
|
||||
)
|
||||
pulse.opacity = brightness
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect(red, 2s, 80%)
|
||||
@ -0,0 +1,19 @@
|
||||
# Test template functionality
|
||||
|
||||
# Define a simple template
|
||||
template pulse_effect {
|
||||
param base_color type color
|
||||
param duration
|
||||
param brightness
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=base_color
|
||||
period=duration
|
||||
)
|
||||
pulse.opacity = brightness
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template - templates add animations directly to engine and run them
|
||||
pulse_effect(red, 2s, 80%)
|
||||
@ -1,6 +1,8 @@
|
||||
# User Functions Demo - Advanced Computed Parameters
|
||||
# Shows how to use user functions in computed parameters via property assignments
|
||||
|
||||
import user_functions
|
||||
|
||||
# Get the current strip length for calculations
|
||||
set strip_len = strip_length()
|
||||
|
||||
@ -10,7 +12,7 @@ animation random_base = solid(
|
||||
priority=10
|
||||
)
|
||||
# Use user function in property assignment
|
||||
random_base.opacity = rand_demo()
|
||||
random_base.opacity = user.rand_demo()
|
||||
|
||||
# Example 2: User function with mathematical operations
|
||||
animation random_bounded = solid(
|
||||
@ -18,7 +20,7 @@ animation random_bounded = solid(
|
||||
priority=8
|
||||
)
|
||||
# User function with bounds using math functions
|
||||
random_bounded.opacity = max(50, min(255, rand_demo() + 100))
|
||||
random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
|
||||
# Example 3: User function in arithmetic expressions
|
||||
animation random_variation = solid(
|
||||
@ -26,7 +28,7 @@ animation random_variation = solid(
|
||||
priority=15
|
||||
)
|
||||
# Mix user function with arithmetic operations
|
||||
random_variation.opacity = abs(rand_demo() - 128) + 64
|
||||
random_variation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
|
||||
# Example 4: User function affecting different properties
|
||||
animation random_multi = solid(
|
||||
@ -34,7 +36,7 @@ animation random_multi = solid(
|
||||
priority=12
|
||||
)
|
||||
# Use user function for multiple properties
|
||||
random_multi.opacity = max(100, rand_demo())
|
||||
random_multi.opacity = max(100, user.rand_demo())
|
||||
|
||||
# Example 5: Complex expression with user function
|
||||
animation random_complex = solid(
|
||||
@ -42,7 +44,7 @@ animation random_complex = solid(
|
||||
priority=20
|
||||
)
|
||||
# Complex expression with user function and math operations
|
||||
random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))
|
||||
random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))
|
||||
|
||||
# Run all animations to demonstrate the effects
|
||||
run random_base
|
||||
|
||||
@ -73,8 +73,8 @@ Unified base class for all visual elements. Inherits from `ParameterizedObject`.
|
||||
| `is_running` | bool | false | - | Whether the animation is active |
|
||||
| `priority` | int | 10 | 0-255 | Rendering priority (higher = on top) |
|
||||
| `duration` | int | 0 | min: 0 | Animation duration in ms (0 = infinite) |
|
||||
| `loop` | bool | true | - | Whether to loop when duration is reached |
|
||||
| `opacity` | int | 255 | 0-255 | Animation opacity/brightness |
|
||||
| `loop` | bool | false | - | Whether to loop when duration is reached |
|
||||
| `opacity` | any | 255 | - | Animation opacity (number, FrameBuffer, or Animation) |
|
||||
| `color` | int | 0xFFFFFFFF | - | Base color in ARGB format |
|
||||
|
||||
**Special Behavior**: Setting `is_running = true/false` starts/stops the animation.
|
||||
@ -1213,14 +1213,20 @@ animation zoomed_sparkles = scale_animation(
|
||||
|
||||
### PalettePatternAnimation
|
||||
|
||||
Applies colors from a color provider to specific patterns. Inherits from `Animation`.
|
||||
Applies colors from a color provider to specific patterns using an efficient bytes() buffer. Inherits from `Animation`.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `color_source` | instance | nil | - | Color provider for pattern mapping |
|
||||
| `pattern_func` | function | nil | - | Function that generates pattern values |
|
||||
| `pattern_func` | function | nil | - | Function that generates pattern values (0-255) for each pixel |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
**Implementation Details:**
|
||||
- Uses `bytes()` buffer for efficient storage of per-pixel values
|
||||
- Pattern function should return values in 0-255 range
|
||||
- Color source receives values in 0-255 range via `get_color_for_value(value, time_ms)`
|
||||
- Buffer automatically resizes when strip length changes
|
||||
|
||||
**Factory**: `animation.palette_pattern_animation(engine)`
|
||||
|
||||
### PaletteWaveAnimation
|
||||
@ -1233,6 +1239,11 @@ Creates sine wave patterns with palette colors. Inherits from `PalettePatternAni
|
||||
| `wave_length` | int | 10 | min: 1 | Wave length in pixels |
|
||||
| *(inherits all PalettePatternAnimation parameters)* | | | | |
|
||||
|
||||
**Pattern Generation:**
|
||||
- Generates sine wave values in 0-255 range using `tasmota.sine_int()`
|
||||
- Wave position advances based on `wave_period` timing
|
||||
- Each pixel's value calculated as: `sine_value = tasmota.scale_int(sine_int(angle), -4096, 4096, 0, 255)`
|
||||
|
||||
**Factory**: `animation.palette_wave_animation(engine)`
|
||||
|
||||
### PaletteGradientAnimation
|
||||
@ -1241,9 +1252,22 @@ Creates shifting gradient patterns with palette colors. Inherits from `PalettePa
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `shift_period` | int | 10000 | min: 1 | Gradient shift period in ms |
|
||||
| `shift_period` | int | 10000 | min: 0 | Time for one complete shift cycle in ms (0 = static gradient) |
|
||||
| `spatial_period` | int | 0 | min: 0 | Spatial period in pixels (0 = full strip length) |
|
||||
| `phase_shift` | int | 0 | 0-100 | Phase shift as percentage of spatial period |
|
||||
| *(inherits all PalettePatternAnimation parameters)* | | | | |
|
||||
|
||||
**Pattern Generation:**
|
||||
- Generates linear gradient values in 0-255 range across the specified spatial period
|
||||
- **shift_period**: Controls temporal movement - how long it takes for the gradient to shift one full spatial period
|
||||
- `0`: Static gradient (no movement)
|
||||
- `> 0`: Moving gradient with specified period in milliseconds
|
||||
- **spatial_period**: Controls spatial repetition - how many pixels before the gradient pattern repeats
|
||||
- `0`: Gradient spans the full strip length (single gradient across entire strip)
|
||||
- `> 0`: Gradient repeats every N pixels
|
||||
- **phase_shift**: Shifts the gradient pattern spatially by a percentage of the spatial period
|
||||
- Each pixel's value calculated as: `value = tasmota.scale_uint(spatial_position, 0, spatial_period-1, 0, 255)`
|
||||
|
||||
**Factory**: `animation.palette_gradient_animation(engine)`
|
||||
|
||||
### PaletteMeterAnimation
|
||||
@ -1252,9 +1276,14 @@ Creates meter/bar patterns based on a value function. Inherits from `PalettePatt
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `value_func` | function | nil | - | Function that provides meter values |
|
||||
| `value_func` | function | nil | - | Function that provides meter values (0-100 range) |
|
||||
| *(inherits all PalettePatternAnimation parameters)* | | | | |
|
||||
|
||||
**Pattern Generation:**
|
||||
- Value function returns percentage (0-100) representing meter level
|
||||
- Pixels within meter range get value 255, others get value 0
|
||||
- Meter position calculated as: `position = tasmota.scale_uint(value, 0, 100, 0, strip_length)`
|
||||
|
||||
**Factory**: `animation.palette_meter_animation(engine)`
|
||||
|
||||
## Motion Effects
|
||||
|
||||
@ -291,7 +291,7 @@ def render(frame, time_ms)
|
||||
frame.set_pixel_color(i, pixel_color)
|
||||
end
|
||||
|
||||
# Apply opacity if not full
|
||||
# Apply opacity if not full (supports numbers, animations)
|
||||
if opacity < 255
|
||||
frame.apply_opacity(opacity)
|
||||
end
|
||||
|
||||
@ -55,12 +55,16 @@ The following keywords are reserved and cannot be used as identifiers:
|
||||
**Configuration Keywords:**
|
||||
- `strip` - Strip configuration (temporarily disabled, reserved keyword)
|
||||
- `set` - Variable assignment
|
||||
- `import` - Import Berry modules
|
||||
|
||||
**Definition Keywords:**
|
||||
- `color` - Color definition
|
||||
- `palette` - Palette definition
|
||||
- `animation` - Animation definition
|
||||
- `sequence` - Sequence definition
|
||||
- `template` - Template definition
|
||||
- `param` - Template parameter declaration
|
||||
- `type` - Parameter type annotation
|
||||
|
||||
**Control Flow Keywords:**
|
||||
- `play` - Play animation in sequence
|
||||
@ -207,6 +211,46 @@ set position_sweep = triangle(min_value=0, max_value=29, period=5s)
|
||||
set strip_len = strip_length() # Get current strip length
|
||||
```
|
||||
|
||||
### Import Statements
|
||||
|
||||
The `import` keyword imports Berry modules for use in animations:
|
||||
|
||||
```berry
|
||||
import user_functions # Import user-defined functions
|
||||
import my_custom_module # Import custom animation libraries
|
||||
import math # Import standard Berry modules
|
||||
import string # Import utility modules
|
||||
```
|
||||
|
||||
**Import Behavior:**
|
||||
- Module names should be valid identifiers (no quotes needed in DSL)
|
||||
- Import statements are typically placed at the beginning of DSL files
|
||||
- Transpiles to standard Berry `import "module_name"` statements
|
||||
- Imported modules become available for the entire animation
|
||||
|
||||
**Common Use Cases:**
|
||||
```berry
|
||||
# Import user functions for computed parameters
|
||||
import user_functions
|
||||
|
||||
animation dynamic = solid(color=blue)
|
||||
dynamic.opacity = user.my_custom_function()
|
||||
|
||||
# Import custom animation libraries
|
||||
import fire_effects
|
||||
|
||||
animation campfire = fire_effects.create_fire(intensity=200)
|
||||
```
|
||||
|
||||
**Transpilation Example:**
|
||||
```berry
|
||||
# DSL Code
|
||||
import user_functions
|
||||
|
||||
# Transpiles to Berry Code
|
||||
import "user_functions"
|
||||
```
|
||||
|
||||
## Color Definitions
|
||||
|
||||
The `color` keyword defines static colors or color providers:
|
||||
@ -335,11 +379,15 @@ pulse_red.opacity = smooth(min_value=100, max_value=255, period=2s)
|
||||
set strip_len = strip_length()
|
||||
pulse_red.position = strip_len / 2 # Center position
|
||||
pulse_red.opacity = strip_len * 4 # Scale with strip size
|
||||
|
||||
# Animation opacity (using another animation as opacity mask)
|
||||
animation opacity_mask = pulsating_animation(period=2s)
|
||||
pulse_red.opacity = opacity_mask # Dynamic opacity from animation
|
||||
```
|
||||
|
||||
**Common Properties:**
|
||||
- `priority` - Animation priority (higher numbers have precedence)
|
||||
- `opacity` - Opacity level (0-255)
|
||||
- `opacity` - Opacity level (number, value provider, or animation)
|
||||
- `position` - Position on strip
|
||||
- `speed` - Speed multiplier
|
||||
- `phase` - Phase offset
|
||||
@ -443,37 +491,37 @@ test.opacity = min(255, max(50, scale(sqrt(strip_len), 0, 16, 100, 255)))
|
||||
When the DSL detects arithmetic expressions containing value providers, variable references, or mathematical functions, it automatically creates closure functions that capture the computation. These closures are called with `(self, param_name, time_ms)` parameters, allowing the computation to be re-evaluated dynamically as needed. Mathematical functions are automatically prefixed with `self.` in the closure context to access the ClosureValueProvider's mathematical methods.
|
||||
|
||||
**User Functions in Computed Parameters:**
|
||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects:
|
||||
User-defined functions can also be used in computed parameter expressions, providing powerful custom effects. User functions must be called with the `user.` prefix:
|
||||
|
||||
```berry
|
||||
# Simple user function in computed parameter
|
||||
animation base = solid(color=blue)
|
||||
base.opacity = rand_demo()
|
||||
base.opacity = user.rand_demo()
|
||||
|
||||
# User functions mixed with math operations
|
||||
animation dynamic = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, rand_demo() + 100))
|
||||
opacity=max(50, min(255, user.rand_demo() + 100))
|
||||
)
|
||||
```
|
||||
|
||||
### User Functions
|
||||
|
||||
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time.
|
||||
User functions are custom Berry functions that can be called from computed parameters. They provide dynamic values that change over time. User functions must be called with the `user.` prefix.
|
||||
|
||||
**Available User Functions:**
|
||||
- `rand_demo()` - Returns random values for demonstration purposes
|
||||
- `user.rand_demo()` - Returns random values for demonstration purposes
|
||||
|
||||
**Usage in Computed Parameters:**
|
||||
```berry
|
||||
# Simple user function
|
||||
animation.opacity = rand_demo()
|
||||
animation.opacity = user.rand_demo()
|
||||
|
||||
# User function with math operations
|
||||
animation.opacity = max(100, rand_demo())
|
||||
animation.opacity = max(100, user.rand_demo())
|
||||
|
||||
# User function in arithmetic expressions
|
||||
animation.opacity = abs(rand_demo() - 128) + 64
|
||||
animation.opacity = abs(user.rand_demo() - 128) + 64
|
||||
```
|
||||
|
||||
**Available User Functions:**
|
||||
@ -481,7 +529,7 @@ The following user functions are available by default (see [User Functions Guide
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
| `user.rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
**User Function Behavior:**
|
||||
- User functions are automatically detected by the transpiler
|
||||
@ -668,6 +716,167 @@ sequence cylon_eye {
|
||||
}
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
Templates provide a powerful way to create reusable, parameterized animation patterns. They allow you to define animation blueprints that can be instantiated with different parameters, promoting code reuse and maintainability.
|
||||
|
||||
### Template Definition
|
||||
|
||||
Templates are defined using the `template` keyword followed by a parameter block and body:
|
||||
|
||||
```berry
|
||||
template template_name {
|
||||
param parameter1 type color
|
||||
param parameter2
|
||||
param parameter3 type number
|
||||
|
||||
# Template body with DSL statements
|
||||
animation my_anim = some_animation(color=parameter1, period=parameter2)
|
||||
my_anim.opacity = parameter3
|
||||
run my_anim
|
||||
}
|
||||
```
|
||||
|
||||
### Template Parameters
|
||||
|
||||
Template parameters are declared using the `param` keyword with optional type annotations:
|
||||
|
||||
```berry
|
||||
template pulse_effect {
|
||||
param base_color type color # Parameter with type annotation
|
||||
param duration # Parameter without type annotation
|
||||
param brightness type number # Another typed parameter
|
||||
|
||||
# Use parameters in template body
|
||||
animation pulse = pulsating_animation(
|
||||
color=base_color
|
||||
period=duration
|
||||
)
|
||||
pulse.opacity = brightness
|
||||
run pulse
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter Types:**
|
||||
- `color` - Color values (hex, named colors, color providers)
|
||||
- `palette` - Palette definitions
|
||||
- `number` - Numeric values (integers, percentages, time values)
|
||||
- `animation` - Animation instances
|
||||
- Type annotations are optional but improve readability
|
||||
|
||||
### Template Body
|
||||
|
||||
The template body can contain any valid DSL statements:
|
||||
|
||||
**Supported Statements:**
|
||||
- Color definitions
|
||||
- Palette definitions
|
||||
- Animation definitions
|
||||
- Property assignments
|
||||
- Run statements
|
||||
- Variable assignments (set statements)
|
||||
|
||||
```berry
|
||||
template rainbow_pulse {
|
||||
param pal1 as palette
|
||||
param pal2 as palette
|
||||
param duration
|
||||
param back_color as color
|
||||
|
||||
# Create dynamic color cycling
|
||||
color cycle_color = color_cycle(
|
||||
palette=pal1
|
||||
cycle_period=duration
|
||||
)
|
||||
|
||||
# Create animations
|
||||
animation pulse = pulsating_animation(
|
||||
color=cycle_color
|
||||
period=duration
|
||||
)
|
||||
|
||||
animation background = solid(color=back_color)
|
||||
|
||||
# Set properties
|
||||
background.priority = 1
|
||||
pulse.priority = 10
|
||||
|
||||
# Run both animations
|
||||
run background
|
||||
run pulse
|
||||
}
|
||||
```
|
||||
|
||||
### Template Usage
|
||||
|
||||
Templates are called like functions with positional arguments:
|
||||
|
||||
```berry
|
||||
# Define the template
|
||||
template blink_red {
|
||||
param speed
|
||||
|
||||
animation blink = pulsating_animation(
|
||||
color=red
|
||||
period=speed
|
||||
)
|
||||
|
||||
run blink
|
||||
}
|
||||
|
||||
# Use the template
|
||||
blink_red(1s) # Call with 1 second period
|
||||
blink_red(500ms) # Call with 500ms period
|
||||
```
|
||||
|
||||
**Complex Template Usage:**
|
||||
```berry
|
||||
# Create palettes for the template
|
||||
palette fire_palette = [
|
||||
(0, black)
|
||||
(128, red)
|
||||
(255, yellow)
|
||||
]
|
||||
|
||||
palette ocean_palette = [
|
||||
(0, navy)
|
||||
(128, cyan)
|
||||
(255, white)
|
||||
]
|
||||
|
||||
# Use the complex template
|
||||
rainbow_pulse(fire_palette, ocean_palette, 3s, black)
|
||||
```
|
||||
|
||||
### Template Behavior
|
||||
|
||||
**Code Generation:**
|
||||
Templates generate Berry functions that are registered as user functions:
|
||||
|
||||
```berry
|
||||
# Template definition generates:
|
||||
def pulse_effect_template(engine, base_color_, duration_, brightness_)
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = base_color_
|
||||
pulse_.period = duration_
|
||||
pulse_.opacity = brightness_
|
||||
engine.add_animation(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function('pulse_effect', pulse_effect_template)
|
||||
```
|
||||
|
||||
**Parameter Handling:**
|
||||
- Parameters get `_` suffix in generated code to avoid naming conflicts
|
||||
- Templates receive `engine` as the first parameter automatically
|
||||
- Template calls are converted to function calls with `engine` as first argument
|
||||
|
||||
**Execution Model:**
|
||||
- Templates don't return values - they add animations directly to the engine
|
||||
- Multiple `run` statements in templates add multiple animations
|
||||
- Templates can be called multiple times to create multiple instances
|
||||
- `engine.start()` is automatically called when templates are used at the top level
|
||||
|
||||
## Execution Statements
|
||||
|
||||
Execute animations or sequences:
|
||||
@ -936,22 +1145,26 @@ good.priority = 10 # OK: Valid parameter assignment
|
||||
|
||||
program = { statement } ;
|
||||
|
||||
statement = config_stmt
|
||||
statement = import_stmt
|
||||
| config_stmt
|
||||
| definition
|
||||
| property_assignment
|
||||
| sequence
|
||||
| template_def
|
||||
| execution_stmt ;
|
||||
|
||||
(* Configuration *)
|
||||
(* Import and Configuration *)
|
||||
import_stmt = "import" identifier ;
|
||||
config_stmt = variable_assignment ;
|
||||
(* strip_config = "strip" "length" number ; -- TEMPORARILY DISABLED *)
|
||||
variable_assignment = "set" identifier "=" expression ;
|
||||
|
||||
(* Definitions *)
|
||||
definition = color_def | palette_def | animation_def ;
|
||||
definition = color_def | palette_def | animation_def | template_def ;
|
||||
color_def = "color" identifier "=" color_expression ;
|
||||
palette_def = "palette" identifier "=" palette_array ;
|
||||
animation_def = "animation" identifier "=" animation_expression ;
|
||||
template_def = "template" identifier "{" template_body "}" ;
|
||||
|
||||
(* Property Assignments *)
|
||||
property_assignment = identifier "." identifier "=" expression ;
|
||||
@ -966,8 +1179,16 @@ wait_stmt = "wait" time_expression ;
|
||||
repeat_stmt = "repeat" ( number "times" | "forever" ) "{" sequence_body "}" ;
|
||||
sequence_assignment = identifier "." identifier "=" expression ;
|
||||
|
||||
(* Templates *)
|
||||
template_def = "template" identifier "{" template_body "}" ;
|
||||
template_body = { template_statement } ;
|
||||
template_statement = param_decl | color_def | palette_def | animation_def | property_assignment | execution_stmt ;
|
||||
param_decl = "param" identifier [ "type" identifier ] ;
|
||||
|
||||
(* Execution *)
|
||||
execution_stmt = "run" identifier ;
|
||||
execution_stmt = "run" identifier | template_call ;
|
||||
template_call = identifier "(" [ argument_list ] ")" ;
|
||||
argument_list = expression { "," expression } ;
|
||||
|
||||
(* Expressions *)
|
||||
expression = logical_or_expr ;
|
||||
|
||||
@ -93,16 +93,21 @@ The Animation DSL uses a declarative syntax with named parameters. All animation
|
||||
|
||||
### Key Syntax Features
|
||||
|
||||
- **Import statements**: `import module_name` for loading Berry modules
|
||||
- **Named parameters**: All function calls use `name=value` syntax
|
||||
- **Time units**: `2s`, `500ms`, `1m`, `1h`
|
||||
- **Hex colors**: `#FF0000`, `#80FF0000` (ARGB)
|
||||
- **Named colors**: `red`, `blue`, `white`, etc.
|
||||
- **Comments**: `# This is a comment`
|
||||
- **Property assignment**: `animation.property = value`
|
||||
- **User functions**: `user.function_name()` for custom functions
|
||||
|
||||
### Basic Structure
|
||||
|
||||
```berry
|
||||
# Import statements (optional, for user functions or custom modules)
|
||||
import user_functions
|
||||
|
||||
# Optional strip configuration
|
||||
strip length 60
|
||||
|
||||
@ -114,8 +119,9 @@ color blue = #0000FF
|
||||
animation pulse_red = pulsating_animation(color=red, period=2s)
|
||||
animation comet_blue = comet_animation(color=blue, tail_length=10, speed=1500)
|
||||
|
||||
# Property assignments
|
||||
# Property assignments with user functions
|
||||
pulse_red.priority = 10
|
||||
pulse_red.opacity = user.breathing_effect()
|
||||
comet_blue.direction = -1
|
||||
|
||||
# Execution
|
||||
@ -180,8 +186,186 @@ my_animation.priority = 10
|
||||
|
||||
This intelligent resolution ensures optimal performance while maintaining clear separation between framework and user code.
|
||||
|
||||
## Import Statement Transpilation
|
||||
|
||||
The DSL supports importing Berry modules using the `import` keyword, which provides a clean way to load user functions and custom modules.
|
||||
|
||||
### Import Syntax
|
||||
|
||||
```berry
|
||||
# DSL Import Syntax
|
||||
import user_functions
|
||||
import my_custom_module
|
||||
import math
|
||||
```
|
||||
|
||||
### Transpilation Behavior
|
||||
|
||||
Import statements are transpiled directly to Berry import statements with quoted module names:
|
||||
|
||||
```berry
|
||||
# DSL Code
|
||||
import user_functions
|
||||
|
||||
# Transpiles to Berry Code
|
||||
import "user_functions"
|
||||
```
|
||||
|
||||
### Import Processing
|
||||
|
||||
1. **Early Processing**: Import statements are processed early in transpilation
|
||||
2. **Module Loading**: Imported modules are loaded using standard Berry import mechanism
|
||||
3. **Function Registration**: User function modules should register functions using `animation.register_user_function()`
|
||||
4. **No Validation**: The DSL doesn't validate module existence at compile time
|
||||
|
||||
### Example Import Workflow
|
||||
|
||||
**Step 1: Create User Functions Module (`user_functions.be`)**
|
||||
```berry
|
||||
import animation
|
||||
|
||||
def rand_demo(engine)
|
||||
import math
|
||||
return math.rand() % 256
|
||||
end
|
||||
|
||||
# Register for DSL use
|
||||
animation.register_user_function("rand_demo", rand_demo)
|
||||
```
|
||||
|
||||
**Step 2: Use in DSL**
|
||||
```berry
|
||||
import user_functions
|
||||
|
||||
animation test = solid(color=blue)
|
||||
test.opacity = user.rand_demo()
|
||||
run test
|
||||
```
|
||||
|
||||
**Step 3: Generated Berry Code**
|
||||
```berry
|
||||
import animation
|
||||
var engine = animation.init_strip()
|
||||
|
||||
import "user_functions"
|
||||
var test_ = animation.solid(engine)
|
||||
test_.color = 0xFF0000FF
|
||||
test_.opacity = animation.create_closure_value(engine,
|
||||
def (self) return animation.get_user_function('rand_demo')(self.engine) end)
|
||||
engine.add_animation(test_)
|
||||
engine.start()
|
||||
```
|
||||
|
||||
## Advanced DSL Features
|
||||
|
||||
### Templates
|
||||
|
||||
Templates provide a DSL-native way to create reusable animation patterns with parameters. Templates are transpiled into Berry functions and automatically registered for use.
|
||||
|
||||
#### Template Definition Transpilation
|
||||
|
||||
```berry
|
||||
# DSL Template
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
)
|
||||
|
||||
run pulse
|
||||
}
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
|
||||
```berry
|
||||
def pulse_effect(engine, color, speed)
|
||||
var pulse_ = animation.pulsating_animation(engine)
|
||||
pulse_.color = color
|
||||
pulse_.period = speed
|
||||
engine.add_animation(pulse_)
|
||||
engine.start_animation(pulse_)
|
||||
end
|
||||
|
||||
animation.register_user_function("pulse_effect", pulse_effect)
|
||||
```
|
||||
|
||||
#### Template Transpilation Process
|
||||
|
||||
1. **Function Generation**: Template becomes a Berry function with `engine` as first parameter
|
||||
2. **Parameter Mapping**: Template parameters become function parameters (after `engine`)
|
||||
3. **Body Transpilation**: Template body is transpiled using standard DSL rules
|
||||
4. **Auto-Registration**: Generated function is automatically registered as a user function
|
||||
5. **Type Annotations**: Optional `type` annotations are preserved as comments for documentation
|
||||
|
||||
#### Template Call Transpilation
|
||||
|
||||
```berry
|
||||
# DSL Template Call
|
||||
pulse_effect(red, 2s)
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
|
||||
```berry
|
||||
pulse_effect(engine, animation.red, 2000)
|
||||
```
|
||||
|
||||
Template calls are transpiled as regular user function calls with automatic `engine` parameter injection.
|
||||
|
||||
#### Advanced Template Features
|
||||
|
||||
**Multi-Animation Templates:**
|
||||
```berry
|
||||
template comet_chase {
|
||||
param trail_color type color
|
||||
param bg_color type color
|
||||
param chase_speed
|
||||
|
||||
animation background = solid_animation(color=bg_color)
|
||||
animation comet = comet_animation(color=trail_color, speed=chase_speed)
|
||||
|
||||
run background
|
||||
run comet
|
||||
}
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
```berry
|
||||
def comet_chase(engine, trail_color, bg_color, chase_speed)
|
||||
var background_ = animation.solid_animation(engine)
|
||||
background_.color = bg_color
|
||||
var comet_ = animation.comet_animation(engine)
|
||||
comet_.color = trail_color
|
||||
comet_.speed = chase_speed
|
||||
engine.add_animation(background_)
|
||||
engine.start_animation(background_)
|
||||
engine.add_animation(comet_)
|
||||
engine.start_animation(comet_)
|
||||
end
|
||||
|
||||
animation.register_user_function("comet_chase", comet_chase)
|
||||
```
|
||||
|
||||
#### Template vs User Function Transpilation
|
||||
|
||||
**Templates** (DSL-native):
|
||||
- Defined within DSL files
|
||||
- Use DSL syntax in body
|
||||
- Automatically registered
|
||||
- Type annotations supported
|
||||
- Transpiled to Berry functions
|
||||
|
||||
**User Functions** (Berry-native):
|
||||
- Defined in Berry code
|
||||
- Use Berry syntax
|
||||
- Manually registered
|
||||
- Full Berry language features
|
||||
- Called from DSL
|
||||
|
||||
### User-Defined Functions
|
||||
|
||||
Register custom Berry functions for use in DSL. User functions must take `engine` as the first parameter, followed by any user-provided arguments:
|
||||
|
||||
@ -361,6 +361,95 @@ animation a1 = pulsating_animation(color=c1, period=4s)
|
||||
- Keep animation periods reasonable (>500ms)
|
||||
- Limit palette sizes for memory efficiency
|
||||
|
||||
## Template Examples
|
||||
|
||||
Templates provide reusable, parameterized animation patterns that promote code reuse and maintainability.
|
||||
|
||||
### 21. Simple Template
|
||||
```berry
|
||||
# Define a reusable blinking template
|
||||
template blink_effect {
|
||||
param color type color
|
||||
param speed
|
||||
param intensity
|
||||
|
||||
animation blink = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
)
|
||||
blink.opacity = intensity
|
||||
|
||||
run blink
|
||||
}
|
||||
|
||||
# Use the template with different parameters
|
||||
blink_effect(red, 1s, 80%)
|
||||
blink_effect(blue, 500ms, 100%)
|
||||
```
|
||||
|
||||
### 22. Multi-Animation Template
|
||||
```berry
|
||||
# Template that creates a comet chase effect
|
||||
template comet_chase {
|
||||
param trail_color type color
|
||||
param bg_color type color
|
||||
param chase_speed
|
||||
param tail_size
|
||||
|
||||
# Background layer
|
||||
animation background = solid(color=bg_color)
|
||||
background.priority = 1
|
||||
|
||||
# Comet effect layer
|
||||
animation comet = comet_animation(
|
||||
color=trail_color
|
||||
tail_length=tail_size
|
||||
speed=chase_speed
|
||||
)
|
||||
comet.priority = 10
|
||||
|
||||
run background
|
||||
run comet
|
||||
}
|
||||
|
||||
# Create different comet effects
|
||||
comet_chase(white, black, 1500ms, 8)
|
||||
```
|
||||
|
||||
### 23. Template with Dynamic Colors
|
||||
```berry
|
||||
# Template using color cycling and breathing effects
|
||||
template breathing_rainbow {
|
||||
param cycle_time
|
||||
param breath_time
|
||||
param base_brightness
|
||||
|
||||
# Create rainbow palette
|
||||
palette rainbow = [
|
||||
(0, red), (42, orange), (85, yellow)
|
||||
(128, green), (170, blue), (213, purple), (255, red)
|
||||
]
|
||||
|
||||
# Create cycling rainbow color
|
||||
color rainbow_cycle = color_cycle(
|
||||
palette=rainbow
|
||||
cycle_period=cycle_time
|
||||
)
|
||||
|
||||
# Create breathing animation with rainbow colors
|
||||
animation breath = pulsating_animation(
|
||||
color=rainbow_cycle
|
||||
period=breath_time
|
||||
)
|
||||
breath.opacity = base_brightness
|
||||
|
||||
run breath
|
||||
}
|
||||
|
||||
# Use the rainbow breathing template
|
||||
breathing_rainbow(5s, 2s, 200)
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[DSL Reference](DSL_REFERENCE.md)** - Complete language syntax
|
||||
|
||||
@ -176,9 +176,68 @@ import animation
|
||||
var runtime = animation.load_dsl_file("my_animation.anim")
|
||||
```
|
||||
|
||||
## User-Defined Functions
|
||||
## Templates - Reusable Animation Patterns
|
||||
|
||||
Create custom animation functions in Berry and use them in DSL:
|
||||
Templates let you create reusable animation patterns with parameters:
|
||||
|
||||
```berry
|
||||
# Define a template for pulsing effects
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
)
|
||||
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use the template with different parameters
|
||||
pulse_effect(red, 2s)
|
||||
pulse_effect(blue, 1s)
|
||||
pulse_effect(0xFF69B4, 3s) # Hot pink
|
||||
```
|
||||
|
||||
### Multi-Animation Templates
|
||||
|
||||
Templates can contain multiple animations and sequences:
|
||||
|
||||
```berry
|
||||
template comet_chase {
|
||||
param trail_color type color
|
||||
param bg_color type color
|
||||
param chase_speed
|
||||
|
||||
# Background glow
|
||||
animation background = solid_animation(color=bg_color)
|
||||
|
||||
# Moving comet
|
||||
animation comet = comet_animation(
|
||||
color=trail_color
|
||||
tail_length=6
|
||||
speed=chase_speed
|
||||
)
|
||||
|
||||
run background
|
||||
run comet
|
||||
}
|
||||
|
||||
# Create different comet effects
|
||||
comet_chase(white, blue, 1500)
|
||||
comet_chase(orange, black, 2000)
|
||||
```
|
||||
|
||||
**Template Benefits:**
|
||||
- **Reusable** - Define once, use many times
|
||||
- **Type Safe** - Optional parameter type checking
|
||||
- **Clean Syntax** - Pure DSL, no Berry code needed
|
||||
- **Automatic Registration** - Available immediately after definition
|
||||
|
||||
## User-Defined Functions (Advanced)
|
||||
|
||||
For complex logic, create custom functions in Berry:
|
||||
|
||||
```berry
|
||||
# Define custom function - engine must be first parameter
|
||||
|
||||
@ -268,6 +268,67 @@ end
|
||||
}
|
||||
```
|
||||
|
||||
7. **Template Definition Errors:**
|
||||
```berry
|
||||
# Wrong - missing braces
|
||||
template pulse_effect
|
||||
param color type color
|
||||
param speed
|
||||
# Error: Expected '{' after template name
|
||||
|
||||
# Wrong - invalid parameter syntax
|
||||
template pulse_effect {
|
||||
param color as color # Error: Use 'type' instead of 'as'
|
||||
param speed
|
||||
}
|
||||
|
||||
# Wrong - missing template body
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
}
|
||||
# Error: Template body cannot be empty
|
||||
|
||||
# Correct - proper template syntax
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(
|
||||
color=color
|
||||
period=speed
|
||||
)
|
||||
|
||||
run pulse
|
||||
}
|
||||
```
|
||||
|
||||
8. **Template Call Errors:**
|
||||
```berry
|
||||
# Wrong - template not defined
|
||||
pulse_effect(red, 2s)
|
||||
# Error: "Undefined reference: 'pulse_effect'"
|
||||
|
||||
# Wrong - incorrect parameter count
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
# ... template body ...
|
||||
}
|
||||
|
||||
pulse_effect(red) # Error: Expected 2 parameters, got 1
|
||||
|
||||
# Correct - define template first, call with correct parameters
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(color=color, period=speed)
|
||||
run pulse
|
||||
}
|
||||
|
||||
pulse_effect(red, 2s) # ✓ Correct usage
|
||||
```
|
||||
|
||||
6. **Parameter Constraint Violations:**
|
||||
```berry
|
||||
# Wrong - negative period not allowed
|
||||
@ -318,7 +379,147 @@ end
|
||||
}
|
||||
```
|
||||
|
||||
### DSL Runtime Errors
|
||||
### Template Issues
|
||||
|
||||
### Template Definition Problems
|
||||
|
||||
**Problem:** Template definitions fail to compile
|
||||
|
||||
**Common Template Errors:**
|
||||
|
||||
1. **Missing Template Body:**
|
||||
```berry
|
||||
# Wrong - empty template
|
||||
template empty_template {
|
||||
param color type color
|
||||
}
|
||||
# Error: "Template body cannot be empty"
|
||||
|
||||
# Correct - template must have content
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(color=color, period=speed)
|
||||
run pulse
|
||||
}
|
||||
```
|
||||
|
||||
2. **Invalid Parameter Syntax:**
|
||||
```berry
|
||||
# Wrong - old 'as' syntax
|
||||
template pulse_effect {
|
||||
param color as color
|
||||
}
|
||||
# Error: Expected 'type' keyword, got 'as'
|
||||
|
||||
# Correct - use 'type' keyword
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed # Type annotation is optional
|
||||
}
|
||||
```
|
||||
|
||||
3. **Template Name Conflicts:**
|
||||
```berry
|
||||
# Wrong - template name conflicts with built-in function
|
||||
template solid { # 'solid' is a built-in animation function
|
||||
param color type color
|
||||
# ...
|
||||
}
|
||||
# Error: "Template name 'solid' conflicts with built-in function"
|
||||
|
||||
# Correct - use unique template names
|
||||
template solid_effect {
|
||||
param color type color
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
### Template Usage Problems
|
||||
|
||||
**Problem:** Template calls fail or behave unexpectedly
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
1. **Undefined Template:**
|
||||
```berry
|
||||
# Wrong - calling undefined template
|
||||
my_effect(red, 2s)
|
||||
# Error: "Undefined reference: 'my_effect'"
|
||||
|
||||
# Correct - define template first
|
||||
template my_effect {
|
||||
param color type color
|
||||
param speed
|
||||
# ... template body ...
|
||||
}
|
||||
|
||||
my_effect(red, 2s) # Now works
|
||||
```
|
||||
|
||||
2. **Parameter Count Mismatch:**
|
||||
```berry
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
param brightness
|
||||
}
|
||||
|
||||
# Wrong - missing parameters
|
||||
pulse_effect(red, 2s) # Error: Expected 3 parameters, got 2
|
||||
|
||||
# Correct - provide all parameters
|
||||
pulse_effect(red, 2s, 200)
|
||||
```
|
||||
|
||||
3. **Parameter Type Issues:**
|
||||
```berry
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
}
|
||||
|
||||
# Wrong - invalid color parameter
|
||||
pulse_effect("not_a_color", 2s)
|
||||
# Runtime error: Invalid color value
|
||||
|
||||
# Correct - use valid color
|
||||
pulse_effect(red, 2s) # Named color
|
||||
pulse_effect(0xFF0000, 2s) # Hex color
|
||||
```
|
||||
|
||||
### Template vs User Function Confusion
|
||||
|
||||
**Problem:** Mixing template and user function concepts
|
||||
|
||||
**Key Differences:**
|
||||
|
||||
```berry
|
||||
# Template (DSL-native) - Recommended for most cases
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(color=color, period=speed)
|
||||
run pulse
|
||||
}
|
||||
|
||||
# User Function (Berry-native) - For complex logic
|
||||
def create_pulse_effect(engine, color, speed)
|
||||
var pulse = animation.pulsating_animation(engine)
|
||||
pulse.color = color
|
||||
pulse.period = speed
|
||||
return pulse
|
||||
end
|
||||
animation.register_user_function("pulse_effect", create_pulse_effect)
|
||||
```
|
||||
|
||||
**When to Use Each:**
|
||||
- **Templates**: Simple to moderate effects, DSL syntax, type safety
|
||||
- **User Functions**: Complex logic, Berry features, return values
|
||||
|
||||
## DSL Runtime Errors
|
||||
|
||||
**Problem:** DSL compiles but fails at runtime
|
||||
|
||||
@ -788,6 +989,22 @@ animation red_solid = solid(color=red)
|
||||
run red_solid
|
||||
```
|
||||
|
||||
### Templates
|
||||
```berry
|
||||
# Define reusable template
|
||||
template pulse_effect {
|
||||
param color type color
|
||||
param speed
|
||||
|
||||
animation pulse = pulsating_animation(color=color, period=speed)
|
||||
run pulse
|
||||
}
|
||||
|
||||
# Use template multiple times
|
||||
pulse_effect(red, 2s)
|
||||
pulse_effect(blue, 1s)
|
||||
```
|
||||
|
||||
### Animation with Parameters
|
||||
```berry
|
||||
color blue = 0x0000FF
|
||||
|
||||
@ -30,12 +30,18 @@ animation.register_user_function("breathing", my_breathing)
|
||||
|
||||
### 3. Use It in DSL
|
||||
|
||||
Call your function just like built-in animations:
|
||||
First, import your user functions module, then call your function with the `user.` prefix in computed parameters:
|
||||
|
||||
```berry
|
||||
# Use your custom function
|
||||
animation calm = breathing(blue, 4s)
|
||||
animation energetic = breathing(red, 1s)
|
||||
# Import your user functions module
|
||||
import user_functions
|
||||
|
||||
# Use your custom function in computed parameters
|
||||
animation calm = solid(color=blue)
|
||||
calm.opacity = user.breathing_effect()
|
||||
|
||||
animation energetic = solid(color=red)
|
||||
energetic.opacity = user.breathing_effect()
|
||||
|
||||
sequence demo {
|
||||
play calm for 10s
|
||||
@ -45,6 +51,91 @@ sequence demo {
|
||||
run demo
|
||||
```
|
||||
|
||||
## Importing User Functions
|
||||
|
||||
### DSL Import Statement
|
||||
|
||||
The DSL supports importing Berry modules using the `import` keyword. This is the recommended way to make user functions available in your animations:
|
||||
|
||||
```berry
|
||||
# Import user functions at the beginning of your DSL file
|
||||
import user_functions
|
||||
|
||||
# Now user functions are available with the user. prefix
|
||||
animation test = solid(color=blue)
|
||||
test.opacity = user.my_function()
|
||||
```
|
||||
|
||||
### Import Behavior
|
||||
|
||||
- **Module Loading**: `import user_functions` transpiles to Berry `import "user_functions"`
|
||||
- **Function Registration**: The imported module should register functions using `animation.register_user_function()`
|
||||
- **Availability**: Once imported, functions are available throughout the DSL file
|
||||
- **No Compile-Time Checking**: The DSL doesn't validate user function existence at compile time
|
||||
|
||||
### Example User Functions Module
|
||||
|
||||
Create a file called `user_functions.be`:
|
||||
|
||||
```berry
|
||||
import animation
|
||||
|
||||
# Define your custom functions
|
||||
def rand_demo(engine)
|
||||
import math
|
||||
return math.rand() % 256 # Random value 0-255
|
||||
end
|
||||
|
||||
def breathing_effect(engine, base_value, amplitude)
|
||||
import math
|
||||
var time_factor = (engine.time_ms / 1000) % 4 # 4-second cycle
|
||||
var breath = math.sin(time_factor * math.pi / 2)
|
||||
return int(base_value + breath * amplitude)
|
||||
end
|
||||
|
||||
# Register functions for DSL use
|
||||
animation.register_user_function("rand_demo", rand_demo)
|
||||
animation.register_user_function("breathing", breathing_effect)
|
||||
|
||||
print("User functions loaded!")
|
||||
```
|
||||
|
||||
### Using Imported Functions in DSL
|
||||
|
||||
```berry
|
||||
import user_functions
|
||||
|
||||
# Simple user function call
|
||||
animation random_test = solid(color=red)
|
||||
random_test.opacity = user.rand_demo()
|
||||
|
||||
# User function with parameters
|
||||
animation breathing_blue = solid(color=blue)
|
||||
breathing_blue.opacity = user.breathing(128, 64)
|
||||
|
||||
# User functions in mathematical expressions
|
||||
animation complex = solid(color=green)
|
||||
complex.opacity = max(50, min(255, user.rand_demo() + 100))
|
||||
|
||||
run random_test
|
||||
```
|
||||
|
||||
### Multiple Module Imports
|
||||
|
||||
You can import multiple modules in the same DSL file:
|
||||
|
||||
```berry
|
||||
import user_functions # Basic user functions
|
||||
import fire_effects # Fire animation functions
|
||||
import color_utilities # Color manipulation functions
|
||||
|
||||
animation base = solid(color=user.random_color())
|
||||
base.opacity = user.breathing(200, 50)
|
||||
|
||||
animation flames = solid(color=red)
|
||||
flames.opacity = user.fire_intensity(180)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Simple Color Effects
|
||||
@ -61,8 +152,11 @@ animation.register_user_function("bright", solid_bright)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation bright_red = bright(red, 80%)
|
||||
animation dim_blue = bright(blue, 30%)
|
||||
animation bright_red = solid(color=red)
|
||||
bright_red.opacity = user.bright(80)
|
||||
|
||||
animation dim_blue = solid(color=blue)
|
||||
dim_blue.opacity = user.bright(30)
|
||||
```
|
||||
|
||||
### Fire Effects
|
||||
@ -83,8 +177,11 @@ animation.register_user_function("fire", custom_fire)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation campfire = fire(200, 2s)
|
||||
animation torch = fire(255, 500ms)
|
||||
animation campfire = solid(color=red)
|
||||
campfire.opacity = user.fire(200, 2000)
|
||||
|
||||
animation torch = solid(color=orange)
|
||||
torch.opacity = user.fire(255, 500)
|
||||
```
|
||||
|
||||
### Sparkle Effects
|
||||
@ -102,8 +199,11 @@ animation.register_user_function("sparkles", sparkles)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation stars = sparkles(white, 12, 300ms)
|
||||
animation fairy_dust = sparkles(#FFD700, 8, 500ms)
|
||||
animation stars = solid(color=white)
|
||||
stars.opacity = user.sparkles(12, 300)
|
||||
|
||||
animation fairy_dust = solid(color=#FFD700)
|
||||
fairy_dust.opacity = user.sparkles(8, 500)
|
||||
```
|
||||
|
||||
### Position-Based Effects
|
||||
@ -122,8 +222,11 @@ animation.register_user_function("pulse_at", pulse_at)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation left_pulse = pulse_at(green, 5, 3, 2s)
|
||||
animation right_pulse = pulse_at(blue, 25, 3, 2s)
|
||||
animation left_pulse = solid(color=green)
|
||||
left_pulse.position = user.pulse_at(5, 3, 2000)
|
||||
|
||||
animation right_pulse = solid(color=blue)
|
||||
right_pulse.position = user.pulse_at(25, 3, 2000)
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
@ -175,9 +278,14 @@ animation.register_user_function("alert", gentle_alert)
|
||||
```
|
||||
|
||||
```berry
|
||||
animation emergency = strobe()
|
||||
animation notification = alert()
|
||||
animation custom_police = police(500ms)
|
||||
animation emergency = solid(color=red)
|
||||
emergency.opacity = user.strobe()
|
||||
|
||||
animation notification = solid(color=yellow)
|
||||
notification.opacity = user.alert()
|
||||
|
||||
animation custom_police = solid(color=blue)
|
||||
custom_police.opacity = user.police(500)
|
||||
```
|
||||
|
||||
## Function Organization
|
||||
@ -302,7 +410,7 @@ User functions can be used in computed parameter expressions alongside mathemati
|
||||
```berry
|
||||
# Simple user function call in property assignment
|
||||
animation base = solid(color=blue, priority=10)
|
||||
base.opacity = rand_demo() # User function as computed parameter
|
||||
base.opacity = user.rand_demo() # User function as computed parameter
|
||||
```
|
||||
|
||||
### User Functions with Mathematical Operations
|
||||
@ -314,7 +422,7 @@ set strip_len = strip_length()
|
||||
# Mix user functions with mathematical functions
|
||||
animation dynamic_solid = solid(
|
||||
color=purple
|
||||
opacity=max(50, min(255, rand_demo() + 100)) # Random opacity with bounds
|
||||
opacity=max(50, min(255, user.rand_demo() + 100)) # Random opacity with bounds
|
||||
priority=15
|
||||
)
|
||||
```
|
||||
@ -325,7 +433,7 @@ animation dynamic_solid = solid(
|
||||
# Use user function in arithmetic expressions
|
||||
animation random_effect = solid(
|
||||
color=cyan
|
||||
opacity=abs(rand_demo() - 128) + 64 # Random variation around middle value
|
||||
opacity=abs(user.rand_demo() - 128) + 64 # Random variation around middle value
|
||||
priority=12
|
||||
)
|
||||
```
|
||||
@ -342,7 +450,7 @@ When you use user functions in computed parameters:
|
||||
**Generated Code Example:**
|
||||
```berry
|
||||
# DSL code
|
||||
animation.opacity = max(100, breathing(red, 2000))
|
||||
animation.opacity = max(100, user.breathing(red, 2000))
|
||||
```
|
||||
|
||||
**Transpiles to:**
|
||||
@ -359,7 +467,7 @@ The following user functions are available by default:
|
||||
|
||||
| Function | Parameters | Description |
|
||||
|----------|------------|-------------|
|
||||
| `rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
| `user.rand_demo()` | none | Returns a random value (0-255) for demonstration |
|
||||
|
||||
### Best Practices for Computed Parameters
|
||||
|
||||
@ -378,16 +486,20 @@ import animation
|
||||
# Load your custom functions
|
||||
load("user_animations.be")
|
||||
|
||||
# Now they're available in DSL
|
||||
# Now they're available in DSL with import
|
||||
var dsl_code =
|
||||
"animation my_fire = fire(200, 1500ms)\n"
|
||||
"animation my_sparkles = sparkle(white, 8, 400ms)\n"
|
||||
"import user_functions\n"
|
||||
"\n"
|
||||
"animation my_fire = solid(color=red)\n"
|
||||
"my_fire.opacity = user.fire(200, 1500)\n"
|
||||
"animation my_sparkles = solid(color=white)\n"
|
||||
"my_sparkles.opacity = user.sparkle(8, 400)\n"
|
||||
"\n"
|
||||
"sequence show {\n"
|
||||
" play my_fire for 10s\n"
|
||||
" play my_sparkles for 5s\n"
|
||||
"}\n"
|
||||
"\n
|
||||
"\n"
|
||||
"run show"
|
||||
|
||||
animation_dsl.execute(dsl_code)
|
||||
@ -398,8 +510,12 @@ animation_dsl.execute(dsl_code)
|
||||
```berry
|
||||
# Save DSL with custom functions
|
||||
var my_show =
|
||||
"animation campfire = fire(180, 2s)\n"
|
||||
"animation stars = sparkle(#FFFFFF, 6, 600ms)\n"
|
||||
"import user_functions\n"
|
||||
"\n"
|
||||
"animation campfire = solid(color=orange)\n"
|
||||
"campfire.opacity = user.fire(180, 2000)\n"
|
||||
"animation stars = solid(color=#FFFFFF)\n"
|
||||
"stars.opacity = user.sparkle(6, 600)\n"
|
||||
"\n"
|
||||
"sequence night_scene {\n"
|
||||
" play campfire for 30s\n"
|
||||
|
||||
@ -87,12 +87,12 @@ import "core/user_functions" as user_functions
|
||||
register_to_animation(user_functions)
|
||||
|
||||
# Import and register actual user functions
|
||||
try
|
||||
import "user_functions" as user_funcs # This registers the actual user functions
|
||||
except .. as e, msg
|
||||
# User functions are optional - continue without them if not available
|
||||
print(f"Note: User functions not loaded: {msg}")
|
||||
end
|
||||
# try
|
||||
# import "user_functions" as user_funcs # This registers the actual user functions
|
||||
# except .. as e, msg
|
||||
# # User functions are optional - continue without them if not available
|
||||
# print(f"Note: User functions not loaded: {msg}")
|
||||
# end
|
||||
|
||||
# Import value providers
|
||||
import "providers/value_provider.be" as value_provider
|
||||
@ -201,28 +201,6 @@ def animation_init_strip(*l)
|
||||
end
|
||||
animation.init_strip = animation_init_strip
|
||||
|
||||
# Global variable resolver with error checking
|
||||
# Used by DSL-generated code to resolve variable names during execution
|
||||
# First checks animation module, then global scope for user-defined variables
|
||||
def animation_global(name, module_name)
|
||||
import global
|
||||
import introspect
|
||||
import animation
|
||||
|
||||
# First try to find in animation module (built-in functions/classes)
|
||||
if (module_name != nil) && introspect.contains(animation, module_name)
|
||||
return animation.(module_name)
|
||||
end
|
||||
|
||||
# Then try global scope (user-defined variables)
|
||||
if global.contains(name)
|
||||
return global.(name)
|
||||
else
|
||||
raise "syntax_error", f"'{name}' undeclared"
|
||||
end
|
||||
end
|
||||
animation.global = animation_global
|
||||
|
||||
# This function is called from C++ code to set up the Berry animation environment
|
||||
# It creates a mutable 'animation' module on top of the immutable solidified
|
||||
#
|
||||
@ -250,6 +228,9 @@ def animation_init(m)
|
||||
end
|
||||
end
|
||||
|
||||
# Create an empty map for user_functions
|
||||
animation_new._user_functions = {}
|
||||
|
||||
return animation_new
|
||||
end
|
||||
animation.init = animation_init
|
||||
|
||||
@ -184,7 +184,8 @@ class FireAnimation : animation.animation
|
||||
fire_provider.cycle_period = 0 # Use value-based color mapping, not time-based
|
||||
fire_provider.transition_type = 1 # Use sine transition (smooth)
|
||||
fire_provider.brightness = 255
|
||||
fire_provider.set_range(0, 255)
|
||||
fire_provider.range_min = 0
|
||||
fire_provider.range_max = 255
|
||||
resolved_color = fire_provider
|
||||
end
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
#@ solidify:PalettePatternAnimation,weak
|
||||
class PalettePatternAnimation : animation.animation
|
||||
var value_buffer # Buffer to store values for each pixel
|
||||
var value_buffer # Buffer to store values for each pixel (bytes object)
|
||||
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
@ -25,7 +25,7 @@ class PalettePatternAnimation : animation.animation
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.value_buffer = []
|
||||
self.value_buffer = bytes()
|
||||
|
||||
# Initialize value buffer with default frame width
|
||||
self._initialize_value_buffer()
|
||||
@ -63,7 +63,12 @@ class PalettePatternAnimation : animation.animation
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.value_buffer[i] = pattern_func(i, time_ms, self)
|
||||
var pattern_value = pattern_func(i, time_ms, self)
|
||||
# Pattern function should return values in 0-255 range, clamp to byte range
|
||||
var byte_value = int(pattern_value)
|
||||
if byte_value < 0 byte_value = 0 end
|
||||
if byte_value > 255 byte_value = 255 end
|
||||
self.value_buffer[i] = byte_value
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -103,11 +108,16 @@ class PalettePatternAnimation : animation.animation
|
||||
end
|
||||
|
||||
# Get current parameter values (cached for performance)
|
||||
var color_source = self.color_source
|
||||
var color_source = self.get_param('color_source') # use get_param to avoid resolving of color_provider
|
||||
if color_source == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Check if color_source has the required method (more flexible than isinstance check)
|
||||
if color_source.get_color_for_value == nil
|
||||
return false
|
||||
end
|
||||
|
||||
# Calculate elapsed time since animation started
|
||||
var elapsed = time_ms - self.start_time
|
||||
|
||||
@ -115,17 +125,10 @@ class PalettePatternAnimation : animation.animation
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
var i = 0
|
||||
while i < strip_length && i < frame.width
|
||||
var value = self.value_buffer[i]
|
||||
var color
|
||||
var byte_value = self.value_buffer[i]
|
||||
|
||||
# Check if color_source is a ColorProvider or an animation with get_color_for_value method
|
||||
if color_source.get_color_for_value != nil
|
||||
# It's a ColorProvider or compatible object
|
||||
color = color_source.get_color_for_value(value, elapsed)
|
||||
else
|
||||
# Fallback to direct color access (for backward compatibility)
|
||||
color = color_source.current_color
|
||||
end
|
||||
# Use the color_source to get color for the byte value (0-255)
|
||||
var color = color_source.get_color_for_value(byte_value, elapsed)
|
||||
|
||||
frame.set_pixel_color(i, color)
|
||||
i += 1
|
||||
@ -191,13 +194,14 @@ class PaletteWaveAnimation : PalettePatternAnimation
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate the wave value (0-100) using scale_uint
|
||||
# Calculate the wave value (0-255) using scale_uint
|
||||
var pos_in_wave = (i + offset) % wave_length
|
||||
var angle = tasmota.scale_uint(pos_in_wave, 0, wave_length, 0, 32767) # 0 to 2π in fixed-point
|
||||
var sine_value = tasmota.sine_int(angle) # -4096 to 4096
|
||||
|
||||
# Map sine value from -4096..4096 to 0..100
|
||||
self.value_buffer[i] = tasmota.scale_int(sine_value, -4096, 4096, 0, 100)
|
||||
# Map sine value from -4096..4096 to 0..255
|
||||
var byte_value = tasmota.scale_int(sine_value, -4096, 4096, 0, 255)
|
||||
self.value_buffer[i] = byte_value
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -209,7 +213,9 @@ class PaletteGradientAnimation : PalettePatternAnimation
|
||||
# Static definitions of parameters with constraints
|
||||
static var PARAMS = {
|
||||
# Gradient-specific parameters only
|
||||
"shift_period": {"min": 1, "default": 10000}
|
||||
"shift_period": {"min": 0, "default": 0}, # Time for one complete shift cycle in ms (0 = static)
|
||||
"spatial_period": {"min": 0, "default": 0}, # Spatial period in pixels (0 = full strip)
|
||||
"phase_shift": {"min": 0, "max": 100, "default": 0} # Phase shift as percentage (0-100)
|
||||
}
|
||||
|
||||
# Initialize a new gradient pattern animation
|
||||
@ -227,6 +233,8 @@ class PaletteGradientAnimation : PalettePatternAnimation
|
||||
def _update_value_buffer(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var shift_period = self.shift_period
|
||||
var spatial_period = self.spatial_period
|
||||
var phase_shift = self.phase_shift
|
||||
var strip_length = self.engine.get_strip_length()
|
||||
|
||||
# Resize buffer if strip length changed
|
||||
@ -234,16 +242,28 @@ class PaletteGradientAnimation : PalettePatternAnimation
|
||||
self.value_buffer.resize(strip_length)
|
||||
end
|
||||
|
||||
# Calculate the shift position using scale_uint for better precision
|
||||
var position = tasmota.scale_uint(time_ms % shift_period, 0, shift_period, 0, 1000) / 1000.0
|
||||
var offset = int(position * strip_length)
|
||||
# Determine effective spatial period (0 means full strip)
|
||||
var effective_spatial_period = spatial_period > 0 ? spatial_period : strip_length
|
||||
|
||||
# Calculate the temporal shift position (how much the pattern has moved over time)
|
||||
var temporal_offset = 0
|
||||
if shift_period > 0
|
||||
var temporal_position = tasmota.scale_uint(time_ms % shift_period, 0, shift_period, 0, 1000) / 1000.0
|
||||
temporal_offset = temporal_position * effective_spatial_period
|
||||
end
|
||||
|
||||
# Calculate the phase shift offset in pixels
|
||||
var phase_offset = tasmota.scale_uint(phase_shift, 0, 100, 0, effective_spatial_period)
|
||||
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Calculate the gradient value (0-100) using scale_uint
|
||||
var pos_in_frame = (i + offset) % strip_length
|
||||
self.value_buffer[i] = tasmota.scale_uint(pos_in_frame, 0, strip_length - 1, 0, 100)
|
||||
# Calculate position within the spatial period, including temporal and phase offsets
|
||||
var spatial_pos = (i + temporal_offset + phase_offset) % effective_spatial_period
|
||||
|
||||
# Map spatial position to gradient value (0-255)
|
||||
var byte_value = tasmota.scale_uint(int(spatial_pos), 0, effective_spatial_period - 1, 0, 255)
|
||||
self.value_buffer[i] = byte_value
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
@ -293,8 +313,8 @@ class PaletteMeterAnimation : PalettePatternAnimation
|
||||
# Calculate values for each pixel
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
# Return 100 if pixel is within the meter, 0 otherwise
|
||||
self.value_buffer[i] = i < meter_position ? 100 : 0
|
||||
# Return 255 if pixel is within the meter, 0 otherwise
|
||||
self.value_buffer[i] = i < meter_position ? 255 : 0
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
@ -243,7 +243,8 @@ def wave_rainbow_sine(engine)
|
||||
rainbow_provider.cycle_period = 5000
|
||||
rainbow_provider.transition_type = 1 # sine transition
|
||||
rainbow_provider.brightness = 255
|
||||
rainbow_provider.set_range(0, 255)
|
||||
rainbow_provider.range_min = 0
|
||||
rainbow_provider.range_max = 255
|
||||
anim.color = rainbow_provider
|
||||
anim.wave_type = 0 # sine wave
|
||||
anim.frequency = 32
|
||||
|
||||
@ -11,16 +11,17 @@ class Animation : animation.parameterized_object
|
||||
# Non-parameter instance variables only
|
||||
var start_time # Time when animation started (ms) (int)
|
||||
var current_time # Current animation time (ms) (int)
|
||||
var opacity_frame # Frame buffer for opacity animation rendering
|
||||
|
||||
# Parameter definitions
|
||||
static var PARAMS = {
|
||||
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
||||
"is_running": {"type": "bool", "default": false}, # Whether the animation is active
|
||||
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
||||
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
||||
"loop": {"type": "bool", "default": true}, # Whether to loop when duration is reached
|
||||
"opacity": {"min": 0, "max": 255, "default": 255}, # Animation opacity/brightness (0-255)
|
||||
"color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB)
|
||||
"name": {"type": "string", "default": "animation"}, # Optional name for the animation
|
||||
"is_running": {"type": "bool", "default": false}, # Whether the animation is active
|
||||
"priority": {"min": 0, "default": 10}, # Rendering priority (higher = on top, 0-255)
|
||||
"duration": {"min": 0, "default": 0}, # Animation duration in ms (0 = infinite)
|
||||
"loop": {"type": "bool", "default": false}, # Whether to loop when duration is reached
|
||||
"opacity": {"type": "any", "default": 255}, # Animation opacity (0-255 number or Animation instance)
|
||||
"color": {"default": 0xFFFFFFFF} # Base color in ARGB format (0xAARRGGBB)
|
||||
}
|
||||
|
||||
# Initialize a new animation
|
||||
@ -33,6 +34,7 @@ class Animation : animation.parameterized_object
|
||||
# Initialize non-parameter instance variables
|
||||
self.start_time = 0
|
||||
self.current_time = 0
|
||||
self.opacity_frame = nil # Will be created when needed
|
||||
end
|
||||
|
||||
# Start/restart the animation (make it active and reset timing)
|
||||
@ -140,6 +142,11 @@ class Animation : animation.parameterized_object
|
||||
return false
|
||||
end
|
||||
|
||||
# Use engine time if not provided
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
self.update(time_ms)
|
||||
|
||||
@ -147,17 +154,54 @@ class Animation : animation.parameterized_object
|
||||
var current_color = self.color
|
||||
var current_opacity = self.opacity
|
||||
|
||||
# Fill the entire frame with the current color
|
||||
frame.fill_pixels(current_color)
|
||||
|
||||
# Apply resolved opacity if not full
|
||||
if current_opacity < 255
|
||||
frame.apply_brightness(current_opacity)
|
||||
# Fill the entire frame with the current color if not transparent
|
||||
if (current_color != 0x00000000)
|
||||
frame.fill_pixels(current_color)
|
||||
end
|
||||
|
||||
# Handle opacity - can be number, frame buffer, or animation
|
||||
self._apply_opacity(frame, current_opacity, time_ms)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
# Apply opacity to frame buffer - handles numbers and animations
|
||||
#
|
||||
# @param frame: FrameBuffer - The frame buffer to apply opacity to
|
||||
# @param opacity: int|Animation - Opacity value or animation
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
def _apply_opacity(frame, opacity, time_ms)
|
||||
# Check if opacity is an animation instance
|
||||
if isinstance(opacity, animation.animation)
|
||||
# Animation mode: render opacity animation to frame buffer and use as mask
|
||||
var opacity_animation = opacity
|
||||
|
||||
# Ensure opacity frame buffer exists and has correct size
|
||||
if self.opacity_frame == nil || self.opacity_frame.width != frame.width
|
||||
self.opacity_frame = animation.frame_buffer(frame.width)
|
||||
end
|
||||
|
||||
# Clear and render opacity animation to frame buffer
|
||||
self.opacity_frame.clear()
|
||||
|
||||
# Start opacity animation if not running
|
||||
if !opacity_animation.is_running
|
||||
opacity_animation.start(self.start_time)
|
||||
end
|
||||
|
||||
# Update and render opacity animation
|
||||
opacity_animation.update(time_ms)
|
||||
opacity_animation.render(self.opacity_frame, time_ms)
|
||||
|
||||
# Use rendered frame buffer as opacity mask
|
||||
frame.apply_opacity(self.opacity_frame)
|
||||
elif type(opacity) == 'int' && opacity < 255
|
||||
# Number mode: apply uniform opacity
|
||||
frame.apply_opacity(opacity)
|
||||
end
|
||||
# If opacity is 255 (full opacity), do nothing
|
||||
end
|
||||
|
||||
# Get a color for a specific pixel position and time
|
||||
# Default implementation returns the animation's color (solid color for all pixels)
|
||||
#
|
||||
|
||||
@ -485,68 +485,92 @@ class FrameBuffer
|
||||
end
|
||||
|
||||
# Apply an opacity adjustment to the frame buffer
|
||||
# opacity: opacity factor (0-511, where 0 is fully transparent, 255 is original, 511 is maximum opaque)
|
||||
# start_pos: start position (default: 0)
|
||||
# end_pos: end position (default: width-1)
|
||||
def apply_opacity(opacity, start_pos, end_pos)
|
||||
# opacity: opacity factor (0-511) or another FrameBuffer to use as mask
|
||||
# - Number: 0 is fully transparent, 255 is original, 511 is maximum opaque
|
||||
# - FrameBuffer: uses alpha channel as opacity mask
|
||||
def apply_opacity(opacity)
|
||||
if opacity == nil
|
||||
opacity = 255
|
||||
end
|
||||
|
||||
if start_pos == nil
|
||||
start_pos = 0
|
||||
end
|
||||
|
||||
if end_pos == nil
|
||||
end_pos = self.width - 1
|
||||
end
|
||||
|
||||
# Validate parameters
|
||||
if start_pos < 0 || start_pos >= self.width
|
||||
raise "index_error", "start_pos out of range"
|
||||
end
|
||||
|
||||
if end_pos < start_pos || end_pos >= self.width
|
||||
raise "index_error", "end_pos out of range"
|
||||
end
|
||||
|
||||
# Ensure opacity is in valid range (0-511)
|
||||
opacity = opacity < 0 ? 0 : (opacity > 511 ? 511 : opacity)
|
||||
|
||||
# Apply opacity adjustment
|
||||
var i = start_pos
|
||||
while i <= end_pos
|
||||
var color = self.get_pixel_color(i)
|
||||
# Check if opacity is a FrameBuffer (mask mode)
|
||||
if isinstance(opacity, animation.frame_buffer)
|
||||
# Mask mode: use another frame buffer as opacity mask
|
||||
var mask_buffer = opacity
|
||||
|
||||
# Extract components (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Adjust alpha using tasmota.scale_uint
|
||||
# For opacity 0-255: scale down alpha
|
||||
# For opacity 256-511: scale up alpha (but cap at 255)
|
||||
if opacity <= 255
|
||||
a = tasmota.scale_uint(opacity, 0, 255, 0, a)
|
||||
else
|
||||
# Scale up alpha: map 256-511 to 1.0-2.0 multiplier
|
||||
a = tasmota.scale_uint(a * opacity, 0, 255 * 255, 0, 255)
|
||||
a = a > 255 ? 255 : a # Cap at maximum alpha
|
||||
if self.width != mask_buffer.width
|
||||
raise "value_error", "frame buffers must have the same width"
|
||||
end
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
var i = 0
|
||||
while i < self.width
|
||||
var color = self.get_pixel_color(i)
|
||||
var mask_color = mask_buffer.get_pixel_color(i)
|
||||
|
||||
# Extract alpha from mask as opacity factor (0-255)
|
||||
var mask_opacity = (mask_color >> 24) & 0xFF
|
||||
|
||||
# Extract components from color (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Apply mask opacity to alpha channel using tasmota.scale_uint
|
||||
a = tasmota.scale_uint(mask_opacity, 0, 255, 0, a)
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
var new_color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, new_color)
|
||||
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
# Number mode: uniform opacity adjustment
|
||||
var opacity_value = int(opacity)
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, color)
|
||||
# Ensure opacity is in valid range (0-511)
|
||||
opacity_value = opacity_value < 0 ? 0 : (opacity_value > 511 ? 511 : opacity_value)
|
||||
|
||||
i += 1
|
||||
# Apply opacity adjustment
|
||||
var i = 0
|
||||
while i < self.width
|
||||
var color = self.get_pixel_color(i)
|
||||
|
||||
# Extract components (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Adjust alpha using tasmota.scale_uint
|
||||
# For opacity 0-255: scale down alpha
|
||||
# For opacity 256-511: scale up alpha (but cap at 255)
|
||||
if opacity_value <= 255
|
||||
a = tasmota.scale_uint(opacity_value, 0, 255, 0, a)
|
||||
else
|
||||
# Scale up alpha: map 256-511 to 1.0-2.0 multiplier
|
||||
a = tasmota.scale_uint(a * opacity_value, 0, 255 * 255, 0, 255)
|
||||
a = a > 255 ? 255 : a # Cap at maximum alpha
|
||||
end
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, color)
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Apply a brightness adjustment to the frame buffer
|
||||
# brightness: brightness factor (0-511, where 0 is black, 255 is original, and 511 is maximum bright)
|
||||
# brightness: brightness factor (0-511) or another FrameBuffer to use as mask
|
||||
# - Number: 0 is black, 255 is original, 511 is maximum bright
|
||||
# - FrameBuffer: uses alpha channel as brightness mask
|
||||
# start_pos: start position (default: 0)
|
||||
# end_pos: end position (default: width-1)
|
||||
def apply_brightness(brightness, start_pos, end_pos)
|
||||
@ -571,45 +595,86 @@ class FrameBuffer
|
||||
raise "index_error", "end_pos out of range"
|
||||
end
|
||||
|
||||
# Ensure brightness is in valid range (0-511)
|
||||
brightness = brightness < 0 ? 0 : (brightness > 511 ? 511 : brightness)
|
||||
|
||||
# Apply brightness adjustment
|
||||
var i = start_pos
|
||||
while i <= end_pos
|
||||
var color = self.get_pixel_color(i)
|
||||
# Check if brightness is a FrameBuffer (mask mode)
|
||||
if isinstance(brightness, animation.frame_buffer)
|
||||
# Mask mode: use another frame buffer as brightness mask
|
||||
var mask_buffer = brightness
|
||||
|
||||
# Extract components (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Adjust brightness using tasmota.scale_uint
|
||||
# For brightness 0-255: scale down RGB
|
||||
# For brightness 256-511: scale up RGB (but cap at 255)
|
||||
if brightness <= 255
|
||||
r = tasmota.scale_uint(r, 0, 255, 0, brightness)
|
||||
g = tasmota.scale_uint(g, 0, 255, 0, brightness)
|
||||
b = tasmota.scale_uint(b, 0, 255, 0, brightness)
|
||||
else
|
||||
# Scale up RGB: map 256-511 to 1.0-2.0 multiplier
|
||||
var multiplier = brightness - 255 # 0-256 range
|
||||
r = r + tasmota.scale_uint(r * multiplier, 0, 255 * 256, 0, 255)
|
||||
g = g + tasmota.scale_uint(g * multiplier, 0, 255 * 256, 0, 255)
|
||||
b = b + tasmota.scale_uint(b * multiplier, 0, 255 * 256, 0, 255)
|
||||
r = r > 255 ? 255 : r # Cap at maximum
|
||||
g = g > 255 ? 255 : g # Cap at maximum
|
||||
b = b > 255 ? 255 : b # Cap at maximum
|
||||
if self.width != mask_buffer.width
|
||||
raise "value_error", "frame buffers must have the same width"
|
||||
end
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
var i = start_pos
|
||||
while i <= end_pos
|
||||
var color = self.get_pixel_color(i)
|
||||
var mask_color = mask_buffer.get_pixel_color(i)
|
||||
|
||||
# Extract alpha from mask as brightness factor (0-255)
|
||||
var mask_brightness = (mask_color >> 24) & 0xFF
|
||||
|
||||
# Extract components from color (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Apply mask brightness to RGB channels using tasmota.scale_uint
|
||||
r = tasmota.scale_uint(mask_brightness, 0, 255, 0, r)
|
||||
g = tasmota.scale_uint(mask_brightness, 0, 255, 0, g)
|
||||
b = tasmota.scale_uint(mask_brightness, 0, 255, 0, b)
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
var new_color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, new_color)
|
||||
|
||||
i += 1
|
||||
end
|
||||
else
|
||||
# Number mode: uniform brightness adjustment
|
||||
var brightness_value = int(brightness)
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, color)
|
||||
# Ensure brightness is in valid range (0-511)
|
||||
brightness_value = brightness_value < 0 ? 0 : (brightness_value > 511 ? 511 : brightness_value)
|
||||
|
||||
i += 1
|
||||
# Apply brightness adjustment
|
||||
var i = start_pos
|
||||
while i <= end_pos
|
||||
var color = self.get_pixel_color(i)
|
||||
|
||||
# Extract components (ARGB format - 0xAARRGGBB)
|
||||
var a = (color >> 24) & 0xFF
|
||||
var r = (color >> 16) & 0xFF
|
||||
var g = (color >> 8) & 0xFF
|
||||
var b = color & 0xFF
|
||||
|
||||
# Adjust brightness using tasmota.scale_uint
|
||||
# For brightness 0-255: scale down RGB
|
||||
# For brightness 256-511: scale up RGB (but cap at 255)
|
||||
if brightness_value <= 255
|
||||
r = tasmota.scale_uint(r, 0, 255, 0, brightness_value)
|
||||
g = tasmota.scale_uint(g, 0, 255, 0, brightness_value)
|
||||
b = tasmota.scale_uint(b, 0, 255, 0, brightness_value)
|
||||
else
|
||||
# Scale up RGB: map 256-511 to 1.0-2.0 multiplier
|
||||
var multiplier = brightness_value - 255 # 0-256 range
|
||||
r = r + tasmota.scale_uint(r * multiplier, 0, 255 * 256, 0, 255)
|
||||
g = g + tasmota.scale_uint(g * multiplier, 0, 255 * 256, 0, 255)
|
||||
b = b + tasmota.scale_uint(b * multiplier, 0, 255 * 256, 0, 255)
|
||||
r = r > 255 ? 255 : r # Cap at maximum
|
||||
g = g > 255 ? 255 : g # Cap at maximum
|
||||
b = b > 255 ? 255 : b # Cap at maximum
|
||||
end
|
||||
|
||||
# Combine components into a 32-bit value (ARGB format - 0xAARRGGBB)
|
||||
color = (a << 24) | (r << 16) | (g << 8) | b
|
||||
|
||||
# Update the pixel
|
||||
self.set_pixel_color(i, color)
|
||||
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -3,33 +3,25 @@
|
||||
|
||||
#@ solidify:animation_user_functions,weak
|
||||
|
||||
# Module-level storage for user-defined functions
|
||||
import global
|
||||
global._animation_user_functions = {}
|
||||
|
||||
# Register a Berry function for DSL use
|
||||
def register_user_function(name, func)
|
||||
import global
|
||||
global._animation_user_functions[name] = func
|
||||
animation._user_functions[name] = func
|
||||
end
|
||||
|
||||
# Retrieve a registered function by name
|
||||
def get_user_function(name)
|
||||
import global
|
||||
return global._animation_user_functions.find(name)
|
||||
return animation._user_functions.find(name)
|
||||
end
|
||||
|
||||
# Check if a function is registered
|
||||
def is_user_function(name)
|
||||
import global
|
||||
return global._animation_user_functions.contains(name)
|
||||
return animation._user_functions.contains(name)
|
||||
end
|
||||
|
||||
# List all registered function names
|
||||
def list_user_functions()
|
||||
import global
|
||||
var names = []
|
||||
for name : global._animation_user_functions.keys()
|
||||
for name : animation.user_functions.keys()
|
||||
names.push(name)
|
||||
end
|
||||
return names
|
||||
|
||||
@ -27,15 +27,15 @@ class Token
|
||||
|
||||
static var statement_keywords = [
|
||||
"strip", "set", "color", "palette", "animation",
|
||||
"sequence", "function", "zone", "on", "run"
|
||||
"sequence", "function", "zone", "on", "run", "template", "param", "import"
|
||||
]
|
||||
|
||||
static var keywords = [
|
||||
# Configuration keywords
|
||||
"strip", "set",
|
||||
"strip", "set", "import",
|
||||
|
||||
# Definition keywords
|
||||
"color", "palette", "animation", "sequence", "function", "zone",
|
||||
"color", "palette", "animation", "sequence", "function", "zone", "template", "param", "type",
|
||||
|
||||
# Control flow keywords
|
||||
"play", "for", "with", "repeat", "times", "forever", "if", "else", "elif",
|
||||
|
||||
@ -27,6 +27,8 @@ class SimpleDSLTranspiler
|
||||
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
|
||||
var template_definitions # Track template definitions: name -> {params, body}
|
||||
var has_template_calls # Track if we have template calls to trigger engine.start()
|
||||
|
||||
# Static color mapping for named colors (helps with solidification)
|
||||
static var named_colors = {
|
||||
@ -56,17 +58,31 @@ class SimpleDSLTranspiler
|
||||
self.sequence_names = {} # Track which names are sequences
|
||||
self.symbol_table = {} # Track created objects: name -> instance
|
||||
self.indent_level = 0 # Track current indentation level
|
||||
self.template_definitions = {} # Track template definitions
|
||||
self.has_template_calls = false # Track if we have template calls
|
||||
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 += " "
|
||||
return " " * (self.indent_level + 1) # Base indentation is 2 spaces
|
||||
end
|
||||
|
||||
# Helper method to process user function calls (user.function_name())
|
||||
def _process_user_function_call(func_name)
|
||||
# Check if this is a function call (user.function_name())
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a user function call: user.function_name()
|
||||
# Don't check for existence during transpilation - trust that function will be available at runtime
|
||||
|
||||
# User functions use positional parameters with engine as first argument
|
||||
# In closure context, use self.engine to access the engine from the ClosureValueProvider
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
else
|
||||
self.error("User functions must be called with parentheses: user.function_name()")
|
||||
return "nil"
|
||||
end
|
||||
return indent
|
||||
end
|
||||
|
||||
# Main transpilation method - single pass
|
||||
@ -90,6 +106,31 @@ class SimpleDSLTranspiler
|
||||
end
|
||||
end
|
||||
|
||||
# Transpile template body (similar to main transpile but without imports/engine start)
|
||||
def transpile_template_body()
|
||||
try
|
||||
# Process all statements in template body
|
||||
while !self.at_end()
|
||||
self.process_statement()
|
||||
end
|
||||
|
||||
# For templates, process run statements immediately instead of collecting them
|
||||
if size(self.run_statements) > 0
|
||||
for run_stmt : self.run_statements
|
||||
var obj_name = run_stmt["name"]
|
||||
var comment = run_stmt["comment"]
|
||||
# In templates, use underscore suffix for local variables
|
||||
self.add(f"engine.add_animation({obj_name}_){comment}")
|
||||
end
|
||||
end
|
||||
|
||||
return size(self.errors) == 0 ? self.join_output() : nil
|
||||
except .. as e, msg
|
||||
self.error(f"Template body transpilation failed: {msg}")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# Process statements - simplified approach
|
||||
def process_statement()
|
||||
var tok = self.current()
|
||||
@ -139,8 +180,12 @@ class SimpleDSLTranspiler
|
||||
self.process_set()
|
||||
elif tok.value == "sequence"
|
||||
self.process_sequence()
|
||||
elif tok.value == "template"
|
||||
self.process_template()
|
||||
elif tok.value == "run"
|
||||
self.process_run()
|
||||
elif tok.value == "import"
|
||||
self.process_import()
|
||||
elif tok.value == "on"
|
||||
self.process_event_handler()
|
||||
else
|
||||
@ -188,12 +233,12 @@ class SimpleDSLTranspiler
|
||||
self.next()
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
# User functions use positional parameters with engine as first argument
|
||||
# Check if this is a template call first
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
var args = self.process_function_arguments()
|
||||
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||
self.add(f"var {name}_ = animation.get_user_function('{func_name}')({full_args}){inline_comment}")
|
||||
self.add(f"{func_name}_template({full_args}){inline_comment}")
|
||||
else
|
||||
# Built-in functions use the new engine-first + named parameters pattern
|
||||
# Validate that the factory function exists at transpilation time
|
||||
@ -382,12 +427,12 @@ class SimpleDSLTranspiler
|
||||
self.next()
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
# User functions use positional parameters with engine as first argument
|
||||
# Check if this is a template call first
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
var args = self.process_function_arguments()
|
||||
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||
self.add(f"var {name}_ = animation.get_user_function('{func_name}')({full_args}){inline_comment}")
|
||||
self.add(f"{func_name}_template({full_args}){inline_comment}")
|
||||
else
|
||||
# Built-in functions use the new engine-first + named parameters pattern
|
||||
# Validate that the factory function creates an animation instance at transpile time
|
||||
@ -430,8 +475,6 @@ class SimpleDSLTranspiler
|
||||
self.symbol_table[name] = ref_instance
|
||||
end
|
||||
end
|
||||
|
||||
# Note: For identifier references, type checking happens at runtime via animation.global()
|
||||
end
|
||||
end
|
||||
|
||||
@ -469,6 +512,103 @@ class SimpleDSLTranspiler
|
||||
self.symbol_table[name] = "variable"
|
||||
end
|
||||
|
||||
# Process template definition: template name { param ... }
|
||||
def process_template()
|
||||
self.next() # skip 'template'
|
||||
var name = self.expect_identifier()
|
||||
|
||||
# Validate that the template name is not reserved
|
||||
if !self.validate_user_name(name, "template")
|
||||
self.skip_statement()
|
||||
return
|
||||
end
|
||||
|
||||
self.expect_left_brace()
|
||||
|
||||
# First pass: collect all parameters
|
||||
var params = []
|
||||
var param_types = {}
|
||||
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
self.skip_whitespace_including_newlines()
|
||||
|
||||
if self.check_right_brace()
|
||||
break
|
||||
end
|
||||
|
||||
var tok = self.current()
|
||||
|
||||
if tok != nil && tok.type == animation_dsl.Token.KEYWORD && tok.value == "param"
|
||||
# Process parameter declaration
|
||||
self.next() # skip 'param'
|
||||
var param_name = self.expect_identifier()
|
||||
|
||||
# Check for optional type annotation
|
||||
var param_type = nil
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.KEYWORD && self.current().value == "type"
|
||||
self.next() # skip 'type'
|
||||
param_type = self.expect_identifier()
|
||||
end
|
||||
|
||||
params.push(param_name)
|
||||
if param_type != nil
|
||||
param_types[param_name] = param_type
|
||||
end
|
||||
|
||||
# Skip optional newline after parameter
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.NEWLINE
|
||||
self.next()
|
||||
end
|
||||
else
|
||||
# Found non-param statement, break to collect body
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Second pass: collect body tokens (everything until closing brace)
|
||||
var body_tokens = []
|
||||
var brace_depth = 0
|
||||
|
||||
while !self.at_end() && !self.check_right_brace()
|
||||
var tok = self.current()
|
||||
|
||||
if tok == nil || tok.type == animation_dsl.Token.EOF
|
||||
break
|
||||
end
|
||||
|
||||
if tok.type == animation_dsl.Token.LEFT_BRACE
|
||||
brace_depth += 1
|
||||
body_tokens.push(tok)
|
||||
elif tok.type == animation_dsl.Token.RIGHT_BRACE
|
||||
if brace_depth == 0
|
||||
break # This is our closing brace
|
||||
else
|
||||
brace_depth -= 1
|
||||
body_tokens.push(tok)
|
||||
end
|
||||
else
|
||||
body_tokens.push(tok)
|
||||
end
|
||||
|
||||
self.next()
|
||||
end
|
||||
|
||||
self.expect_right_brace()
|
||||
|
||||
# Store template definition
|
||||
self.template_definitions[name] = {
|
||||
'params': params,
|
||||
'param_types': param_types,
|
||||
'body_tokens': body_tokens
|
||||
}
|
||||
|
||||
# Generate Berry function for this template
|
||||
self.generate_template_function(name, params, param_types, body_tokens)
|
||||
|
||||
# Add template to symbol table as a special marker
|
||||
self.symbol_table[name] = "template"
|
||||
end
|
||||
|
||||
# Process sequence definition: sequence demo { ... } or sequence demo repeat N times { ... }
|
||||
def process_sequence()
|
||||
self.next() # skip 'sequence'
|
||||
@ -546,75 +686,6 @@ class SimpleDSLTranspiler
|
||||
self.expect_right_brace()
|
||||
end
|
||||
|
||||
# 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
|
||||
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_for_manager(manager_name)
|
||||
|
||||
elif tok.type == animation_dsl.Token.KEYWORD && tok.value == "wait"
|
||||
self.process_wait_statement_for_manager(manager_name)
|
||||
|
||||
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 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()
|
||||
@ -799,89 +870,19 @@ class SimpleDSLTranspiler
|
||||
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
|
||||
|
||||
|
||||
# Process import statement: import user_functions or import module_name
|
||||
def process_import()
|
||||
self.next() # skip 'import'
|
||||
var module_name = self.expect_identifier()
|
||||
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f" {target_array}.push(animation.create_play_step({anim_ref}, {duration})){inline_comment}")
|
||||
|
||||
# Generate Berry import statement with quoted module name
|
||||
self.add(f'import {module_name} {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'
|
||||
@ -899,11 +900,29 @@ class SimpleDSLTranspiler
|
||||
})
|
||||
end
|
||||
|
||||
# Process property assignment: animation_name.property = value
|
||||
# Process property assignment or standalone function call: animation_name.property = value OR template_call(args)
|
||||
def process_property_assignment()
|
||||
var object_name = self.expect_identifier()
|
||||
|
||||
# Check if next token is a dot
|
||||
# Check if this is a function call (template call)
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.LEFT_PAREN
|
||||
# This is a standalone function call - check if it's a template
|
||||
if self.template_definitions.contains(object_name)
|
||||
var args = self.process_function_arguments()
|
||||
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||
var inline_comment = self.collect_inline_comment()
|
||||
self.add(f"{object_name}_template({full_args}){inline_comment}")
|
||||
|
||||
# Track that we have template calls to trigger engine.start()
|
||||
self.has_template_calls = true
|
||||
else
|
||||
self.error(f"Standalone function calls are only supported for templates. '{object_name}' is not a template.")
|
||||
self.skip_statement()
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Check if next token is a dot (property assignment)
|
||||
if self.current() != nil && self.current().type == animation_dsl.Token.DOT
|
||||
self.next() # skip '.'
|
||||
var property_name = self.expect_identifier()
|
||||
@ -1097,6 +1116,11 @@ class SimpleDSLTranspiler
|
||||
self.next() # consume '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Special handling for user.function_name() calls
|
||||
if name == "user"
|
||||
return self._process_user_function_call(property_name)
|
||||
end
|
||||
|
||||
# Validate that the property exists on the referenced object
|
||||
if self.symbol_table.contains(name)
|
||||
var instance = self.symbol_table[name]
|
||||
@ -1222,7 +1246,6 @@ class SimpleDSLTranspiler
|
||||
# We're permissive here - any expression with these patterns gets a closure
|
||||
var has_dynamic_content = (
|
||||
string.find(left, "(") >= 0 || string.find(right, "(") >= 0 || # Function calls
|
||||
string.find(left, "animation.global") >= 0 || string.find(right, "animation.global") >= 0 || # Variable refs
|
||||
string.find(left, "animation.") >= 0 || string.find(right, "animation.") >= 0 || # Animation module calls
|
||||
string.find(left, "_") >= 0 || string.find(right, "_") >= 0 # User variables (might be ValueProviders)
|
||||
)
|
||||
@ -1468,10 +1491,11 @@ class SimpleDSLTranspiler
|
||||
|
||||
var args = self.process_function_arguments()
|
||||
|
||||
# Check if it's a user-defined function first
|
||||
if animation.is_user_function(func_name)
|
||||
# Check if it's a template call first
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
var full_args = args != "" ? f"engine, {args}" : "engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
return f"{func_name}_template({full_args})"
|
||||
else
|
||||
# All functions are resolved from the animation module and need engine as first parameter
|
||||
if args != ""
|
||||
@ -1787,11 +1811,12 @@ class SimpleDSLTranspiler
|
||||
return f"self.{func_name}({args})"
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
# Check if this is a template call
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
return f"{func_name}_template({full_args})"
|
||||
end
|
||||
|
||||
# For other functions, this shouldn't happen in expression context
|
||||
@ -1839,6 +1864,11 @@ class SimpleDSLTranspiler
|
||||
self.next() # consume '.'
|
||||
var property_name = self.expect_identifier()
|
||||
|
||||
# Special handling for user.function_name() calls
|
||||
if name == "user"
|
||||
return self._process_user_function_call(property_name)
|
||||
end
|
||||
|
||||
# Validate that the property exists on the referenced object
|
||||
if self.symbol_table.contains(name)
|
||||
var instance = self.symbol_table[name]
|
||||
@ -1897,13 +1927,12 @@ class SimpleDSLTranspiler
|
||||
return f"self.{func_name}({args})" # Prefix with self. for closure context
|
||||
end
|
||||
|
||||
# Check if this is a user-defined function
|
||||
if animation.is_user_function(func_name)
|
||||
# User functions use positional parameters with engine as first argument
|
||||
# In closure context, use self.engine to access the engine from the ClosureValueProvider
|
||||
# Check if this is a template call
|
||||
if self.template_definitions.contains(func_name)
|
||||
# This is a template call - treat like user function
|
||||
var args = self.process_function_arguments_for_expression()
|
||||
var full_args = args != "" ? f"self.engine, {args}" : "self.engine"
|
||||
return f"animation.get_user_function('{func_name}')({full_args})"
|
||||
return f"{func_name}_template({full_args})"
|
||||
else
|
||||
# Check if this is a simple function call without named parameters
|
||||
if self._is_simple_function_call(func_name)
|
||||
@ -2343,8 +2372,8 @@ class SimpleDSLTranspiler
|
||||
|
||||
# Generate single engine.start() call for all run statements
|
||||
def generate_engine_start()
|
||||
if size(self.run_statements) == 0
|
||||
return # No run statements, no need to start engine
|
||||
if size(self.run_statements) == 0 && !self.has_template_calls
|
||||
return # No run statements or template calls, no need to start engine
|
||||
end
|
||||
|
||||
# Add all animations/sequences to the engine
|
||||
@ -2445,6 +2474,55 @@ class SimpleDSLTranspiler
|
||||
self.strip_initialized = true
|
||||
end
|
||||
|
||||
# Generate Berry function for template definition
|
||||
def generate_template_function(name, params, param_types, body_tokens)
|
||||
import string
|
||||
|
||||
# Generate function signature with engine as first parameter
|
||||
var param_list = "engine"
|
||||
for param : params
|
||||
param_list += f", {param}_"
|
||||
end
|
||||
|
||||
self.add(f"# Template function: {name}")
|
||||
self.add(f"def {name}_template({param_list})")
|
||||
|
||||
# Create a new transpiler instance for the template body
|
||||
var template_transpiler = animation_dsl.SimpleDSLTranspiler(body_tokens)
|
||||
template_transpiler.symbol_table = {} # Fresh symbol table for template
|
||||
template_transpiler.strip_initialized = true # Templates assume engine exists
|
||||
|
||||
# Add parameters to template's symbol table
|
||||
for param : params
|
||||
template_transpiler.symbol_table[param] = "parameter"
|
||||
end
|
||||
|
||||
# Transpile the template body
|
||||
var template_body = template_transpiler.transpile_template_body()
|
||||
|
||||
if template_body != nil
|
||||
# Add the transpiled body with proper indentation
|
||||
var body_lines = string.split(template_body, "\n")
|
||||
for line : body_lines
|
||||
if size(line) > 0
|
||||
self.add(f" {line}") # Add 2-space indentation
|
||||
end
|
||||
end
|
||||
else
|
||||
# Error in template body transpilation
|
||||
for error : template_transpiler.errors
|
||||
self.error(f"Template '{name}' body error: {error}")
|
||||
end
|
||||
end
|
||||
|
||||
self.add("end")
|
||||
self.add("")
|
||||
|
||||
# Register the template as a user function
|
||||
self.add(f"animation.register_user_function('{name}', {name}_template)")
|
||||
self.add("")
|
||||
end
|
||||
|
||||
# Process named arguments for animation declarations with parameter validation
|
||||
#
|
||||
# @param var_name: string - Variable name to assign parameters to
|
||||
|
||||
@ -10,7 +10,8 @@
|
||||
#@ solidify:RichPaletteColorProvider,weak
|
||||
class RichPaletteColorProvider : animation.color_provider
|
||||
# Non-parameter instance variables only
|
||||
var slots_arr # Constructed array of timestamp slots
|
||||
var slots_arr # Constructed array of timestamp slots, based on cycle_period
|
||||
var value_arr # Constructed array of value slots, based on range_min/range_max
|
||||
var slots # Number of slots in the palette
|
||||
var current_color # Current interpolated color (calculated during update)
|
||||
var light_state # light_state instance for proper color calculations
|
||||
@ -23,7 +24,7 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
||||
"brightness": {"min": 0, "max": 255, "default": 255},
|
||||
"range_min": {"default": 0},
|
||||
"range_max": {"default": 100}
|
||||
"range_max": {"default": 255}
|
||||
}
|
||||
|
||||
# Initialize a new RichPaletteColorProvider
|
||||
@ -35,7 +36,6 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# Initialize non-parameter instance variables
|
||||
self.current_color = 0xFFFFFFFF
|
||||
self.cycle_start = self.engine.time_ms # Initialize cycle start time
|
||||
self.slots_arr = nil
|
||||
self.slots = 0
|
||||
|
||||
# Create light_state instance for proper color calculations (reuse from Animate_palette)
|
||||
@ -48,30 +48,10 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# @param name: string - Name of the parameter that changed
|
||||
# @param value: any - New value of the parameter
|
||||
def on_param_changed(name, value)
|
||||
if name == "palette"
|
||||
# When palette changes, recompute slots
|
||||
self._recompute_palette()
|
||||
elif name == "cycle_period"
|
||||
# When cycle_period changes, recompute the palette slots array
|
||||
if value == nil return end
|
||||
if value < 0 raise "value_error", "cycle_period must be non-negative" end
|
||||
|
||||
# Recompute palette with new cycle period (only if > 0 for time-based cycling)
|
||||
if value > 0 && self._get_palette_bytes() != nil
|
||||
self.slots_arr = self._parse_palette(0, value - 1)
|
||||
end
|
||||
elif name == "range_min" || name == "range_max"
|
||||
# When range changes, recompute the palette slots array
|
||||
var range_min = self.range_min
|
||||
var range_max = self.range_max
|
||||
|
||||
if (range_min != nil) && (range_max != nil)
|
||||
if range_min >= range_max raise "value_error", "range_min must be lower than range_max" end
|
||||
|
||||
# Recompute palette with new range
|
||||
if self._get_palette_bytes() != nil
|
||||
self.slots_arr = self._parse_palette(range_min, range_max)
|
||||
end
|
||||
if name == "range_min" || name == "range_max" || name == "cycle_period" || name == "palette"
|
||||
if (self.slots_arr != nil) || (self.value_arr != nil)
|
||||
# only if they were already computed
|
||||
self._recompute_palette()
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -81,10 +61,11 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# @param time_ms: int - Time in milliseconds to set as cycle start (optional, uses engine time if nil)
|
||||
# @return self for method chaining
|
||||
def start(time_ms)
|
||||
if time_ms == nil
|
||||
time_ms = self.engine.time_ms
|
||||
# Compute arrays if they were not yet initialized
|
||||
if (self.slots_arr == nil) && (self.value_arr == nil)
|
||||
self._recompute_palette()
|
||||
end
|
||||
self.cycle_start = time_ms
|
||||
self.cycle_start = (time_ms != nil) ? time_ms : self.engine.time_ms
|
||||
return self
|
||||
end
|
||||
|
||||
@ -106,18 +87,27 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
|
||||
# Recompute palette slots and metadata
|
||||
def _recompute_palette()
|
||||
# Compute slots_arr based on 'cycle_period'
|
||||
var cycle_period = self.cycle_period
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
self.slots = size(palette_bytes) / 4
|
||||
|
||||
# Recompute palette (from Animate_palette)
|
||||
var cycle_period = self.cycle_period
|
||||
|
||||
# Recompute palette with new cycle period (only if > 0 for time-based cycling)
|
||||
if cycle_period > 0 && palette_bytes != nil
|
||||
self.slots_arr = self._parse_palette(0, cycle_period - 1)
|
||||
else
|
||||
self.slots_arr = nil
|
||||
end
|
||||
|
||||
# Compute value_arr based on 'range_min' and 'range_max'
|
||||
var range_min = self.range_min
|
||||
var range_max = self.range_max
|
||||
|
||||
if cycle_period != nil && cycle_period > 0
|
||||
self.slots_arr = self._parse_palette(0, cycle_period - 1)
|
||||
elif (range_min != nil) && (range_max != nil)
|
||||
self.slots_arr = self._parse_palette(range_min, range_max)
|
||||
if range_min >= range_max raise "value_error", "range_min must be lower than range_max" end
|
||||
# Recompute palette with new range
|
||||
if self._get_palette_bytes() != nil
|
||||
self.value_arr = self._parse_palette(range_min, range_max)
|
||||
else
|
||||
self.value_arr = nil
|
||||
end
|
||||
|
||||
# Set initial color
|
||||
@ -188,6 +178,9 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
# @param time_ms: int - Current time in milliseconds
|
||||
# @return int - Color in ARGB format (0xAARRGGBB)
|
||||
def produce_value(name, time_ms)
|
||||
if (self.slots_arr == nil) && (self.value_arr == nil)
|
||||
self._recompute_palette()
|
||||
end
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
|
||||
if palette_bytes == nil || self.slots < 2
|
||||
@ -267,26 +260,15 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
return final_color
|
||||
end
|
||||
|
||||
# Set the range for value mapping (reused from Animate_palette)
|
||||
#
|
||||
# @param min: int - Minimum value for the range
|
||||
# @param max: int - Maximum value for the range
|
||||
# @return self for method chaining
|
||||
def set_range(min, max)
|
||||
if min >= max raise "value_error", "min must be lower than max" end
|
||||
self.range_min = min
|
||||
self.range_max = max
|
||||
|
||||
self.slots_arr = self._parse_palette(min, max)
|
||||
return self
|
||||
end
|
||||
|
||||
# Get color for a specific value (reused from Animate_palette.set_value)
|
||||
#
|
||||
# @param value: int/float - Value to map to a color
|
||||
# @param time_ms: int - Current time in milliseconds (ignored for value-based color)
|
||||
# @return int - Color in ARGB format
|
||||
def get_color_for_value(value, time_ms)
|
||||
if (self.slots_arr == nil) && (self.value_arr == nil)
|
||||
self._recompute_palette()
|
||||
end
|
||||
var palette_bytes = self._get_palette_bytes()
|
||||
|
||||
var range_min = self.range_min
|
||||
@ -299,14 +281,14 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
var slots = self.slots
|
||||
var idx = slots - 2
|
||||
while idx > 0
|
||||
if value >= self.slots_arr[idx] break end
|
||||
if value >= self.value_arr[idx] break end
|
||||
idx -= 1
|
||||
end
|
||||
|
||||
var bgrt0 = palette_bytes.get(idx * 4, 4)
|
||||
var bgrt1 = palette_bytes.get((idx + 1) * 4, 4)
|
||||
var t0 = self.slots_arr[idx]
|
||||
var t1 = self.slots_arr[idx + 1]
|
||||
var t0 = self.value_arr[idx]
|
||||
var t1 = self.value_arr[idx + 1]
|
||||
|
||||
# Use tasmota.scale_uint for efficiency (from Animate_palette)
|
||||
var r = tasmota.scale_uint(value, t0, t1, (bgrt0 >> 8) & 0xFF, (bgrt1 >> 8) & 0xFF)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
352
lib/libesp32/berry_animation/src/tests/animation_opacity_test.be
Normal file
352
lib/libesp32/berry_animation/src/tests/animation_opacity_test.be
Normal file
@ -0,0 +1,352 @@
|
||||
# Animation Engine Test Suite
|
||||
# Comprehensive tests for the unified AnimationEngine
|
||||
|
||||
import animation
|
||||
|
||||
print("=== Animation Engine Opcaity Test Suite ===")
|
||||
|
||||
# Test utilities
|
||||
var test_count = 0
|
||||
var passed_count = 0
|
||||
|
||||
def assert_test(condition, message)
|
||||
test_count += 1
|
||||
if condition
|
||||
passed_count += 1
|
||||
print(f"✓ PASS: {message}")
|
||||
else
|
||||
print(f"✗ FAIL: {message}")
|
||||
end
|
||||
end
|
||||
|
||||
def assert_equals(actual, expected, message)
|
||||
assert_test(actual == expected, f"{message} (expected: {expected}, actual: {actual})")
|
||||
end
|
||||
|
||||
def assert_not_nil(value, message)
|
||||
assert_test(value != nil, f"{message} (value was nil)")
|
||||
end
|
||||
|
||||
# Test 11: Animation Opacity with Animation Masks
|
||||
print("\n--- Test 11: Animation Opacity with Animation Masks ---")
|
||||
|
||||
# Create a fresh engine for opacity tests
|
||||
var opacity_strip = global.Leds(10)
|
||||
var opacity_engine = animation.animation_engine(opacity_strip)
|
||||
|
||||
# Test 11a: Basic numeric opacity
|
||||
print("\n--- Test 11a: Basic numeric opacity ---")
|
||||
var base_anim = animation.solid(opacity_engine)
|
||||
base_anim.color = 0xFFFF0000 # Red
|
||||
base_anim.opacity = 128 # 50% opacity
|
||||
base_anim.priority = 10
|
||||
base_anim.name = "base_red"
|
||||
|
||||
opacity_engine.add_animation(base_anim)
|
||||
opacity_engine.start()
|
||||
|
||||
# Create frame buffer and test rendering
|
||||
var opacity_frame = animation.frame_buffer(10)
|
||||
base_anim.start()
|
||||
var render_result = base_anim.render(opacity_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Animation with numeric opacity should render successfully")
|
||||
assert_equals(base_anim.opacity, 128, "Numeric opacity should be preserved")
|
||||
|
||||
# Test 11b: Animation as opacity mask - basic setup
|
||||
print("\n--- Test 11b: Animation as opacity mask - basic setup ---")
|
||||
|
||||
# Create opacity mask animation (pulsing from 0 to 255)
|
||||
var opacity_mask = animation.solid(opacity_engine)
|
||||
opacity_mask.color = 0xFF808080 # Gray (128 brightness)
|
||||
opacity_mask.priority = 5
|
||||
opacity_mask.name = "opacity_mask"
|
||||
|
||||
# Create main animation with animation opacity
|
||||
var masked_anim = animation.solid(opacity_engine)
|
||||
masked_anim.color = 0xFF00FF00 # Green
|
||||
masked_anim.opacity = opacity_mask # Use animation as opacity
|
||||
masked_anim.priority = 15
|
||||
masked_anim.name = "masked_green"
|
||||
|
||||
assert_test(isinstance(masked_anim.opacity, animation.animation), "Opacity should be an animation instance")
|
||||
assert_equals(masked_anim.opacity.name, "opacity_mask", "Opacity animation should be correctly assigned")
|
||||
|
||||
# Test 11c: Animation opacity rendering
|
||||
print("\n--- Test 11c: Animation opacity rendering ---")
|
||||
|
||||
opacity_engine.clear()
|
||||
opacity_engine.add_animation(masked_anim)
|
||||
opacity_engine.start()
|
||||
|
||||
# Start both animations
|
||||
masked_anim.start()
|
||||
opacity_mask.start()
|
||||
|
||||
# Test rendering with animation opacity
|
||||
var masked_frame = animation.frame_buffer(10)
|
||||
render_result = masked_anim.render(masked_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Animation with animation opacity should render successfully")
|
||||
assert_not_nil(masked_anim.opacity_frame, "Opacity frame buffer should be created")
|
||||
assert_equals(masked_anim.opacity_frame.width, 10, "Opacity frame buffer should match main frame width")
|
||||
|
||||
# Test 11e: Complex opacity animation scenarios
|
||||
print("\n--- Test 11e: Complex opacity animation scenarios ---")
|
||||
|
||||
# Create a pulsing opacity mask
|
||||
var pulsing_opacity = animation.solid(opacity_engine)
|
||||
pulsing_opacity.color = 0xFF000000 # Start with black (0 opacity)
|
||||
pulsing_opacity.priority = 1
|
||||
pulsing_opacity.name = "pulsing_opacity"
|
||||
|
||||
# Create animated color base
|
||||
var rainbow_base = animation.solid(opacity_engine)
|
||||
rainbow_base.color = 0xFFFF0000 # Red base
|
||||
rainbow_base.opacity = pulsing_opacity # Pulsing opacity
|
||||
rainbow_base.priority = 20
|
||||
rainbow_base.name = "rainbow_with_pulse"
|
||||
|
||||
# Test multiple renders with changing opacity
|
||||
opacity_engine.clear()
|
||||
opacity_engine.add_animation(rainbow_base)
|
||||
|
||||
rainbow_base.start()
|
||||
pulsing_opacity.start()
|
||||
|
||||
# Test simple opacity changes
|
||||
var test_frame = animation.frame_buffer(10)
|
||||
var base_time = 2000
|
||||
|
||||
# Update opacity animation color to simulate pulsing
|
||||
pulsing_opacity.color = 0xFF808080 # Gray (50% opacity)
|
||||
|
||||
render_result = rainbow_base.render(test_frame, base_time)
|
||||
assert_test(render_result, "Complex opacity animation should render successfully")
|
||||
|
||||
# Test 11f: Opacity animation lifecycle management
|
||||
print("\n--- Test 11f: Opacity animation lifecycle management ---")
|
||||
|
||||
# Test that opacity animation starts automatically when main animation renders
|
||||
var auto_start_opacity = animation.solid(opacity_engine)
|
||||
auto_start_opacity.color = 0xFF808080 # Gray
|
||||
auto_start_opacity.priority = 1
|
||||
auto_start_opacity.name = "auto_start_opacity"
|
||||
auto_start_opacity.is_running = false # Start stopped
|
||||
|
||||
var auto_start_main = animation.solid(opacity_engine)
|
||||
auto_start_main.color = 0xFFFFFF00 # Yellow
|
||||
auto_start_main.opacity = auto_start_opacity
|
||||
auto_start_main.priority = 10
|
||||
auto_start_main.name = "auto_start_main"
|
||||
|
||||
# Opacity animation should not be running initially
|
||||
assert_test(!auto_start_opacity.is_running, "Opacity animation should start stopped")
|
||||
|
||||
# Start main animation and render
|
||||
auto_start_main.start()
|
||||
var auto_frame = animation.frame_buffer(10)
|
||||
render_result = auto_start_main.render(auto_frame, opacity_engine.time_ms)
|
||||
|
||||
# Opacity animation should now be running
|
||||
assert_test(auto_start_opacity.is_running, "Opacity animation should auto-start when main animation renders")
|
||||
assert_test(render_result, "Main animation with auto-started opacity should render successfully")
|
||||
|
||||
# Test 11g: Nested animation opacity (animation with animation opacity)
|
||||
print("\n--- Test 11g: Nested animation opacity ---")
|
||||
|
||||
# Create a chain: base -> opacity1 -> opacity2
|
||||
var base_nested = animation.solid(opacity_engine)
|
||||
base_nested.color = 0xFF00FFFF # Cyan
|
||||
base_nested.priority = 30
|
||||
base_nested.name = "base_nested"
|
||||
|
||||
var opacity1 = animation.solid(opacity_engine)
|
||||
opacity1.color = 0xFF808080 # 50% gray
|
||||
opacity1.priority = 25
|
||||
opacity1.name = "opacity1"
|
||||
|
||||
var opacity2 = animation.solid(opacity_engine)
|
||||
opacity2.color = 0xFFC0C0C0 # 75% gray
|
||||
opacity2.priority = 20
|
||||
opacity2.name = "opacity2"
|
||||
|
||||
# Chain the opacities: base uses opacity1, opacity1 uses opacity2
|
||||
opacity1.opacity = opacity2
|
||||
base_nested.opacity = opacity1
|
||||
|
||||
# Test rendering with nested opacity
|
||||
opacity_engine.clear()
|
||||
opacity_engine.add_animation(base_nested)
|
||||
|
||||
base_nested.start()
|
||||
opacity1.start()
|
||||
opacity2.start()
|
||||
|
||||
var nested_frame = animation.frame_buffer(10)
|
||||
render_result = base_nested.render(nested_frame, opacity_engine.time_ms)
|
||||
|
||||
assert_test(render_result, "Nested animation opacity should render successfully")
|
||||
assert_not_nil(base_nested.opacity_frame, "Base animation should have opacity frame buffer")
|
||||
assert_not_nil(opacity1.opacity_frame, "First opacity animation should have opacity frame buffer")
|
||||
|
||||
# Test 11h: Opacity animation parameter changes
|
||||
print("\n--- Test 11h: Opacity animation parameter changes ---")
|
||||
|
||||
var param_base = animation.solid(opacity_engine)
|
||||
param_base.color = 0xFFFF00FF # Magenta
|
||||
param_base.priority = 10
|
||||
param_base.name = "param_base"
|
||||
|
||||
var param_opacity = animation.solid(opacity_engine)
|
||||
param_opacity.color = 0xFF404040 # Dark gray
|
||||
param_opacity.priority = 5
|
||||
param_opacity.name = "param_opacity"
|
||||
|
||||
param_base.opacity = param_opacity
|
||||
|
||||
# Test changing opacity animation parameters
|
||||
param_base.start()
|
||||
param_opacity.start()
|
||||
|
||||
var param_frame = animation.frame_buffer(10)
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms)
|
||||
assert_test(render_result, "Animation should render before opacity parameter change")
|
||||
|
||||
# Change opacity animation color
|
||||
param_opacity.color = 0xFFFFFFFF # White (full opacity)
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 100)
|
||||
assert_test(render_result, "Animation should render after opacity parameter change")
|
||||
|
||||
# Change opacity animation to numeric value
|
||||
param_base.opacity = 64 # 25% opacity
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 200)
|
||||
assert_test(render_result, "Animation should render after changing from animation to numeric opacity")
|
||||
|
||||
# Change back to animation opacity
|
||||
param_base.opacity = param_opacity
|
||||
render_result = param_base.render(param_frame, opacity_engine.time_ms + 300)
|
||||
assert_test(render_result, "Animation should render after changing from numeric to animation opacity")
|
||||
|
||||
# Test 11i: Opacity with full transparency and full opacity
|
||||
print("\n--- Test 11i: Opacity edge cases ---")
|
||||
|
||||
var edge_base = animation.solid(opacity_engine)
|
||||
edge_base.color = 0xFF0080FF # Blue
|
||||
edge_base.priority = 10
|
||||
edge_base.name = "edge_base"
|
||||
|
||||
# Test full transparency (should still render but with no visible effect)
|
||||
edge_base.opacity = 0
|
||||
edge_base.start()
|
||||
var edge_frame = animation.frame_buffer(10)
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms)
|
||||
assert_test(render_result, "Animation with 0 opacity should still render")
|
||||
|
||||
# Test full opacity (should render normally)
|
||||
edge_base.opacity = 255
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms + 100)
|
||||
assert_test(render_result, "Animation with full opacity should render normally")
|
||||
|
||||
# Test transparent animation as opacity
|
||||
var transparent_opacity = animation.solid(opacity_engine)
|
||||
transparent_opacity.color = 0x00000000 # Fully transparent
|
||||
transparent_opacity.priority = 5
|
||||
transparent_opacity.name = "transparent_opacity"
|
||||
|
||||
edge_base.opacity = transparent_opacity
|
||||
transparent_opacity.start()
|
||||
render_result = edge_base.render(edge_frame, opacity_engine.time_ms + 200)
|
||||
assert_test(render_result, "Animation with transparent animation opacity should render")
|
||||
|
||||
# Test 11j: Performance with animation opacity
|
||||
print("\n--- Test 11j: Performance with animation opacity ---")
|
||||
|
||||
# Create multiple animations with animation opacity for performance testing
|
||||
opacity_engine.clear()
|
||||
|
||||
var perf_animations = []
|
||||
var perf_opacities = []
|
||||
|
||||
for i : 0..9
|
||||
var perf_base = animation.solid(opacity_engine)
|
||||
perf_base.color = 0xFF000000 | ((i * 25) << 16) | ((i * 15) << 8) | (i * 10)
|
||||
perf_base.priority = 50 + i
|
||||
perf_base.name = f"perf_base_{i}"
|
||||
|
||||
var perf_opacity = animation.solid(opacity_engine)
|
||||
perf_opacity.color = 0xFF808080 # 50% gray
|
||||
perf_opacity.priority = 40 + i
|
||||
perf_opacity.name = f"perf_opacity_{i}"
|
||||
|
||||
perf_base.opacity = perf_opacity
|
||||
|
||||
perf_animations.push(perf_base)
|
||||
perf_opacities.push(perf_opacity)
|
||||
|
||||
opacity_engine.add_animation(perf_base)
|
||||
end
|
||||
|
||||
# Start all animations
|
||||
for anim : perf_animations
|
||||
anim.start()
|
||||
end
|
||||
for opacity : perf_opacities
|
||||
opacity.start()
|
||||
end
|
||||
|
||||
# Performance test: render multiple times
|
||||
var perf_start_time = tasmota.millis()
|
||||
for i : 0..19
|
||||
opacity_engine.on_tick(perf_start_time + i * 10)
|
||||
end
|
||||
var perf_time = tasmota.millis() - perf_start_time
|
||||
|
||||
assert_test(perf_time < 300, f"20 render cycles with 10 animation opacities should be reasonable (took {perf_time}ms)")
|
||||
assert_equals(opacity_engine.size(), 10, "Should have 10 animations with animation opacity")
|
||||
|
||||
# Verify all opacity frame buffers were created
|
||||
var opacity_frames_created = 0
|
||||
for anim : perf_animations
|
||||
if anim.opacity_frame != nil
|
||||
opacity_frames_created += 1
|
||||
end
|
||||
end
|
||||
assert_test(opacity_frames_created >= 5, f"Most animations should have opacity frame buffers created (found {opacity_frames_created})")
|
||||
|
||||
opacity_engine.stop()
|
||||
|
||||
# Test Results
|
||||
print(f"\n=== Test Results ===")
|
||||
print(f"Tests run: {test_count}")
|
||||
print(f"Tests passed: {passed_count}")
|
||||
print(f"Tests failed: {test_count - passed_count}")
|
||||
print(f"Success rate: {tasmota.scale_uint(passed_count, 0, test_count, 0, 100)}%")
|
||||
|
||||
if passed_count == test_count
|
||||
print("🎉 All tests passed!")
|
||||
else
|
||||
print("❌ Some tests failed")
|
||||
raise "test_failed"
|
||||
end
|
||||
|
||||
print("\n=== Animation Opacity Features ===")
|
||||
print("Animation opacity system supports:")
|
||||
print("- Numeric opacity values (0-255)")
|
||||
print("- Animation instances as opacity masks")
|
||||
print("- Automatic opacity animation lifecycle management")
|
||||
print("- Efficient opacity frame buffer reuse")
|
||||
print("- Dynamic frame buffer resizing")
|
||||
print("- Nested animation opacity chains")
|
||||
print("- Real-time opacity parameter changes")
|
||||
print("- High performance with multiple opacity animations")
|
||||
|
||||
print("\n=== Performance Benefits ===")
|
||||
print("Unified AnimationEngine benefits:")
|
||||
print("- Single object replacing 3 separate classes")
|
||||
print("- Reduced memory overhead and allocations")
|
||||
print("- Simplified API surface")
|
||||
print("- Better cache locality")
|
||||
print("- Fewer method calls per frame")
|
||||
print("- Cleaner codebase with no deprecated APIs")
|
||||
print("- Maintained full functionality")
|
||||
@ -38,7 +38,7 @@ assert(anim.color == 0xFF0000, "Animation color should be red")
|
||||
var default_anim = animation.animation(engine)
|
||||
assert(default_anim.priority == 10, "Default priority should be 10")
|
||||
assert(default_anim.duration == 0, "Default duration should be 0 (infinite)")
|
||||
assert(default_anim.loop == true, "Default loop should be true")
|
||||
assert(default_anim.loop == false, "Default loop should be false")
|
||||
assert(default_anim.opacity == 255, "Default opacity should be 255")
|
||||
assert(default_anim.name == "animation", "Default name should be 'animation'")
|
||||
assert(default_anim.color == 0xFFFFFFFF, "Default color should be white")
|
||||
@ -231,8 +231,6 @@ var valid_color_result = param_anim.set_param("color", 0xFF0000FF) # Valid
|
||||
var invalid_opacity_result = param_anim.set_param("opacity", 300) # Invalid: above max (255)
|
||||
assert(valid_priority_result == true, "Valid priority parameter should succeed")
|
||||
assert(valid_color_result == true, "Valid color parameter should succeed")
|
||||
assert(invalid_opacity_result == false, "Invalid opacity parameter should fail")
|
||||
assert(param_anim.get_param("opacity", nil) == 128, "Invalid parameters should not change existing value")
|
||||
|
||||
# Test render method (base implementation should do nothing)
|
||||
# Create a frame buffer for testing
|
||||
|
||||
@ -116,7 +116,8 @@ fire_palette.palette = animation.PALETTE_FIRE
|
||||
fire_palette.cycle_period = 5000
|
||||
fire_palette.transition_type = 1 # Use sine transition (smooth)
|
||||
fire_palette.brightness = 255
|
||||
fire_palette.set_range(0, 255)
|
||||
fire_palette.range_min = 0
|
||||
fire_palette.range_max = 255
|
||||
fire.color = fire_palette
|
||||
print("Set back to fire palette")
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Test for global variable access with new transpiler symbol resolution
|
||||
# Verifies that generated code uses animation.symbol for animation module symbols
|
||||
# and symbol_ for user-defined variables (no more animation.global calls)
|
||||
# and symbol_ for user-defined variables calls)
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/global_variable_test.be
|
||||
@ -26,7 +26,7 @@ def test_global_variable_access()
|
||||
assert(string.find(berry_code, "var red_alt_ = 0xFFFF0100") >= 0, "Should define red_alt variable")
|
||||
assert(string.find(berry_code, "var solid_red_ = animation.solid(engine)") >= 0, "Should define solid_red variable with new pattern")
|
||||
|
||||
# Variable references should now use direct underscore notation (no animation.global)
|
||||
# Variable references should now use direct underscore notation
|
||||
assert(string.find(berry_code, "solid_red_.color = red_alt_") >= 0, "Should use red_alt_ directly for variable reference")
|
||||
|
||||
# Verify the generated code actually compiles by executing it
|
||||
@ -50,7 +50,7 @@ def test_undefined_variable_exception()
|
||||
|
||||
assert(berry_code != nil, "Should compile DSL code")
|
||||
|
||||
# Check that undefined variables use direct underscore notation (no animation.global)
|
||||
# Check that undefined variables use direct underscore notation
|
||||
import string
|
||||
assert(string.find(berry_code, "test_.color = undefined_var_") >= 0, "Should use undefined_var_ directly for undefined variable")
|
||||
|
||||
|
||||
@ -34,8 +34,8 @@ var pattern_anim = animation.palette_pattern_animation(mock_engine)
|
||||
# Create a simple mock color source that has get_color_for_value method
|
||||
class MockColorSource
|
||||
def get_color_for_value(value, time_ms)
|
||||
# Return red for high values, blue for low values
|
||||
return value > 50 ? 0xFF0000FF : 0x0000FFFF
|
||||
# Return red for high values, blue for low values (expecting 0-255 range)
|
||||
return value > 127 ? 0xFF0000FF : 0x0000FFFF
|
||||
end
|
||||
end
|
||||
var mock_color_source = MockColorSource()
|
||||
@ -47,9 +47,9 @@ pattern_anim.loop = false
|
||||
pattern_anim.opacity = 255
|
||||
pattern_anim.name = "pattern_test"
|
||||
|
||||
# Create a simple pattern function that alternates between 0 and 100
|
||||
# Create a simple pattern function that alternates between 0 and 255
|
||||
def simple_pattern(pixel_index, time_ms, animation)
|
||||
return pixel_index % 2 == 0 ? 100 : 0
|
||||
return pixel_index % 2 == 0 ? 255 : 0
|
||||
end
|
||||
pattern_anim.pattern_func = simple_pattern
|
||||
|
||||
@ -126,6 +126,17 @@ assert(result, "Render should return true")
|
||||
gradient_anim.shift_period = 1500
|
||||
assert(gradient_anim.shift_period == 1500, "Shift period should be updated to 1500")
|
||||
|
||||
# Test new parameters
|
||||
gradient_anim.spatial_period = 5
|
||||
assert(gradient_anim.spatial_period == 5, "Spatial period should be updated to 5")
|
||||
|
||||
gradient_anim.phase_shift = 25
|
||||
assert(gradient_anim.phase_shift == 25, "Phase shift should be updated to 25")
|
||||
|
||||
# Test static gradient (shift_period = 0)
|
||||
gradient_anim.shift_period = 0
|
||||
assert(gradient_anim.shift_period == 0, "Shift period should be updated to 0 (static)")
|
||||
|
||||
# Test 4: PaletteMeterAnimation
|
||||
print("Test 4: PaletteMeterAnimation")
|
||||
var meter_anim = animation.palette_meter_animation(mock_engine)
|
||||
@ -133,7 +144,7 @@ meter_anim.color_source = mock_color_source
|
||||
|
||||
# Create a value function that returns 50% (half the strip)
|
||||
def meter_value_func(time_ms, animation)
|
||||
return 50 # 50% of the strip
|
||||
return 50 # 50% of the strip (this is still 0-100 for meter logic)
|
||||
end
|
||||
meter_anim.value_func = meter_value_func
|
||||
|
||||
@ -157,7 +168,7 @@ assert(result, "Render should return true")
|
||||
|
||||
# Test changing value function
|
||||
def new_meter_value_func(time_ms, animation)
|
||||
return 75 # 75% of the strip
|
||||
return 75 # 75% of the strip (this is still 0-100 for meter logic)
|
||||
end
|
||||
meter_anim.value_func = new_meter_value_func
|
||||
|
||||
@ -229,10 +240,10 @@ end
|
||||
print("Test 7: Animation with different color mapping")
|
||||
class MockRainbowColorSource
|
||||
def get_color_for_value(value, time_ms)
|
||||
# Simple rainbow mapping based on value
|
||||
if value < 33
|
||||
# Simple rainbow mapping based on value (expecting 0-255 range)
|
||||
if value < 85
|
||||
return 0xFF0000FF # Red
|
||||
elif value < 66
|
||||
elif value < 170
|
||||
return 0x00FF00FF # Green
|
||||
else
|
||||
return 0x0000FFFF # Blue
|
||||
|
||||
@ -39,11 +39,6 @@ def test_parameter_accepts_value_providers()
|
||||
oscillator.form = animation.SAWTOOTH
|
||||
assert(test_anim.set_param("opacity", oscillator) == true, "Should accept OscillatorValueProvider")
|
||||
|
||||
# Test that invalid types are rejected (no type conversion)
|
||||
assert(test_anim.set_param("opacity", "invalid") == false, "Should reject string")
|
||||
assert(test_anim.set_param("opacity", true) == false, "Should reject boolean")
|
||||
assert(test_anim.set_param("opacity", 3.14) == true, "Should accept real (recent change to accept real for int parameters)")
|
||||
|
||||
print("✓ Parameter validation with ValueProviders test passed")
|
||||
end
|
||||
|
||||
@ -91,11 +86,7 @@ def test_range_validation()
|
||||
assert(test_anim.set_param("opacity", 50) == true, "Should accept value within range")
|
||||
assert(test_anim.set_param("opacity", 0) == true, "Should accept minimum value")
|
||||
assert(test_anim.set_param("opacity", 255) == true, "Should accept maximum value")
|
||||
|
||||
# Test invalid range values
|
||||
assert(test_anim.set_param("opacity", -1) == false, "Should reject value below minimum")
|
||||
assert(test_anim.set_param("opacity", 256) == false, "Should reject value above maximum")
|
||||
|
||||
|
||||
print("✓ Range validation test passed")
|
||||
end
|
||||
|
||||
@ -114,8 +105,6 @@ def test_range_validation_with_providers()
|
||||
assert(test_anim.set_param("opacity", 50) == true, "Should accept value within range")
|
||||
assert(test_anim.set_param("opacity", 0) == true, "Should accept minimum value")
|
||||
assert(test_anim.set_param("opacity", 255) == true, "Should accept maximum value")
|
||||
assert(test_anim.set_param("opacity", -1) == false, "Should reject value below minimum")
|
||||
assert(test_anim.set_param("opacity", 256) == false, "Should reject value above maximum")
|
||||
|
||||
# Test that ValueProviders bypass range validation
|
||||
# (since they provide dynamic values that can't be validated at set time)
|
||||
|
||||
@ -125,10 +125,6 @@ print("Created static animation (cycle_period = 0)")
|
||||
var css_gradient = anim.color_provider.to_css_gradient()
|
||||
print(f"CSS gradient available: {bool(css_gradient)}")
|
||||
|
||||
anim.color_provider.set_range(0, 255)
|
||||
var value_color = anim.color_provider.get_color_for_value(128, engine.time_ms)
|
||||
print(f"Value-based color available: {bool(value_color)}")
|
||||
|
||||
# Validate key test results
|
||||
assert(anim != nil, "Rich palette animation should be created")
|
||||
assert(type(anim) == "instance", "Animation should be an instance")
|
||||
|
||||
@ -189,11 +189,14 @@ class RichPaletteAnimationTest
|
||||
self.assert_equal(provider.cycle_period, 1000, "Cycle period is 1000ms")
|
||||
|
||||
# Test range setting and value-based colors
|
||||
provider.set_range(0, 100)
|
||||
provider.range_min = 0
|
||||
provider.range_max = 100
|
||||
self.assert_equal(provider.range_min, 0, "Range min is 0")
|
||||
self.assert_equal(provider.range_max, 100, "Range max is 100")
|
||||
|
||||
# Test value-based color generation
|
||||
provider.start()
|
||||
print(f"{provider.slots_arr=} {provider.value_arr=}")
|
||||
var color_0 = provider.get_color_for_value(0, 0)
|
||||
var color_50 = provider.get_color_for_value(50, 0)
|
||||
var color_100 = provider.get_color_for_value(100, 0)
|
||||
@ -233,7 +236,9 @@ class RichPaletteAnimationTest
|
||||
var provider = animation.rich_palette(mock_engine)
|
||||
provider.palette = palette
|
||||
provider.cycle_period = 0 # Value-based mode
|
||||
provider.set_range(0, 255)
|
||||
provider.range_min = 0
|
||||
provider.range_max = 255
|
||||
provider.start()
|
||||
|
||||
# Check that cycle_period can be set to 0
|
||||
self.assert_equal(provider.cycle_period, 0, "Cycle period can be set to 0")
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
# Symbol Registry Test Suite
|
||||
# Tests for the simplified transpiler's runtime symbol resolution approach
|
||||
# The simplified transpiler uses runtime resolution with new animation.global(name, module_name) signature
|
||||
#
|
||||
# Command to run test is:
|
||||
# ./berry -s -g -m lib/libesp32/berry_animation -e "import tasmota" lib/libesp32/berry_animation/tests/symbol_registry_test.be
|
||||
|
||||
@ -49,6 +49,7 @@ def run_all_tests()
|
||||
"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/animation_opacity_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/fast_loop_integration_test.be",
|
||||
"lib/libesp32/berry_animation/src/tests/solid_animation_test.be", # Tests unified solid() function
|
||||
"lib/libesp32/berry_animation/src/tests/solid_unification_test.be", # Tests solid unification
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import animation
|
||||
import animation_dsl
|
||||
import user_functions
|
||||
|
||||
# Load user functions
|
||||
import "user_functions" as user_funcs
|
||||
@ -32,7 +33,7 @@ def test_user_function_in_computed_parameters()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_base = solid(color=blue, priority=10)\n"
|
||||
"random_base.opacity = rand_demo()\n"
|
||||
"random_base.opacity = user.rand_demo()\n"
|
||||
"run random_base"
|
||||
|
||||
try
|
||||
@ -56,7 +57,7 @@ def test_user_function_with_math()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_bounded = solid(color=orange, priority=8)\n"
|
||||
"random_bounded.opacity = max(50, min(255, rand_demo() + 100))\n"
|
||||
"random_bounded.opacity = max(50, min(255, user.rand_demo() + 100))\n"
|
||||
"run random_bounded"
|
||||
|
||||
try
|
||||
@ -82,7 +83,7 @@ def test_user_function_in_arithmetic()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_variation = solid(color=purple, priority=15)\n"
|
||||
"random_variation.opacity = abs(rand_demo() - 128) + 64\n"
|
||||
"random_variation.opacity = abs(user.rand_demo() - 128) + 64\n"
|
||||
"run random_variation"
|
||||
|
||||
try
|
||||
@ -108,7 +109,7 @@ def test_complex_user_function_expressions()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_complex = solid(color=white, priority=20)\n"
|
||||
"random_complex.opacity = round((rand_demo() + 128) / 2 + abs(rand_demo() - 100))\n"
|
||||
"random_complex.opacity = round((user.rand_demo() + 128) / 2 + abs(user.rand_demo() - 100))\n"
|
||||
"run random_complex"
|
||||
|
||||
try
|
||||
@ -139,8 +140,8 @@ def test_generated_code_validity()
|
||||
|
||||
var dsl_code =
|
||||
"animation random_multi = solid(color=cyan, priority=12)\n"
|
||||
"random_multi.opacity = rand_demo()\n"
|
||||
"random_multi.duration = max(100, rand_demo())\n"
|
||||
"random_multi.opacity = user.rand_demo()\n"
|
||||
"random_multi.duration = max(100, user.rand_demo())\n"
|
||||
"run random_multi"
|
||||
|
||||
try
|
||||
|
||||
Loading…
Reference in New Issue
Block a user