Tasmota/lib/libesp32/berry_animation/src/animations/crenel_position.be

131 lines
4.1 KiB
Plaintext

# Crenel Position animation effect for Berry Animation Framework
#
# This animation creates a crenel (square wave) effect at a specific position on the LED strip.
# It displays repeating rectangular pulses with configurable spacing and count.
#
# Crenel diagram:
# pos (1)
# |
# v (*4)
# ______ ____
# | | |
# _________| |_________|
#
# | 2 | 3 |
#
# 1: `pos`, start of the pulse (in pixel)
# 2: `pulse_size`, number of pixels of the pulse
# 3: `low_size`, number of pixel until next pos - full cycle is 2 + 3
# 4: `nb_pulse`, number of pulses, or `-1` for infinite
#@ solidify:CrenelPositionAnimation,weak
class CrenelPositionAnimation : animation.animation
# NO instance variables for parameters - they are handled by the virtual parameter system
# Parameter definitions with constraints
static var PARAMS = {
# 'color' for the comet head (32-bit ARGB value), inherited from animation class
"back_color": {"default": 0xFF000000}, # background color, TODO change to transparent
"pos": {"default": 0}, # start of the pulse (in pixel)
"pulse_size": {"min": 0, "default": 1}, # number of pixels of the pulse
"low_size": {"min": 0, "default": 3}, # number of pixel until next pos - full cycle is 2 + 3
"nb_pulse": {"default": -1} # number of pulses, or `-1` for infinite
}
# Render the crenel pattern to the provided frame buffer
#
# @param frame: FrameBuffer - The frame buffer to render to
# @param time_ms: int - Optional current time in milliseconds (defaults to self.engine.time_ms)
# @return bool - True if frame was modified, false otherwise
def render(frame, time_ms)
if !self.is_running || frame == nil
return false
end
# Auto-fix time_ms and start_time
time_ms = self._fix_time_ms(time_ms)
var pixel_size = frame.width
# Access parameters via virtual members (automatically resolves ValueProviders)
var back_color = self.back_color
var pos = self.pos
var pulse_size = self.pulse_size
var low_size = self.low_size
var nb_pulse = self.nb_pulse
var color = self.color
var period = int(pulse_size + low_size)
# Fill background if not transparent
if back_color != 0xFF000000
frame.fill_pixels(back_color)
end
# Ensure we have a meaningful period
if period <= 0
period = 1
end
# Nothing to paint if nb_pulse is 0
if nb_pulse == 0
return true
end
# For infinite pulses, optimize starting position
if nb_pulse < 0
# Find the position of the first visible falling range (pos + pulse_size - 1)
pos = ((pos + pulse_size - 1) % period) - pulse_size + 1
else
# For finite pulses, skip periods that are completely before the visible area
while (pos < -period) && (nb_pulse != 0)
pos += period
nb_pulse -= 1
end
end
# Render pulses
while (pos < pixel_size) && (nb_pulse != 0)
var i = 0
if pos < 0
i = -pos
end
# Invariant: pos + i >= 0
# Draw the pulse pixels
while (i < pulse_size) && (pos + i < pixel_size)
frame.set_pixel_color(pos + i, color)
i += 1
end
# Move to next pulse position
pos += period
nb_pulse -= 1
end
return true
end
# NO setter/getter methods - use direct assignment instead:
# obj.color = value
# obj.back_color = value
# obj.pos = value
# obj.pulse_size = value
# obj.low_size = value
# obj.nb_pulse = value
# String representation of the animation
def tostring()
var color_str
var raw_color = self.get_param("color")
if animation.is_value_provider(raw_color)
color_str = str(raw_color)
else
color_str = f"0x{self.color :08x}"
end
return f"CrenelPositionAnimation(color={color_str}, pos={self.pos}, pulse_size={self.pulse_size}, low_size={self.low_size}, nb_pulse={self.nb_pulse}, priority={self.priority}, running={self.is_running})"
end
end
return {'crenel_position_animation': CrenelPositionAnimation}