Berry animation simplify gradient (#24290)
This commit is contained in:
parent
5967b4401c
commit
f5d8ec43fc
@ -22,13 +22,14 @@ ParameterizedObject (base class with parameter management and playable interface
|
||||
│ ├── BeaconAnimation (pulse at specific position)
|
||||
│ ├── CrenelPositionAnimation (crenel/square wave pattern)
|
||||
│ ├── BreatheAnimation (breathing effect)
|
||||
│ ├── BeaconAnimation (pulse at specific position)
|
||||
│ │ └── GradientAnimation (linear/radial color gradients)
|
||||
│ ├── PaletteGradientAnimation (gradient patterns with palette colors)
|
||||
│ │ ├── PaletteMeterAnimation (meter/bar patterns)
|
||||
│ │ └── GradientMeterAnimation (VU meter with gradient colors and peak hold)
|
||||
│ ├── CometAnimation (moving comet with tail)
|
||||
│ ├── FireAnimation (realistic fire effect)
|
||||
│ ├── TwinkleAnimation (twinkling stars effect)
|
||||
│ ├── GradientAnimation (color gradients)
|
||||
│ ├── NoiseAnimation (Perlin noise patterns)
|
||||
│ ├── WaveAnimation (wave motion effects)
|
||||
│ └── RichPaletteAnimation (smooth palette transitions)
|
||||
@ -590,19 +591,25 @@ Creates a realistic fire effect with flickering flames. Inherits from `Animation
|
||||
|
||||
### GradientAnimation
|
||||
|
||||
Creates smooth color gradients that can be linear or radial. Inherits from `Animation`.
|
||||
Creates smooth two-color gradients. Subclass of `BeaconAnimation` that uses beacon slew regions to create gradient effects.
|
||||
|
||||
| Parameter | Type | Default | Constraints | Description |
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `color` | instance | nil | nillable | Color provider (nil = rainbow gradient) |
|
||||
| `gradient_type` | int | 0 | 0-1 | 0=linear, 1=radial |
|
||||
| `direction` | int | 0 | 0-255 | Gradient direction/orientation |
|
||||
| `center_pos` | int | 128 | 0-255 | Center position for radial gradients |
|
||||
| `spread` | int | 255 | 1-255 | Gradient spread/compression |
|
||||
| `movement_speed` | int | 0 | 0-255 | Speed of gradient movement |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
| `color1` | int | 0xFFFF0000 | - | First color (default red) |
|
||||
| `color2` | int | 0xFF0000FF | - | Second color (default blue) |
|
||||
| `direction` | int | 0 | enum: [0, 1] | 0=forward (color1→color2), 1=reverse (color2→color1) |
|
||||
| `gradient_type` | int | 0 | enum: [0, 1] | 0=linear, 1=radial |
|
||||
| *(inherits all BeaconAnimation parameters)* | | | | |
|
||||
|
||||
**Factories**: `animation.gradient_animation(engine)`, `animation.gradient_rainbow_linear(engine)`, `animation.gradient_rainbow_radial(engine)`, `animation.gradient_two_color_linear(engine)`
|
||||
**Gradient Types:**
|
||||
- **Linear (0)**: Creates a 2-color gradient from `color1` to `color2` (or reversed if `direction=1`). Implemented as the left slew of a large beacon positioned at the right edge.
|
||||
- **Radial (1)**: Creates a symmetric gradient with `color1` at center and `color2` at edges (or reversed if `direction=1`). Implemented as a centered beacon with size=1 and slew regions extending to the edges.
|
||||
|
||||
**Implementation Details:**
|
||||
- Linear gradient uses a beacon with `beacon_size=1000` (off-screen) and `slew_size=strip_length`
|
||||
- Radial gradient uses a centered beacon with `beacon_size=1` and `slew_size=strip_length/2`
|
||||
|
||||
**Factory**: `animation.gradient_animation(engine)`
|
||||
|
||||
### GradientMeterAnimation
|
||||
|
||||
@ -737,6 +744,7 @@ Creates a pulse effect at a specific position with optional fade regions. Inheri
|
||||
|
||||
#### Visual Pattern
|
||||
|
||||
**right_edge=0 (default, left edge):**
|
||||
```
|
||||
pos (1)
|
||||
|
|
||||
@ -748,8 +756,20 @@ Creates a pulse effect at a specific position with optional fade regions. Inheri
|
||||
|2| 3 |2|
|
||||
```
|
||||
|
||||
**right_edge=1 (right edge):**
|
||||
```
|
||||
pos (1)
|
||||
|
|
||||
v
|
||||
_______ |
|
||||
/ \ |
|
||||
_______/ \______|__
|
||||
| | | |
|
||||
|2| 3 |2|
|
||||
```
|
||||
|
||||
Where:
|
||||
1. `pos` - Start of the pulse (in pixels)
|
||||
1. `pos` - Position of the beacon edge (left edge for right_edge=0, right edge for right_edge=1)
|
||||
2. `slew_size` - Number of pixels to fade from back to fore color (can be 0)
|
||||
3. `beacon_size` - Number of pixels of the pulse
|
||||
|
||||
@ -764,29 +784,52 @@ The pulse consists of:
|
||||
|-----------|------|---------|-------------|-------------|
|
||||
| `color` | int | 0xFFFFFFFF | - | Pulse color in ARGB format |
|
||||
| `back_color` | int | 0xFF000000 | - | Background color in ARGB format |
|
||||
| `pos` | int | 0 | - | Pulse start position in pixels |
|
||||
| `pos` | int | 0 | - | Beacon edge position (left edge for right_edge=0, right edge for right_edge=1) |
|
||||
| `beacon_size` | int | 1 | min: 0 | Size of core pulse in pixels |
|
||||
| `slew_size` | int | 0 | min: 0 | Fade region size on each side in pixels |
|
||||
| `right_edge` | int | 0 | enum: [0, 1] | 0=left edge (default), 1=right edge |
|
||||
| *(inherits all Animation parameters)* | | | | |
|
||||
|
||||
#### right_edge Behavior
|
||||
|
||||
- **right_edge=0 (default)**: `pos` specifies the left edge of the beacon. `pos=0` places the beacon starting at the leftmost pixel.
|
||||
- **right_edge=1**: `pos` specifies the right edge of the beacon from the right side of the strip. `pos=0` places the beacon's right edge at the rightmost pixel.
|
||||
|
||||
The effective left position is calculated as:
|
||||
- `right_edge=0`: `effective_pos = pos`
|
||||
- `right_edge=1`: `effective_pos = strip_length - pos - beacon_size`
|
||||
|
||||
#### Pattern Behavior
|
||||
|
||||
- **Sharp Pulse** (`slew_size = 0`): Rectangular pulse with hard edges
|
||||
- **Soft Pulse** (`slew_size > 0`): Pulse with smooth fade-in/fade-out regions
|
||||
- **Positioning**: `pos` defines the start of the core pulse region
|
||||
- **Positioning**: `pos` defines the beacon edge from the specified side
|
||||
- **Fade Calculation**: Linear fade from full brightness to background color
|
||||
- **Boundary Handling**: Fade regions are clipped to frame boundaries
|
||||
|
||||
#### Usage Examples
|
||||
|
||||
```berry
|
||||
# Sharp pulse at center
|
||||
animation sharp_pulse = beacon_animation(
|
||||
# Sharp pulse at left edge (right_edge=0, default)
|
||||
animation left_pulse = beacon_animation(
|
||||
color=red,
|
||||
pos=10,
|
||||
pos=0,
|
||||
beacon_size=3,
|
||||
slew_size=0
|
||||
slew_size=0,
|
||||
right_edge=0
|
||||
)
|
||||
# Shows 3 red pixels at positions 0, 1, 2
|
||||
|
||||
# Pulse from right edge
|
||||
animation right_pulse = beacon_animation(
|
||||
color=blue,
|
||||
pos=0,
|
||||
beacon_size=3,
|
||||
slew_size=0,
|
||||
right_edge=1
|
||||
)
|
||||
# With pos=0 and right_edge=1, shows 3 pixels at the right edge
|
||||
# (positions strip_length-3, strip_length-2, strip_length-1)
|
||||
|
||||
# Soft pulse with fade regions
|
||||
animation soft_pulse = beacon_animation(
|
||||
@ -847,6 +890,30 @@ animation breathing_spot = beacon_animation(
|
||||
breathing_spot.opacity = smooth(min_value=50, max_value=255, period=2s)
|
||||
```
|
||||
|
||||
**Bidirectional Animations:**
|
||||
```berry
|
||||
# Two beacons moving from opposite edges toward center
|
||||
set strip_len = strip_length()
|
||||
set sweep = triangle(min_value=0, max_value=strip_len/2, period=2s)
|
||||
|
||||
animation left_beacon = beacon_animation(
|
||||
color=red,
|
||||
beacon_size=2,
|
||||
right_edge=0
|
||||
)
|
||||
left_beacon.pos = sweep
|
||||
|
||||
animation right_beacon = beacon_animation(
|
||||
color=blue,
|
||||
beacon_size=2,
|
||||
right_edge=1
|
||||
)
|
||||
right_beacon.pos = sweep
|
||||
|
||||
run left_beacon
|
||||
run right_beacon
|
||||
```
|
||||
|
||||
**Factory**: `animation.beacon_animation(engine)`
|
||||
|
||||
### CrenelPositionAnimation
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
# This animation creates a beacon effect at a specific position on the LED strip.
|
||||
# It displays a color beacon with optional slew (fade) regions on both sides.
|
||||
#
|
||||
# Beacon diagram:
|
||||
# Beacon diagram (right_edge=0, default, left edge):
|
||||
# pos (1)
|
||||
# |
|
||||
# v
|
||||
@ -13,9 +13,20 @@
|
||||
# | | | |
|
||||
# |2| 3 |2|
|
||||
#
|
||||
# 1: `pos`, start of the beacon (in pixel)
|
||||
# Beacon diagram (right_edge=1, right edge):
|
||||
# pos (1)
|
||||
# |
|
||||
# v
|
||||
# _______
|
||||
# / \
|
||||
# _______/ \_________
|
||||
# | | | |
|
||||
# |2| 3 |2|
|
||||
#
|
||||
# 1: `pos`, position of the beacon edge (left edge for right_edge=0, right edge for right_edge=1)
|
||||
# 2: `slew_size`, number of pixels to fade from back to fore color, can be `0`
|
||||
# 3: `beacon_size`, number of pixels of the beacon
|
||||
# When right_edge=1, pos=0 shows 1 pixel at the right edge (rightmost pixel of strip)
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
@ -28,7 +39,8 @@ class BeaconAnimation : animation.animation
|
||||
"back_color": {"default": 0xFF000000},
|
||||
"pos": {"default": 0},
|
||||
"beacon_size": {"min": 0, "default": 1},
|
||||
"slew_size": {"min": 0, "default": 0}
|
||||
"slew_size": {"min": 0, "default": 0},
|
||||
"right_edge": {"enum": [0, 1], "default": 0}
|
||||
})
|
||||
|
||||
# Render the beacon to the provided frame buffer
|
||||
@ -44,15 +56,28 @@ class BeaconAnimation : animation.animation
|
||||
var slew_size = self.slew_size
|
||||
var beacon_size = self.beacon_size
|
||||
var color = self.color
|
||||
var right_edge = self.right_edge
|
||||
|
||||
# Fill background if not transparent
|
||||
if (back_color != 0xFF000000) && ((back_color & 0xFF000000) != 0x00)
|
||||
frame.fill_pixels(frame.pixels, back_color)
|
||||
end
|
||||
|
||||
# Calculate effective position based on right_edge
|
||||
# right_edge=0: pos is the left edge of the beacon (default)
|
||||
# right_edge=1: pos is the right edge of the beacon (from right side of strip)
|
||||
var effective_pos
|
||||
if right_edge == 1
|
||||
# Right edge mode: pos indicates right edge of beacon from right side of strip
|
||||
# effective_pos is the left edge of the beacon in absolute coordinates
|
||||
effective_pos = pos - beacon_size + 1
|
||||
else
|
||||
effective_pos = pos
|
||||
end
|
||||
|
||||
# Calculate beacon boundaries
|
||||
var beacon_min = pos
|
||||
var beacon_max = pos + beacon_size
|
||||
var beacon_min = effective_pos
|
||||
var beacon_max = effective_pos + beacon_size
|
||||
|
||||
# Clamp to frame boundaries
|
||||
if beacon_min < 0
|
||||
@ -65,17 +90,12 @@ class BeaconAnimation : animation.animation
|
||||
# Draw the main beacon
|
||||
frame.fill_pixels(frame.pixels, color, beacon_min, beacon_max)
|
||||
var i
|
||||
# var i = beacon_min
|
||||
# while i < beacon_max
|
||||
# frame.set_pixel_color(i, color)
|
||||
# i += 1
|
||||
# end
|
||||
|
||||
# Draw slew regions if slew_size > 0
|
||||
if slew_size > 0
|
||||
# Left slew (fade from background to beacon color)
|
||||
var left_slew_min = pos - slew_size
|
||||
var left_slew_max = pos
|
||||
var left_slew_min = effective_pos - slew_size
|
||||
var left_slew_max = effective_pos
|
||||
|
||||
if left_slew_min < 0
|
||||
left_slew_min = 0
|
||||
@ -87,15 +107,15 @@ class BeaconAnimation : animation.animation
|
||||
i = left_slew_min
|
||||
while i < left_slew_max
|
||||
# Calculate blend factor - blend from 255 (back) to 0 (fore) like original
|
||||
var blend_factor = tasmota.scale_int(i, pos - slew_size - 1, pos, 255, 0)
|
||||
var blend_factor = tasmota.scale_int(i, effective_pos - slew_size - 1, effective_pos, 255, 0)
|
||||
var blended_color = frame.blend_linear(back_color, color, blend_factor)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
end
|
||||
|
||||
# Right slew (fade from beacon color to background)
|
||||
var right_slew_min = pos + beacon_size
|
||||
var right_slew_max = pos + beacon_size + slew_size
|
||||
var right_slew_min = effective_pos + beacon_size
|
||||
var right_slew_max = effective_pos + beacon_size + slew_size
|
||||
|
||||
if right_slew_min < 0
|
||||
right_slew_min = 0
|
||||
@ -107,7 +127,7 @@ class BeaconAnimation : animation.animation
|
||||
i = right_slew_min
|
||||
while i < right_slew_max
|
||||
# Calculate blend factor - blend from 0 (fore) to 255 (back) like original
|
||||
var blend_factor = tasmota.scale_int(i, pos + beacon_size - 1, pos + beacon_size + slew_size, 0, 255)
|
||||
var blend_factor = tasmota.scale_int(i, effective_pos + beacon_size - 1, effective_pos + beacon_size + slew_size, 0, 255)
|
||||
var blended_color = frame.blend_linear(back_color, color, blend_factor)
|
||||
frame.set_pixel_color(i, blended_color)
|
||||
i += 1
|
||||
|
||||
@ -46,7 +46,7 @@ class BreatheAnimation : animation.animation
|
||||
if name == "color"
|
||||
# When color is set, update the breathe_provider's base_color
|
||||
# but keep the breathe_provider as the actual color source for rendering
|
||||
if type(value) == 'int' || animation.is_value_provider(value)
|
||||
if type(value) == 'int'
|
||||
self.breathe_provider.base_color = value
|
||||
# Restore the breathe_provider as the color source (bypass on_param_changed)
|
||||
self.values["color"] = self.breathe_provider
|
||||
|
||||
@ -1,244 +1,63 @@
|
||||
# Gradient animation effect for Berry Animation Framework
|
||||
#
|
||||
# This animation creates smooth color gradients that can be linear or radial,
|
||||
# with optional movement and color transitions over time.
|
||||
# Creates smooth color gradients between two colors.
|
||||
# Reimplemented as a subclass of BeaconAnimation for simplicity.
|
||||
#
|
||||
# Parameters:
|
||||
# - color1: First color (default: red 0xFFFF0000)
|
||||
# - color2: Second color (default: blue 0xFF0000FF)
|
||||
# - gradient_type: 0=linear (two-point), 1=radial (center-to-edges)
|
||||
# - direction: 0=forward (color1 to color2), 1=reverse (color2 to color1)
|
||||
#
|
||||
# Implementation:
|
||||
# - Linear gradient: Left slew of a large beacon positioned at the right edge
|
||||
# - Radial gradient: Centered beacon of size=1 with slew_size=strip_length/2
|
||||
|
||||
import "./core/param_encoder" as encode_constraints
|
||||
|
||||
#@ solidify:GradientAnimation,weak
|
||||
class GradientAnimation : animation.animation
|
||||
# Non-parameter instance variables only
|
||||
var current_colors # Array of current colors for each pixel
|
||||
var phase_offset # Current phase offset for movement
|
||||
|
||||
# Parameter definitions following parameterized class specification
|
||||
class GradientAnimation : animation.beacon_animation
|
||||
# Parameter definitions - gradient-specific parameters
|
||||
static var PARAMS = animation.enc_params({
|
||||
"color": {"default": nil, "nillable": true},
|
||||
"gradient_type": {"min": 0, "max": 1, "default": 0},
|
||||
"direction": {"min": 0, "max": 255, "default": 0},
|
||||
"center_pos": {"min": 0, "max": 255, "default": 128},
|
||||
"spread": {"min": 1, "max": 255, "default": 255},
|
||||
"movement_speed": {"min": 0, "max": 255, "default": 0}
|
||||
"color1": {"default": 0xFFFF0000}, # First color (default red)
|
||||
"color2": {"default": 0xFF0000FF}, # Second color (default blue)
|
||||
"direction": {"enum": [0, 1], "default": 0}, # 0=forward, 1=reverse
|
||||
"gradient_type": {"enum": [0, 1], "default": 0} # 0=linear, 1=radial
|
||||
})
|
||||
|
||||
# Initialize a new Gradient animation
|
||||
def init(engine)
|
||||
# Call parent constructor with engine only
|
||||
super(self).init(engine)
|
||||
|
||||
# Initialize non-parameter instance variables only
|
||||
self.current_colors = []
|
||||
self.phase_offset = 0
|
||||
|
||||
# Initialize with default strip length from engine
|
||||
var strip_length = self.engine.strip_length
|
||||
self.current_colors.resize(strip_length)
|
||||
|
||||
# Initialize colors to black
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
self.current_colors[i] = 0xFF000000
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Handle parameter changes
|
||||
def on_param_changed(name, value)
|
||||
super(self).on_param_changed(name, value)
|
||||
# TODO maybe be more specific on attribute name
|
||||
# Handle strip length changes from engine
|
||||
var current_strip_length = self.engine.strip_length
|
||||
if size(self.current_colors) != current_strip_length
|
||||
self.current_colors.resize(current_strip_length)
|
||||
var i = size(self.current_colors)
|
||||
while i < current_strip_length
|
||||
if i >= size(self.current_colors) || self.current_colors[i] == nil
|
||||
if i < size(self.current_colors)
|
||||
self.current_colors[i] = 0xFF000000
|
||||
end
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Update animation state
|
||||
def update(time_ms)
|
||||
super(self).update(time_ms)
|
||||
|
||||
# Cache parameter values for performance
|
||||
var movement_speed = self.movement_speed
|
||||
|
||||
# Update movement phase if movement is enabled
|
||||
if movement_speed > 0
|
||||
var elapsed = time_ms - self.start_time
|
||||
# Movement speed: 0-255 maps to 0-10 cycles per second
|
||||
var cycles_per_second = tasmota.scale_uint(movement_speed, 0, 255, 0, 10)
|
||||
if cycles_per_second > 0
|
||||
self.phase_offset = (elapsed * cycles_per_second / 1000) % 256
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate gradient colors
|
||||
self._calculate_gradient(time_ms)
|
||||
end
|
||||
|
||||
# Calculate gradient colors for all pixels
|
||||
def _calculate_gradient(time_ms)
|
||||
# Cache parameter values for performance
|
||||
var gradient_type = self.gradient_type
|
||||
var color_param = self.color
|
||||
var strip_length = self.engine.strip_length
|
||||
|
||||
# Ensure current_colors array matches strip length
|
||||
if size(self.current_colors) != strip_length
|
||||
self.current_colors.resize(strip_length)
|
||||
end
|
||||
|
||||
var i = 0
|
||||
while i < strip_length
|
||||
var gradient_pos = 0
|
||||
|
||||
if gradient_type == 0
|
||||
# Linear gradient
|
||||
gradient_pos = self._calculate_linear_position(i, strip_length)
|
||||
else
|
||||
# Radial gradient
|
||||
gradient_pos = self._calculate_radial_position(i, strip_length)
|
||||
end
|
||||
|
||||
# Apply movement offset
|
||||
gradient_pos = (gradient_pos + self.phase_offset) % 256
|
||||
|
||||
# Get color from provider
|
||||
var color = 0xFF000000
|
||||
|
||||
# Handle default rainbow gradient if color is nil
|
||||
if color_param == nil
|
||||
# Create default rainbow gradient on-the-fly
|
||||
var hue = tasmota.scale_uint(gradient_pos, 0, 255, 0, 359)
|
||||
import light_state
|
||||
var ls = light_state(3) # Create RGB light state
|
||||
ls.HsToRgb(hue, 255) # Convert HSV to RGB
|
||||
color = 0xFF000000 | (ls.r << 16) | (ls.g << 8) | ls.b
|
||||
elif animation.is_color_provider(color_param) && color_param.get_color_for_value != nil
|
||||
color = color_param.get_color_for_value(gradient_pos, 0)
|
||||
elif animation.is_value_provider(color_param)
|
||||
# Use resolve_value with position influence
|
||||
color = self.resolve_value(color_param, "color", time_ms + gradient_pos * 10)
|
||||
elif type(color_param) == "int"
|
||||
# Single color - create gradient from black to color
|
||||
var intensity = gradient_pos
|
||||
var r = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 16) & 0xFF)
|
||||
var g = tasmota.scale_uint(intensity, 0, 255, 0, (color_param >> 8) & 0xFF)
|
||||
var b = tasmota.scale_uint(intensity, 0, 255, 0, color_param & 0xFF)
|
||||
color = 0xFF000000 | (r << 16) | (g << 8) | b
|
||||
else
|
||||
color = color_param
|
||||
end
|
||||
|
||||
self.current_colors[i] = color
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate position for linear gradient
|
||||
def _calculate_linear_position(pixel, strip_length)
|
||||
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Cache parameter values
|
||||
var direction = self.direction
|
||||
var spread = self.spread
|
||||
|
||||
# Apply direction (0=left-to-right, 128=center-out, 255=right-to-left)
|
||||
if direction <= 128
|
||||
# Forward direction with varying start point
|
||||
var start_offset = tasmota.scale_uint(direction, 0, 128, 0, 128)
|
||||
strip_pos = (strip_pos + start_offset) % 256
|
||||
else
|
||||
# Reverse direction
|
||||
var reverse_amount = tasmota.scale_uint(direction, 128, 255, 0, 255)
|
||||
strip_pos = 255 - ((strip_pos + reverse_amount) % 256)
|
||||
end
|
||||
|
||||
# Apply spread (compress or expand the gradient)
|
||||
strip_pos = tasmota.scale_uint(strip_pos, 0, 255, 0, spread)
|
||||
|
||||
return strip_pos
|
||||
end
|
||||
|
||||
# Calculate position for radial gradient
|
||||
def _calculate_radial_position(pixel, strip_length)
|
||||
var strip_pos = tasmota.scale_uint(pixel, 0, strip_length - 1, 0, 255)
|
||||
|
||||
# Cache parameter values
|
||||
var center = self.center_pos
|
||||
var spread = self.spread
|
||||
|
||||
# Calculate distance from center
|
||||
var distance = 0
|
||||
if strip_pos >= center
|
||||
distance = strip_pos - center
|
||||
else
|
||||
distance = center - strip_pos
|
||||
end
|
||||
|
||||
# Scale distance by spread
|
||||
distance = tasmota.scale_uint(distance, 0, 128, 0, spread)
|
||||
if distance > 255
|
||||
distance = 255
|
||||
end
|
||||
|
||||
return distance
|
||||
end
|
||||
|
||||
# Render gradient to frame buffer
|
||||
# Override render to dynamically configure beacon based on strip_length and gradient parameters
|
||||
def render(frame, time_ms, strip_length)
|
||||
var i = 0
|
||||
while i < strip_length && i < frame.width
|
||||
if i < size(self.current_colors)
|
||||
frame.set_pixel_color(i, self.current_colors[i])
|
||||
end
|
||||
i += 1
|
||||
var col1 = self.color1
|
||||
var col2 = self.color2
|
||||
var direction = self.direction
|
||||
var gradient_type = self.gradient_type
|
||||
if direction
|
||||
self.color = col2
|
||||
self.back_color = col1
|
||||
else
|
||||
self.color = col1
|
||||
self.back_color = col2
|
||||
end
|
||||
|
||||
return true
|
||||
if gradient_type
|
||||
# Radial gradient: centered beacon, color at center, back_color at edges
|
||||
var center = (strip_length - 1) / 2
|
||||
self.pos = center
|
||||
self.beacon_size = 1 + (1 - strip_length & 1)
|
||||
self.slew_size = (center > 0) ? center - 1 : 0
|
||||
self.right_edge = 0
|
||||
else
|
||||
# Linear gradient: right slew of a large beacon at left edge
|
||||
self.pos = 0
|
||||
self.beacon_size = 1000
|
||||
self.slew_size = (strip_length > 1) ? strip_length - 2 : 0
|
||||
self.right_edge = 1
|
||||
end
|
||||
|
||||
return super(self).render(frame, time_ms, strip_length)
|
||||
end
|
||||
end
|
||||
|
||||
# Factory functions following parameterized class specification
|
||||
|
||||
# Create a rainbow linear gradient
|
||||
def gradient_rainbow_linear(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = nil # Default rainbow
|
||||
anim.gradient_type = 0 # Linear
|
||||
anim.direction = 0 # Left-to-right
|
||||
anim.movement_speed = 50 # Medium movement
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a rainbow radial gradient
|
||||
def gradient_rainbow_radial(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = nil # Default rainbow
|
||||
anim.gradient_type = 1 # Radial
|
||||
anim.center_pos = 128 # Center
|
||||
anim.movement_speed = 30 # Slow movement
|
||||
return anim
|
||||
end
|
||||
|
||||
# Create a two-color linear gradient
|
||||
def gradient_two_color_linear(engine)
|
||||
var anim = animation.gradient_animation(engine)
|
||||
anim.color = 0xFFFF0000 # Default red gradient
|
||||
anim.gradient_type = 0 # Linear
|
||||
anim.direction = 0 # Left-to-right
|
||||
anim.movement_speed = 0 # Static
|
||||
return anim
|
||||
end
|
||||
|
||||
return {'gradient_animation': GradientAnimation,
|
||||
'gradient_rainbow_linear': gradient_rainbow_linear,
|
||||
'gradient_rainbow_radial': gradient_rainbow_radial,
|
||||
'gradient_two_color_linear': gradient_two_color_linear}
|
||||
return {
|
||||
'gradient_animation': GradientAnimation
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ class RichPaletteAnimation : animation.animation
|
||||
# RichPaletteColorProvider parameters (forwarded to internal provider)
|
||||
"colors": {"type": "instance", "default": nil},
|
||||
"period": {"min": 0, "default": 5000},
|
||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.SINE},
|
||||
"transition_type": {"enum": [1 #-LINEAR-#, 5 #-SINE-#], "default": 5 #-SINE-#},
|
||||
"brightness": {"min": 0, "max": 255, "default": 255}
|
||||
})
|
||||
|
||||
|
||||
@ -444,7 +444,7 @@ class AnimationEngine
|
||||
# var cpu_percent = (self.tick_time_sum * 100) / period_ms
|
||||
|
||||
# Format and log stats - split into animation calc vs hardware output
|
||||
var stats_msg = f"AnimEngine: ticks={self.tick_count} total={mean_time:.2f}ms({self.tick_time_min}-{self.tick_time_max}) events={mean_phase1:.2f}ms({self.phase1_time_min}-{self.phase1_time_max}) update={mean_phase2:.2f}ms({self.phase2_time_min}-{self.phase2_time_max}) anim={mean_anim:.2f}ms({self.anim_time_min}-{self.anim_time_max}) hw={mean_hw:.2f}ms({self.hw_time_min}-{self.hw_time_max})"
|
||||
var stats_msg = f"ANI: ticks={self.tick_count} total={mean_time:.2f}ms({self.tick_time_min}-{self.tick_time_max}) events={mean_phase1:.2f}ms({self.phase1_time_min}-{self.phase1_time_max}) update={mean_phase2:.2f}ms({self.phase2_time_min}-{self.phase2_time_max}) anim={mean_anim:.2f}ms({self.anim_time_min}-{self.anim_time_max}) hw={mean_hw:.2f}ms({self.hw_time_min}-{self.hw_time_max})"
|
||||
tasmota.log(stats_msg, 3) # Log level 3 (DEBUG)
|
||||
end
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ class BreatheColorProvider : animation.oscillator_value
|
||||
super(self).init(engine)
|
||||
|
||||
# Configure the inherited oscillator for breathing behavior
|
||||
self.form = animation.COSINE # Use cosine wave for smooth breathing
|
||||
self.form = 4 #-animation.COSINE-# # Use cosine wave for smooth breathing
|
||||
self.min_value = 0 # Fixed range 0-255 for normalized oscillation
|
||||
self.max_value = 255 # Fixed range 0-255 for normalized oscillation
|
||||
self.duration = 3000 # Default duration
|
||||
@ -43,11 +43,7 @@ class BreatheColorProvider : animation.oscillator_value
|
||||
if name == "curve_factor"
|
||||
# For curve_factor = 1, use pure cosine
|
||||
# For curve_factor > 1, we'll apply the curve in produce_value
|
||||
if value == 1
|
||||
self.form = animation.COSINE
|
||||
else
|
||||
self.form = animation.COSINE # Still use cosine as base, apply curve later
|
||||
end
|
||||
self.form = 4 #-animation.COSINE-#
|
||||
end
|
||||
|
||||
# Call parent's parameter change handler
|
||||
|
||||
@ -28,9 +28,6 @@ class OscillatorValueProvider : animation.value_provider
|
||||
# Non-parameter instance variables only
|
||||
var value # current calculated value
|
||||
|
||||
# Static array for better solidification (moved from inline array)
|
||||
static var form_names = ["", "SAWTOOTH", "TRIANGLE", "SQUARE", "COSINE", "SINE", "EASE_IN", "EASE_OUT", "ELASTIC", "BOUNCE"]
|
||||
|
||||
# Parameter definitions for the oscillator
|
||||
static var PARAMS = animation.enc_params({
|
||||
"min_value": {"default": 0},
|
||||
@ -78,6 +75,8 @@ class OscillatorValueProvider : animation.value_provider
|
||||
var form = self.form
|
||||
var phase = self.phase
|
||||
var duty_cycle = self.duty_cycle
|
||||
var scale_uint = tasmota.scale_uint
|
||||
var scale_int = tasmota.scale_int
|
||||
|
||||
# Ensure time_ms is valid and initialize start_time if needed
|
||||
time_ms = self._fix_time_ms(time_ms)
|
||||
@ -86,114 +85,80 @@ class OscillatorValueProvider : animation.value_provider
|
||||
return min_value
|
||||
end
|
||||
|
||||
# Calculate elapsed time since start_time
|
||||
# Calculate elapsed time with cycle wrapping
|
||||
var past = time_ms - self.start_time
|
||||
if past < 0
|
||||
past = 0
|
||||
end
|
||||
|
||||
var duration_ms_mid = tasmota.scale_uint(duty_cycle, 0, 255, 0, duration)
|
||||
|
||||
# Handle cycle wrapping
|
||||
if past >= duration
|
||||
var cycles = past / duration
|
||||
self.start_time += cycles * duration
|
||||
self.start_time += (past / duration) * duration
|
||||
past = past % duration
|
||||
end
|
||||
|
||||
var past_with_phase = past
|
||||
|
||||
# Apply phase shift
|
||||
if phase > 0
|
||||
past_with_phase += tasmota.scale_uint(phase, 0, 255, 0, duration)
|
||||
if past_with_phase >= duration
|
||||
past_with_phase -= duration
|
||||
past += scale_uint(phase, 0, 255, 0, duration)
|
||||
if past >= duration
|
||||
past -= duration
|
||||
end
|
||||
end
|
||||
|
||||
# Calculate value based on waveform
|
||||
if form == animation.SAWTOOTH
|
||||
self.value = tasmota.scale_int(past_with_phase, 0, duration - 1, min_value, max_value)
|
||||
elif form == animation.TRIANGLE
|
||||
if past_with_phase < duration_ms_mid
|
||||
self.value = tasmota.scale_int(past_with_phase, 0, duration_ms_mid - 1, min_value, max_value)
|
||||
# Compute normalized value (0-255) based on waveform, then scale to min/max
|
||||
var v # normalized value 0-255
|
||||
var duty_mid = scale_uint(duty_cycle, 0, 255, 0, duration)
|
||||
|
||||
if form == 3 #-SQUARE-#
|
||||
self.value = past < duty_mid ? min_value : max_value
|
||||
return self.value
|
||||
elif form == 2 #-TRIANGLE-#
|
||||
if past < duty_mid
|
||||
v = scale_uint(past, 0, duty_mid - 1, 0, 255)
|
||||
else
|
||||
self.value = tasmota.scale_int(past_with_phase, duration_ms_mid, duration - 1, max_value, min_value)
|
||||
v = scale_uint(past, duty_mid, duration - 1, 255, 0)
|
||||
end
|
||||
elif form == animation.SQUARE
|
||||
if past_with_phase < duration_ms_mid
|
||||
self.value = min_value
|
||||
elif form == 4 || form == 5 #-COSINE/SINE-#
|
||||
var angle = scale_uint(past, 0, duration - 1, 0, 32767)
|
||||
if form == 4 angle -= 8192 end # cosine phase shift
|
||||
v = scale_int(tasmota.sine_int(angle), -4096, 4096, 0, 255)
|
||||
elif form == 6 || form == 7 #-EASE_IN/EASE_OUT-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
if form == 6 # ease_in: t^2
|
||||
v = scale_int(t * t, 0, 65025, 0, 255)
|
||||
else # ease_out: 1-(1-t)^2
|
||||
var inv = 255 - t
|
||||
v = 255 - scale_int(inv * inv, 0, 65025, 0, 255)
|
||||
end
|
||||
elif form == 8 #-ELASTIC-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
if t == 0 self.value = min_value return self.value end
|
||||
if t == 255 self.value = max_value return self.value end
|
||||
var decay = scale_uint(255 - t, 0, 255, 255, 32)
|
||||
var osc = tasmota.sine_int(scale_uint(t, 0, 255, 0, 196602) % 32767)
|
||||
var offset = scale_int(osc * decay, -1044480, 1044480, -255, 255)
|
||||
self.value = min_value + scale_int(t, 0, 255, 0, max_value - min_value) + offset
|
||||
# Clamp with 25% overshoot allowance
|
||||
var overshoot = (max_value - min_value) / 4
|
||||
if self.value > max_value + overshoot self.value = max_value + overshoot end
|
||||
if self.value < min_value - overshoot self.value = min_value - overshoot end
|
||||
return self.value
|
||||
elif form == 9 #-BOUNCE-#
|
||||
var t = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
if t < 128
|
||||
var s = scale_uint(t, 0, 127, 0, 255)
|
||||
v = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
|
||||
elif t < 192
|
||||
var s = scale_uint(t - 128, 0, 63, 0, 255)
|
||||
v = scale_int(255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255), 0, 255, 0, 128)
|
||||
else
|
||||
self.value = max_value
|
||||
var s = scale_uint(t - 192, 0, 63, 0, 255)
|
||||
var bv = 255 - scale_int((255-s)*(255-s), 0, 65025, 0, 255)
|
||||
v = 255 - scale_int(255 - bv, 0, 255, 0, 64)
|
||||
end
|
||||
elif form == animation.COSINE
|
||||
# Map timing to 0..32767 for sine calculation
|
||||
var angle = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 32767)
|
||||
var x = tasmota.sine_int(angle - 8192) # -4096 .. 4096, dephase from cosine to sine
|
||||
self.value = tasmota.scale_int(x, -4096, 4096, min_value, max_value)
|
||||
elif form == animation.SINE
|
||||
# Map timing to 0..32767 for sine calculation
|
||||
var angle = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 32767)
|
||||
var x = tasmota.sine_int(angle) # -4096 .. 4096, pure sine wave
|
||||
self.value = tasmota.scale_int(x, -4096, 4096, min_value, max_value)
|
||||
elif form == animation.EASE_IN
|
||||
# Quadratic ease-in: starts slow, accelerates
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
var eased = tasmota.scale_int(t * t, 0, 255 * 255, 0, 255) # t^2 scaled back to 0..255
|
||||
self.value = tasmota.scale_int(eased, 0, 255, min_value, max_value)
|
||||
elif form == animation.EASE_OUT
|
||||
# Quadratic ease-out: starts fast, decelerates
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
var inv_t = 255 - t
|
||||
var eased = 255 - tasmota.scale_int(inv_t * inv_t, 0, 255 * 255, 0, 255) # 1 - (1-t)^2 scaled to 0..255
|
||||
self.value = tasmota.scale_int(eased, 0, 255, min_value, max_value)
|
||||
elif form == animation.ELASTIC
|
||||
# Elastic easing: overshoots and oscillates like a spring
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
if t == 0
|
||||
self.value = min_value
|
||||
elif t == 255
|
||||
self.value = max_value
|
||||
else
|
||||
# Elastic formula: -2^(10*(t-1)) * sin((t-1-s)*2*pi/p) where s=p/4, p=0.3
|
||||
# Simplified for integer math: amplitude decreases exponentially, frequency is high
|
||||
var decay = tasmota.scale_uint(255 - t, 0, 255, 255, 32) # Exponential decay approximation
|
||||
var freq_angle = tasmota.scale_uint(t, 0, 255, 0, 32767 * 6) # High frequency oscillation
|
||||
var oscillation = tasmota.sine_int(freq_angle % 32767) # -4096 to 4096
|
||||
var elastic_offset = tasmota.scale_int(oscillation * decay, -4096 * 255, 4096 * 255, -255, 255) # Scale oscillation by decay
|
||||
var base_progress = tasmota.scale_int(t, 0, 255, 0, max_value - min_value)
|
||||
self.value = min_value + base_progress + elastic_offset
|
||||
# Clamp to reasonable bounds to prevent extreme overshoots
|
||||
var value_range = max_value - min_value
|
||||
var max_overshoot = tasmota.scale_int(value_range, 0, 4, 0, 1) # Allow 25% overshoot
|
||||
if self.value > max_value + max_overshoot self.value = max_value + max_overshoot end
|
||||
if self.value < min_value - max_overshoot self.value = min_value - max_overshoot end
|
||||
end
|
||||
elif form == animation.BOUNCE
|
||||
# Bounce easing: like a ball bouncing with decreasing amplitude
|
||||
var t = tasmota.scale_uint(past_with_phase, 0, duration - 1, 0, 255) # 0..255
|
||||
var bounced_t = 0
|
||||
|
||||
# Simplified bounce with 3 segments for better behavior
|
||||
if t < 128 # First big bounce (0-50% of time)
|
||||
var segment_t = tasmota.scale_uint(t, 0, 127, 0, 255)
|
||||
var inv_segment = 255 - segment_t
|
||||
bounced_t = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255) # Ease-out curve
|
||||
elif t < 192 # Second smaller bounce (50-75% of time)
|
||||
var segment_t = tasmota.scale_uint(t - 128, 0, 63, 0, 255)
|
||||
var inv_segment = 255 - segment_t
|
||||
var bounce_val = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255)
|
||||
bounced_t = tasmota.scale_int(bounce_val, 0, 255, 0, 128) # Scale to 50% height
|
||||
else # Final settle (75-100% of time)
|
||||
var segment_t = tasmota.scale_uint(t - 192, 0, 63, 0, 255)
|
||||
var inv_segment = 255 - segment_t
|
||||
var bounce_val = 255 - tasmota.scale_int(inv_segment * inv_segment, 0, 255 * 255, 0, 255)
|
||||
bounced_t = 255 - tasmota.scale_int(255 - bounce_val, 0, 255, 0, 64) # Settle towards full value
|
||||
end
|
||||
|
||||
self.value = tasmota.scale_int(bounced_t, 0, 255, min_value, max_value)
|
||||
else #-SAWTOOTH (default)-#
|
||||
v = scale_uint(past, 0, duration - 1, 0, 255)
|
||||
end
|
||||
|
||||
self.value = scale_int(v, 0, 255, min_value, max_value)
|
||||
return self.value
|
||||
end
|
||||
end
|
||||
@ -209,7 +174,7 @@ end
|
||||
# @return OscillatorValueProvider - New ramp instance
|
||||
def ramp(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.SAWTOOTH
|
||||
osc.form = 1 #-animation.SAWTOOTH-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -219,7 +184,7 @@ end
|
||||
# @return OscillatorValueProvider - New linear oscillator instance
|
||||
def linear(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.TRIANGLE
|
||||
osc.form = 2 #-animation.TRIANGLE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -229,7 +194,7 @@ end
|
||||
# @return OscillatorValueProvider - New smooth oscillator instance
|
||||
def smooth(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.COSINE
|
||||
osc.form = 4 #-animation.COSINE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -239,7 +204,7 @@ end
|
||||
# @return OscillatorValueProvider - New cosine oscillator instance
|
||||
def cosine_osc(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.COSINE
|
||||
osc.form = 4 #-animation.COSINE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -249,7 +214,7 @@ end
|
||||
# @return OscillatorValueProvider - New sine wave instance
|
||||
def sine_osc(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.SINE
|
||||
osc.form = 5 #-animation.SINE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -259,7 +224,7 @@ end
|
||||
# @return OscillatorValueProvider - New square wave instance
|
||||
def square(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.SQUARE
|
||||
osc.form = 3 #-animation.SQUARE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -269,7 +234,7 @@ end
|
||||
# @return OscillatorValueProvider - New ease-in instance
|
||||
def ease_in(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.EASE_IN
|
||||
osc.form = 6 #-animation.EASE_IN-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -279,7 +244,7 @@ end
|
||||
# @return OscillatorValueProvider - New ease-out instance
|
||||
def ease_out(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.EASE_OUT
|
||||
osc.form = 7 #-animation.EASE_OUT-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -289,7 +254,7 @@ end
|
||||
# @return OscillatorValueProvider - New elastic instance
|
||||
def elastic(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.ELASTIC
|
||||
osc.form = 8 #-animation.ELASTIC-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -299,7 +264,7 @@ end
|
||||
# @return OscillatorValueProvider - New bounce instance
|
||||
def bounce(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.BOUNCE
|
||||
osc.form = 9 #-animation.BOUNCE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -309,7 +274,7 @@ end
|
||||
# @return OscillatorValueProvider - New sawtooth instance
|
||||
def sawtooth(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.SAWTOOTH
|
||||
osc.form = 1 #-animation.SAWTOOTH-#
|
||||
return osc
|
||||
end
|
||||
|
||||
@ -319,7 +284,7 @@ end
|
||||
# @return OscillatorValueProvider - New triangle instance
|
||||
def triangle(engine)
|
||||
var osc = animation.oscillator_value(engine)
|
||||
osc.form = animation.TRIANGLE
|
||||
osc.form = 2 #-animation.TRIANGLE-#
|
||||
return osc
|
||||
end
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
static var PARAMS = animation.enc_params({
|
||||
"colors": {"type": "bytes", "default": nil}, # Palette bytes or predefined palette constant
|
||||
"period": {"min": 0, "default": 5000}, # 5 seconds default, 0 = value-based only
|
||||
"transition_type": {"enum": [animation.LINEAR, animation.SINE], "default": animation.LINEAR}
|
||||
"transition_type": {"enum": [1 #-animation.LINEAR-#, 5 #-animation.SINE-#], "default": 1 #-animation.LINEAR-#}
|
||||
# brightness parameter inherited from ColorProvider base class
|
||||
})
|
||||
|
||||
@ -212,7 +212,7 @@ class RichPaletteColorProvider : animation.color_provider
|
||||
def _interpolate(value, from_min, from_max, to_min, to_max)
|
||||
var transition_type = self.transition_type
|
||||
|
||||
if transition_type == animation.SINE
|
||||
if transition_type == 5 #-animation.SINE-#
|
||||
# Cosine interpolation for smooth transitions
|
||||
# Map value to 0..255 range first
|
||||
var t = tasmota.scale_uint(value, from_min, from_max, 0, 255)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -50,15 +50,6 @@ red_breathe.period = 3000
|
||||
red_breathe.curve_factor = 2
|
||||
print(f"Red breathe animation color: 0x{red_breathe.breathe_provider.base_color :08x}")
|
||||
|
||||
# Create green breathe animation with color as a closure value provider
|
||||
var green_breathe = animation.breathe_animation(engine)
|
||||
green_breathe.color = animation.create_closure_value(engine, def (engine) return 0xFF00FF00 end)
|
||||
green_breathe.min_brightness = 10
|
||||
green_breathe.max_brightness = 180
|
||||
green_breathe.period = 3000
|
||||
green_breathe.curve_factor = 2
|
||||
print(f"Green breathe animation color: {green_breathe.breathe_provider.base_color}")
|
||||
|
||||
# Test parameter updates using virtual member assignment
|
||||
blue_breathe.min_brightness = 30
|
||||
blue_breathe.max_brightness = 220
|
||||
@ -142,8 +133,6 @@ print("✓ Animation added to engine successfully")
|
||||
assert(anim != nil, "Default breathe animation should be created")
|
||||
assert(blue_breathe != nil, "Custom breathe animation should be created")
|
||||
assert(red_breathe != nil, "Red breathe animation should be created")
|
||||
assert(green_breathe != nil, "Green breathe animation should be created")
|
||||
assert(animation.is_value_provider(green_breathe.breathe_provider.base_color), "Green breathe should have color as a value provider")
|
||||
assert(blue_breathe.breathe_provider.base_color == 0xFF0000FF, "Blue breathe should have correct color")
|
||||
assert(blue_breathe.min_brightness == 30, "Min brightness should be updated to 30")
|
||||
assert(blue_breathe.max_brightness == 220, "Max brightness should be updated to 220")
|
||||
|
||||
@ -207,7 +207,7 @@ assert(blue_breathe.min_brightness == 30, "Min brightness should be updated to 3
|
||||
assert(blue_breathe.max_brightness == 220, "Max brightness should be updated to 220")
|
||||
assert(blue_breathe.duration == 3500, "Duration should be updated to 3500")
|
||||
assert(blue_breathe.curve_factor == 4, "Curve factor should be updated to 4")
|
||||
assert(blue_breathe.form == animation.COSINE, "Form should be COSINE")
|
||||
assert(blue_breathe.form == 4 #-COSINE-#, "Form should be COSINE")
|
||||
assert(blue_breathe.min_value == 0, "Inherited min_value should be 0")
|
||||
assert(blue_breathe.max_value == 255, "Inherited max_value should be 255")
|
||||
assert(blue_breathe.engine == engine, "Provider should have correct engine reference")
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Test suite for GradientAnimation
|
||||
#
|
||||
# This test verifies that the GradientAnimation works correctly
|
||||
# with different gradient types, colors, and movement patterns.
|
||||
# This test verifies that the simplified GradientAnimation works correctly
|
||||
# with linear and radial gradients using beacon-based rendering.
|
||||
|
||||
import animation
|
||||
|
||||
@ -13,26 +13,19 @@ def test_gradient_creation()
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test default gradient (rainbow linear)
|
||||
# Test default gradient
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
assert(gradient != nil, "Should create gradient animation")
|
||||
assert(gradient.gradient_type == 0, "Should default to linear gradient")
|
||||
assert(gradient.direction == 0, "Should default to left-to-right direction")
|
||||
|
||||
# Test single color gradient
|
||||
var red_gradient = animation.gradient_animation(engine)
|
||||
red_gradient.color = 0xFFFF0000
|
||||
assert(red_gradient != nil, "Should create red gradient")
|
||||
assert(gradient.direction == 0, "Should default to forward direction")
|
||||
assert(gradient.color1 == 0xFFFF0000, "Should default to red color1")
|
||||
assert(gradient.color2 == 0xFF0000FF, "Should default to blue color2")
|
||||
|
||||
# Test radial gradient
|
||||
var radial_gradient = animation.gradient_animation(engine)
|
||||
radial_gradient.gradient_type = 1
|
||||
radial_gradient.center_pos = 64
|
||||
radial_gradient.spread = 200
|
||||
radial_gradient.movement_speed = 100
|
||||
radial_gradient.priority = 10
|
||||
radial_gradient.duration = 5000
|
||||
radial_gradient.loop = false
|
||||
radial_gradient.color1 = 0xFF000000
|
||||
radial_gradient.color2 = 0xFFFFFFFF
|
||||
assert(radial_gradient != nil, "Should create radial gradient")
|
||||
assert(radial_gradient.gradient_type == 1, "Should be radial gradient")
|
||||
|
||||
@ -46,27 +39,23 @@ def test_gradient_parameters()
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFFFFFFFF
|
||||
|
||||
# Test parameter setting via virtual members
|
||||
gradient.gradient_type = 1
|
||||
assert(gradient.gradient_type == 1, "Should update gradient type")
|
||||
|
||||
gradient.direction = 128
|
||||
assert(gradient.direction == 128, "Should update direction")
|
||||
gradient.direction = 1
|
||||
assert(gradient.direction == 1, "Should update direction")
|
||||
|
||||
gradient.center_pos = 200
|
||||
assert(gradient.center_pos == 200, "Should update center position")
|
||||
gradient.color1 = 0xFF0000FF
|
||||
assert(gradient.color1 == 0xFF0000FF, "Should update color1")
|
||||
|
||||
gradient.spread = 128
|
||||
assert(gradient.spread == 128, "Should update spread")
|
||||
|
||||
gradient.movement_speed = 150
|
||||
assert(gradient.movement_speed == 150, "Should update movement speed")
|
||||
gradient.color2 = 0xFFFF0000
|
||||
assert(gradient.color2 == 0xFFFF0000, "Should update color2")
|
||||
|
||||
# Test parameter validation via set_param method
|
||||
assert(gradient.set_param("gradient_type", 5) == false, "Should reject invalid gradient type")
|
||||
assert(gradient.set_param("spread", 0) == false, "Should reject zero spread")
|
||||
assert(gradient.set_param("direction", 5) == false, "Should reject invalid direction")
|
||||
|
||||
print("✓ GradientAnimation parameters test passed")
|
||||
end
|
||||
@ -78,28 +67,19 @@ def test_gradient_updates()
|
||||
var strip = global.Leds(5)
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFF00FF00
|
||||
gradient.movement_speed = 100
|
||||
gradient.color1 = 0xFF000000
|
||||
gradient.color2 = 0xFF00FF00
|
||||
|
||||
# Start the animation
|
||||
# Note: When testing animations directly (not through engine_proxy), we must set start_time manually
|
||||
gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
gradient.start_time = 1000
|
||||
gradient.start(1000)
|
||||
assert(gradient.is_running == true, "Should be running after start")
|
||||
|
||||
# Test update at different times
|
||||
gradient.update(1000)
|
||||
assert(gradient.is_running == true, "Should be running after update at start time")
|
||||
assert(gradient.is_running == true, "Should be running after update")
|
||||
gradient.update(1500)
|
||||
assert(gradient.is_running == true, "Should be running after update at 500ms")
|
||||
gradient.update(2000)
|
||||
assert(gradient.is_running == true, "Should be running after update at 1000ms")
|
||||
|
||||
# Test that movement_speed affects phase_offset
|
||||
var initial_offset = gradient.phase_offset
|
||||
gradient.update(3000) # 2 seconds later
|
||||
# With movement_speed=100, should have moved
|
||||
# (movement is time-based, so offset should change)
|
||||
|
||||
print("✓ GradientAnimation updates test passed")
|
||||
end
|
||||
@ -111,14 +91,14 @@ def test_gradient_rendering()
|
||||
var strip = global.Leds(5)
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color = 0xFFFF0000
|
||||
gradient.movement_speed = 0
|
||||
gradient.color1 = 0xFF000000 # Black
|
||||
gradient.color2 = 0xFFFF0000 # Red
|
||||
|
||||
# Create a frame buffer
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
var frame = animation.frame_buffer(5)
|
||||
|
||||
# Start and update the animation
|
||||
gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
gradient.start_time = 1000
|
||||
gradient.start(1000)
|
||||
gradient.update(1000)
|
||||
|
||||
@ -127,181 +107,88 @@ def test_gradient_rendering()
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
# Test that colors were set (basic check)
|
||||
# For a red gradient, pixels should have some red component
|
||||
var first_color = frame.get_pixel_color(0)
|
||||
var last_color = frame.get_pixel_color(4) # Last pixel in 5-pixel strip
|
||||
# Colors should be different in a gradient
|
||||
var last_color = frame.get_pixel_color(4)
|
||||
# Colors should be different in a gradient (black to red)
|
||||
assert(first_color != last_color, "First and last pixels should be different in gradient")
|
||||
|
||||
print("✓ GradientAnimation rendering test passed")
|
||||
end
|
||||
|
||||
# Test gradient factory methods
|
||||
def test_gradient_factory_methods()
|
||||
print("Testing GradientAnimation factory methods...")
|
||||
|
||||
var strip = global.Leds(20)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test rainbow linear factory
|
||||
var rainbow_linear = animation.gradient_rainbow_linear(engine)
|
||||
assert(rainbow_linear != nil, "Should create rainbow linear gradient")
|
||||
assert(rainbow_linear.gradient_type == 0, "Should be linear")
|
||||
assert(rainbow_linear.movement_speed == 50, "Should set movement speed")
|
||||
|
||||
# Test rainbow radial factory
|
||||
var rainbow_radial = animation.gradient_rainbow_radial(engine)
|
||||
assert(rainbow_radial != nil, "Should create rainbow radial gradient")
|
||||
assert(rainbow_radial.gradient_type == 1, "Should be radial")
|
||||
assert(rainbow_radial.center_pos == 128, "Should set center position")
|
||||
assert(rainbow_radial.movement_speed == 30, "Should set movement speed")
|
||||
|
||||
# Test two-color linear factory
|
||||
var two_color = animation.gradient_two_color_linear(engine)
|
||||
assert(two_color != nil, "Should create two-color gradient")
|
||||
assert(two_color.gradient_type == 0, "Should be linear")
|
||||
assert(two_color.movement_speed == 0, "Should set movement speed")
|
||||
|
||||
print("✓ GradientAnimation factory methods test passed")
|
||||
end
|
||||
|
||||
# Test gradient position calculations
|
||||
def test_gradient_position_calculations()
|
||||
print("Testing GradientAnimation position calculations...")
|
||||
# Test linear gradient direction
|
||||
def test_gradient_direction()
|
||||
print("Testing GradientAnimation direction...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test linear gradient with different directions
|
||||
var linear_gradient = animation.gradient_animation(engine)
|
||||
linear_gradient.color = 0xFFFFFFFF
|
||||
linear_gradient.movement_speed = 0
|
||||
linear_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
linear_gradient.start(1000)
|
||||
linear_gradient.update(1000)
|
||||
# Test forward direction (color1 -> color2)
|
||||
var forward_gradient = animation.gradient_animation(engine)
|
||||
forward_gradient.color1 = 0xFF000000 # Black
|
||||
forward_gradient.color2 = 0xFFFF0000 # Red
|
||||
forward_gradient.direction = 0
|
||||
forward_gradient.start_time = 1000
|
||||
forward_gradient.start(1000)
|
||||
forward_gradient.update(1000)
|
||||
|
||||
# The _calculate_linear_position method is private, but we can test the overall effect
|
||||
# by checking that different pixels get different colors in a linear gradient
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
linear_gradient.render(frame, 1000, engine.strip_length)
|
||||
var frame1 = animation.frame_buffer(10)
|
||||
forward_gradient.render(frame1, 1000, engine.strip_length)
|
||||
var forward_first = frame1.get_pixel_color(0)
|
||||
var forward_last = frame1.get_pixel_color(9)
|
||||
|
||||
var first_color = frame.get_pixel_color(0)
|
||||
var last_color = frame.get_pixel_color(9)
|
||||
# In a gradient, first and last pixels should typically have different colors
|
||||
# (unless it's a very specific case)
|
||||
# Test reverse direction (color2 -> color1)
|
||||
var reverse_gradient = animation.gradient_animation(engine)
|
||||
reverse_gradient.color1 = 0xFF000000 # Black
|
||||
reverse_gradient.color2 = 0xFFFF0000 # Red
|
||||
reverse_gradient.direction = 1
|
||||
reverse_gradient.start_time = 1000
|
||||
reverse_gradient.start(1000)
|
||||
reverse_gradient.update(1000)
|
||||
|
||||
var frame2 = animation.frame_buffer(10)
|
||||
reverse_gradient.render(frame2, 1000, engine.strip_length)
|
||||
var reverse_first = frame2.get_pixel_color(0)
|
||||
var reverse_last = frame2.get_pixel_color(9)
|
||||
|
||||
# Forward: first should be darker (black), last should be brighter (red)
|
||||
# Reverse: first should be brighter (red), last should be darker (black)
|
||||
print(f" Forward: first=0x{forward_first:08X}, last=0x{forward_last:08X}")
|
||||
print(f" Reverse: first=0x{reverse_first:08X}, last=0x{reverse_last:08X}")
|
||||
|
||||
print("✓ GradientAnimation direction test passed")
|
||||
end
|
||||
|
||||
# Test radial gradient
|
||||
def test_radial_gradient()
|
||||
print("Testing GradientAnimation radial mode...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
var radial_gradient = animation.gradient_animation(engine)
|
||||
radial_gradient.color = 0xFFFFFFFF
|
||||
radial_gradient.gradient_type = 1
|
||||
radial_gradient.movement_speed = 0
|
||||
radial_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
radial_gradient.gradient_type = 1 # Radial
|
||||
radial_gradient.color1 = 0xFF000000 # Black at center
|
||||
radial_gradient.color2 = 0xFFFFFFFF # White at edges
|
||||
radial_gradient.start_time = 1000
|
||||
radial_gradient.start(1000)
|
||||
radial_gradient.update(1000)
|
||||
|
||||
var frame = animation.frame_buffer(10)
|
||||
radial_gradient.render(frame, 1000, engine.strip_length)
|
||||
|
||||
# In a radial gradient, center pixel should be different from edge pixels
|
||||
var center_color = frame.get_pixel_color(5) # Middle pixel
|
||||
var edge_color = frame.get_pixel_color(0) # Edge pixel
|
||||
# In radial gradient with direction=0, color1 at center, color2 at edges
|
||||
var edge_color = frame.get_pixel_color(0)
|
||||
var center_color = frame.get_pixel_color(5)
|
||||
|
||||
print("✓ GradientAnimation position calculations test passed")
|
||||
end
|
||||
print(f" Edge (0): 0x{edge_color:08X}")
|
||||
print(f" Center (5): 0x{center_color:08X}")
|
||||
|
||||
# Test refactored color system
|
||||
def test_gradient_color_refactoring()
|
||||
print("Testing GradientAnimation color refactoring...")
|
||||
# Edge should be brighter than center (white vs black)
|
||||
var edge_brightness = ((edge_color >> 16) & 0xFF) + ((edge_color >> 8) & 0xFF) + (edge_color & 0xFF)
|
||||
var center_brightness = ((center_color >> 16) & 0xFF) + ((center_color >> 8) & 0xFF) + (center_color & 0xFF)
|
||||
assert(edge_brightness > center_brightness, "Edge should be brighter than center in radial gradient")
|
||||
|
||||
var strip = global.Leds(5)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test with static color
|
||||
var static_gradient = animation.gradient_animation(engine)
|
||||
static_gradient.color = 0xFFFF0000
|
||||
assert(static_gradient.color == 0xFFFF0000, "Should have color set")
|
||||
|
||||
# Test with nil color (default rainbow)
|
||||
var rainbow_gradient = animation.gradient_animation(engine)
|
||||
rainbow_gradient.color = nil
|
||||
assert(rainbow_gradient.color == nil, "Should accept nil color for rainbow")
|
||||
|
||||
# Test color resolution
|
||||
var resolved_color = static_gradient.resolve_value(static_gradient.color, "color", 1000)
|
||||
assert(resolved_color != nil, "Should resolve color")
|
||||
|
||||
# Test basic rendering with different color types
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
static_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
static_gradient.start(1000)
|
||||
static_gradient.update(1000)
|
||||
var result = static_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render with static color")
|
||||
|
||||
rainbow_gradient.start_time = 1000 # Set start_time manually for direct testing
|
||||
rainbow_gradient.start(1000)
|
||||
rainbow_gradient.update(1000)
|
||||
result = rainbow_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render with rainbow color")
|
||||
|
||||
print("✓ GradientAnimation color refactoring test passed")
|
||||
end
|
||||
|
||||
# Test virtual parameter access
|
||||
def test_gradient_virtual_parameters()
|
||||
print("Testing GradientAnimation virtual parameters...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
|
||||
# Test virtual parameter assignment and access
|
||||
gradient.color = 0xFFFF00FF
|
||||
assert(gradient.color == 0xFFFF00FF, "Should update color via virtual member")
|
||||
|
||||
gradient.gradient_type = 1
|
||||
assert(gradient.gradient_type == 1, "Should update gradient type via virtual member")
|
||||
|
||||
gradient.direction = 200
|
||||
assert(gradient.direction == 200, "Should update direction via virtual member")
|
||||
|
||||
gradient.center_pos = 64
|
||||
assert(gradient.center_pos == 64, "Should update center position via virtual member")
|
||||
|
||||
gradient.spread = 128
|
||||
assert(gradient.spread == 128, "Should update spread via virtual member")
|
||||
|
||||
gradient.movement_speed = 75
|
||||
assert(gradient.movement_speed == 75, "Should update movement speed via virtual member")
|
||||
|
||||
print("✓ GradientAnimation virtual parameters test passed")
|
||||
end
|
||||
|
||||
# Test updated tostring method
|
||||
def test_gradient_tostring()
|
||||
print("Testing GradientAnimation tostring...")
|
||||
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test with static color
|
||||
var static_gradient = animation.gradient_animation(engine)
|
||||
static_gradient.color = 0xFFFF0000
|
||||
static_gradient.movement_speed = 50
|
||||
var str_static = str(static_gradient)
|
||||
assert(str_static != nil, "Should have string representation")
|
||||
assert(type(str_static) == "string", "Should be a string")
|
||||
|
||||
# Test with color provider
|
||||
var color_provider = animation.static_color(engine)
|
||||
color_provider.color = 0xFF00FF00
|
||||
var provider_gradient = animation.gradient_animation(engine)
|
||||
provider_gradient.color = color_provider
|
||||
provider_gradient.gradient_type = 1
|
||||
provider_gradient.movement_speed = 25
|
||||
var str_provider = str(provider_gradient)
|
||||
assert(str_provider != nil, "Should have string representation")
|
||||
assert(type(str_provider) == "string", "Should be a string")
|
||||
|
||||
print("✓ GradientAnimation tostring test passed")
|
||||
print("✓ GradientAnimation radial mode test passed")
|
||||
end
|
||||
|
||||
# Run all tests
|
||||
@ -313,11 +200,8 @@ def run_gradient_animation_tests()
|
||||
test_gradient_parameters()
|
||||
test_gradient_updates()
|
||||
test_gradient_rendering()
|
||||
test_gradient_factory_methods()
|
||||
test_gradient_position_calculations()
|
||||
test_gradient_color_refactoring()
|
||||
test_gradient_virtual_parameters()
|
||||
test_gradient_tostring()
|
||||
test_gradient_direction()
|
||||
test_radial_gradient()
|
||||
|
||||
print("=== All GradientAnimation tests passed! ===")
|
||||
return true
|
||||
|
||||
@ -1,27 +1,29 @@
|
||||
# Test for gradient rainbow functionality with light_state HSV conversion
|
||||
# Test for gradient color variation
|
||||
import animation
|
||||
|
||||
print("Testing gradient rainbow with light_state HSV conversion...")
|
||||
print("Testing gradient color variation...")
|
||||
|
||||
# Create LED strip and engine
|
||||
var strip = global.Leds(10)
|
||||
var engine = animation.create_engine(strip)
|
||||
|
||||
# Test rainbow gradient (nil color)
|
||||
var rainbow_gradient = animation.gradient_animation(engine)
|
||||
rainbow_gradient.color = nil # Should use rainbow
|
||||
rainbow_gradient.movement_speed = 0 # Static for testing
|
||||
# Test linear gradient with two colors
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
gradient.color1 = 0xFF0000FF # Blue
|
||||
gradient.color2 = 0xFFFF0000 # Red
|
||||
gradient.gradient_type = 0 # Linear
|
||||
gradient.direction = 0 # Forward (blue to red)
|
||||
|
||||
# Start and update
|
||||
rainbow_gradient.start(1000)
|
||||
rainbow_gradient.update(1000)
|
||||
gradient.start(1000)
|
||||
gradient.update(1000)
|
||||
|
||||
# Create frame and render
|
||||
var frame = animation.frame_buffer(10, 1)
|
||||
var result = rainbow_gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render rainbow gradient successfully")
|
||||
var frame = animation.frame_buffer(10)
|
||||
var result = gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render gradient successfully")
|
||||
|
||||
# Check that different pixels have different colors (rainbow effect)
|
||||
# Check that different pixels have different colors (gradient effect)
|
||||
var colors = []
|
||||
var i = 0
|
||||
while i < 10
|
||||
@ -31,17 +33,10 @@ end
|
||||
|
||||
# Verify that we have some color variation (not all the same)
|
||||
var first_color = colors[0]
|
||||
var has_variation = false
|
||||
i = 1
|
||||
while i < size(colors)
|
||||
if colors[i] != first_color
|
||||
has_variation = true
|
||||
break
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
var last_color = colors[9]
|
||||
var has_variation = first_color != last_color
|
||||
|
||||
assert(has_variation, "Rainbow gradient should have color variation across pixels")
|
||||
assert(has_variation, "Gradient should have color variation across pixels")
|
||||
|
||||
# Test that colors have proper alpha channel (should be 0xFF)
|
||||
i = 0
|
||||
@ -51,6 +46,10 @@ while i < size(colors)
|
||||
i += 1
|
||||
end
|
||||
|
||||
print("✓ Gradient rainbow with light_state HSV conversion test passed!")
|
||||
# Print colors for debugging
|
||||
print(f" First pixel: 0x{first_color:08X}")
|
||||
print(f" Last pixel: 0x{last_color:08X}")
|
||||
|
||||
print("✓ Gradient color variation test passed!")
|
||||
|
||||
return true
|
||||
@ -12,14 +12,16 @@ var gradient = animation.gradient_animation(engine)
|
||||
assert(gradient != nil, "Should create gradient animation")
|
||||
|
||||
# Test parameter setting
|
||||
gradient.color = 0xFFFF0000
|
||||
gradient.color1 = 0xFF000000
|
||||
gradient.color2 = 0xFFFF0000
|
||||
gradient.gradient_type = 0
|
||||
gradient.movement_speed = 50
|
||||
gradient.direction = 0
|
||||
|
||||
# Test parameter access
|
||||
assert(gradient.color == 0xFFFF0000, "Should set color")
|
||||
assert(gradient.color1 == 0xFF000000, "Should set color1")
|
||||
assert(gradient.color2 == 0xFFFF0000, "Should set color2")
|
||||
assert(gradient.gradient_type == 0, "Should set gradient type")
|
||||
assert(gradient.movement_speed == 50, "Should set movement speed")
|
||||
assert(gradient.direction == 0, "Should set direction")
|
||||
|
||||
# Test start and update
|
||||
gradient.start(1000)
|
||||
@ -29,8 +31,8 @@ gradient.update(1000)
|
||||
assert(gradient.is_running == true, "Should still be running after update")
|
||||
|
||||
# Test rendering
|
||||
var frame = animation.frame_buffer(5, 1)
|
||||
result = gradient.render(frame, 1000, engine.strip_length)
|
||||
var frame = animation.frame_buffer(5)
|
||||
var result = gradient.render(frame, 1000, engine.strip_length)
|
||||
assert(result == true, "Should render successfully")
|
||||
|
||||
print("✓ Basic GradientAnimation test passed!")
|
||||
|
||||
@ -45,17 +45,16 @@ success = test_obj.set_param("non_nillable_param", 100)
|
||||
assert(success == true, "Should accept valid value for non-nillable parameter")
|
||||
assert(test_obj.non_nillable_param == 100, "Should store valid value for non-nillable parameter")
|
||||
|
||||
# Test gradient animation nillable color parameter
|
||||
# Test gradient animation color parameter
|
||||
var gradient = animation.gradient_animation(engine)
|
||||
|
||||
# Test setting nil on gradient color (should work because it's nillable)
|
||||
gradient.color = nil
|
||||
assert(gradient.color == nil, "Should accept nil for nillable gradient color")
|
||||
|
||||
# Test setting a valid color (should work)
|
||||
gradient.color = 0xFFFF0000
|
||||
assert(gradient.color == 0xFFFF0000, "Should accept valid color for gradient")
|
||||
|
||||
# Note: The 'color' parameter behavior for nil depends on the base Animation class definition
|
||||
# This test focuses on the custom nillable parameter attribute, not gradient-specific behavior
|
||||
|
||||
print("✓ Nillable parameter attribute test passed!")
|
||||
|
||||
return true
|
||||
@ -98,7 +98,7 @@ def test_ease_constructors()
|
||||
assert(ease_in_provider.min_value == 10, "ease_in should set correct start value")
|
||||
assert(ease_in_provider.max_value == 90, "ease_in should set correct end value")
|
||||
assert(ease_in_provider.duration == 2000, "ease_in should set correct duration")
|
||||
assert(ease_in_provider.form == animation.EASE_IN, "ease_in should set EASE_IN form")
|
||||
assert(ease_in_provider.form == 6 #-EASE_IN-#, "ease_in should set EASE_IN form")
|
||||
|
||||
# Test ease_out constructor
|
||||
var ease_out_provider = animation.ease_out(engine)
|
||||
@ -108,7 +108,7 @@ def test_ease_constructors()
|
||||
assert(ease_out_provider.min_value == 20, "ease_out should set correct start value")
|
||||
assert(ease_out_provider.max_value == 80, "ease_out should set correct end value")
|
||||
assert(ease_out_provider.duration == 1500, "ease_out should set correct duration")
|
||||
assert(ease_out_provider.form == animation.EASE_OUT, "ease_out should set EASE_OUT form")
|
||||
assert(ease_out_provider.form == 7 #-EASE_OUT-#, "ease_out should set EASE_OUT form")
|
||||
|
||||
print("✓ Ease constructor functions test passed")
|
||||
end
|
||||
@ -183,8 +183,8 @@ def test_ease_tostring()
|
||||
ease_out_provider.duration = 2500
|
||||
|
||||
# Verify form values are set correctly
|
||||
assert(ease_in_provider.form == animation.EASE_IN, "EASE_IN form should be set")
|
||||
assert(ease_out_provider.form == animation.EASE_OUT, "EASE_OUT form should be set")
|
||||
assert(ease_in_provider.form == 6 #-EASE_IN-#, "EASE_IN form should be set")
|
||||
assert(ease_out_provider.form == 7 #-EASE_OUT-#, "EASE_OUT form should be set")
|
||||
|
||||
print("✓ Ease tostring test passed")
|
||||
end
|
||||
@ -198,16 +198,16 @@ def test_ease_constants()
|
||||
direct_ease_in.min_value = 0
|
||||
direct_ease_in.max_value = 100
|
||||
direct_ease_in.duration = 1000
|
||||
direct_ease_in.form = animation.EASE_IN
|
||||
direct_ease_in.form = 6 #-EASE_IN-#
|
||||
|
||||
var direct_ease_out = animation.oscillator_value(engine)
|
||||
direct_ease_out.min_value = 0
|
||||
direct_ease_out.max_value = 100
|
||||
direct_ease_out.duration = 1000
|
||||
direct_ease_out.form = animation.EASE_OUT
|
||||
direct_ease_out.form = 7 #-EASE_OUT-#
|
||||
|
||||
assert(direct_ease_in.form == animation.EASE_IN, "Direct EASE_IN should work")
|
||||
assert(direct_ease_out.form == animation.EASE_OUT, "Direct EASE_OUT should work")
|
||||
assert(direct_ease_in.form == 6 #-EASE_IN-#, "Direct EASE_IN should work")
|
||||
assert(direct_ease_out.form == 7 #-EASE_OUT-#, "Direct EASE_OUT should work")
|
||||
|
||||
print("✓ Ease constants test passed")
|
||||
end
|
||||
|
||||
@ -163,7 +163,7 @@ rebuild_provider.get_color_for_value(128, 0)
|
||||
log(f"After lookup with new palette: lut_dirty = {rebuild_provider._lut_dirty}")
|
||||
|
||||
# Change transition_type - SHOULD trigger rebuild
|
||||
rebuild_provider.transition_type = animation.SINE
|
||||
rebuild_provider.transition_type = 5 #-SINE-#
|
||||
log(f"After transition_type change: lut_dirty = {rebuild_provider._lut_dirty}")
|
||||
rebuild_provider.get_color_for_value(128, 0)
|
||||
log(f"After lookup with new transition: lut_dirty = {rebuild_provider._lut_dirty}")
|
||||
|
||||
@ -82,7 +82,7 @@ provider.brightness = 200
|
||||
log(f"After brightness change: _lut_dirty = {provider._lut_dirty}")
|
||||
|
||||
provider._lut_dirty = false
|
||||
provider.transition_type = animation.SINE
|
||||
provider.transition_type = 5 #-SINE-#
|
||||
log(f"After transition_type change: _lut_dirty = {provider._lut_dirty}")
|
||||
|
||||
provider._lut_dirty = false
|
||||
|
||||
Loading…
Reference in New Issue
Block a user