2323"""
2424import gc
2525import time
26+ from math import pi
2627import bitmaptools
2728from displayio import TileGrid , Bitmap , Palette
2829import adafruit_imageload
@@ -43,16 +44,18 @@ class IconAnimated(IconWidget):
4344
4445 :param str label_text: the text that will be shown beneath the icon image.
4546 :param str icon: the filepath of the bmp image to be used as the icon.
46- :param bool on_disk: if True use OnDiskBitmap instead of imageload.
47- This can be helpful to save memory. Defaults to False
48-
49- :param float max_scale: the maximum zoom during animation, set 1.0 for no animation
50- a value of 1.4 is a good starting point (default: 1.0, no animation),
51- ``max_scale`` must be between 1.0 and 1.5.
52- :param float max_angle: the maximum degrees of rotation during animation, positive values
53- are clockwise, set 0 for no rotation, in degrees (default: 0 degrees)
47+ :param bool on_disk: if True use OnDiskBitmap instead of imageload to load static
48+ icon image. This can be helpful to save memory. (default: False) Note: Bitmap
49+ file must use indexed colors to allow animations in the IconAnimated widget.
50+
51+ :param float scale: the maximum zoom during animation, set 1.0 for no zoom.
52+ A value of 1.5 is a good starting point. The ``scale`` can be less than
53+ 1.0 for shrink animations. (default: same as ``max_scale`` set in ``init_class``),
54+
55+ :param float angle: the maximum degrees of rotation during animation, positive values
56+ are clockwise, set 0 for no rotation, in degrees (default: 4 degrees)
5457 :param float animation_time: the time for the animation in seconds, set to 0.0 for
55- no animation, a value of 0.15 is a good starting point (default: 0.0 seconds)
58+ no animation, a value of 0.15 is a good starting point (default: 0.15 seconds)
5659
5760 :param int x: x location the icon widget should be placed. Pixel coordinates.
5861 :param int y: y location the icon widget should be placed. Pixel coordinates.
@@ -62,8 +65,8 @@ class IconAnimated(IconWidget):
6265 :param int anchored_position: (x,y) pixel value for the location of the anchor_point
6366 :type anchored_position: Tuple[int, int]
6467 :param int max_size: (Optional) this will get passed through to the
65- displayio.Group constructor. If omitted we default to
66- grid_size width * grid_size height to make room for all (1, 1) sized cells .
68+ displayio.Group constructor. ``max_size`` should be set to the maximum number of
69+ graphical elements that will be held within the Group of this widget .
6770 """
6871
6972 # pylint: disable=bad-super-call, too-many-instance-attributes, too-many-locals
@@ -75,33 +78,55 @@ class IconAnimated(IconWidget):
7578
7679 @classmethod
7780 def init_class (
78- cls , display = None , max_scale = 1.5 , max_size = (80 , 80 ), max_color_depth = 512
81+ cls , display = None , max_scale = 1.5 , max_icon_size = (80 , 80 ), max_color_depth = 256
7982 ):
8083 """
8184 Initializes the IconAnimated Class variables, including preallocating memory
82- buffers for the icon zoom bitmap and icon palette.
85+ buffers for the icon zoom bitmap and icon zoom palette.
8386
8487 .. Note:: The `init_class` class function must be called before instancing any
8588 IconAnimated widgets. Usage example:
8689 ``IconAnimated.init_class(display=board.DISPLAY, max_scale=1.5,
87- max_size =(80,80), max_color_depth=256)``
90+ max_icon_size =(80,80), max_color_depth=256)``
8891
8992 :param displayio.Display display: The display where the icons will be displayed.
90- :param float max_scale: The maximum zoom of the any of the icons, should be > 1.0,
93+ :param float max_scale: The maximum zoom of the any of the icons, should be >= 1.0,
9194 (default: 1.5)
92- :param max_size: The maximum (x,y) pixel dimensions of any `IconAnimated` bitmap size
93- that will be created (default: (80,80))
94- :type max_size: Tuple[int,int]
95+ :param max_icon_size: The maximum (x,y) pixel dimensions of any `IconAnimated` bitmap size
96+ that will be created (default: (80,80)). Note: This is the original pixel size,
97+ before scaling
98+ :type max_icon_size: Tuple[int,int]
9599 :param int max_color_depth: The maximum color depth of any `IconAnimated`
96- bitmap that will be created (default: 512 )
100+ bitmap that will be created (default: 256 )
97101 """
98102 if display is None :
99- raise ValueError ("Must provide display parameter for IconAnimated." )
103+ raise ValueError (
104+ "IconAninmated.init_class: Must provide display parameter for IconAnimated."
105+ )
106+
107+ if (
108+ isinstance (max_icon_size , tuple )
109+ and len (max_icon_size ) == 2 # validate max_icon_size input
110+ and isinstance (max_icon_size [0 ], int )
111+ and isinstance (max_icon_size [1 ], int )
112+ ):
113+ pass
114+ else :
115+ raise ValueError (
116+ "IconAninmated.init_class: max_icon_size must be an (x,y) "
117+ "tuple of integer pixel sizes."
118+ )
119+
100120 cls .display = display
121+ if max_scale < 1.0 :
122+ print (
123+ "Warning: IconAnimated.init_class - max_scale value was "
124+ "constrained to minimum of 1.0"
125+ )
101126 cls .max_scale = max (1.0 , max_scale )
102127 cls .bitmap_buffer = Bitmap (
103- round (max_scale * max_size [0 ]),
104- round (max_scale * max_size [1 ]),
128+ round (cls . max_scale * max_icon_size [0 ]),
129+ round (cls . max_scale * max_icon_size [1 ]),
105130 max_color_depth + 1 ,
106131 )
107132 cls .palette_buffer = Palette (max_color_depth + 1 )
@@ -111,8 +136,8 @@ def __init__(
111136 label_text ,
112137 icon ,
113138 on_disk = False ,
114- max_scale = 1.4 ,
115- max_angle = 8 ,
139+ scale = None ,
140+ angle = 4 ,
116141 animation_time = 0.15 ,
117142 ** kwargs ,
118143 ):
@@ -125,14 +150,23 @@ def __init__(
125150 )
126151
127152 super ().__init__ (label_text , icon , on_disk , ** kwargs ) # initialize superclasses
153+ print ("icon: {}" .format (self ._icon ))
128154
129155 # constrain instance's maximum_scaling between 1.0 and the Class's max_scale
130- self ._max_scale = min (max (1.0 , max_scale ), self .__class__ .max_scale )
131- if max_scale == 1.0 : # no animation
132- self ._animation_time = 0
156+ if scale is None :
157+ self ._scale = self .__class__ .max_scale
133158 else :
134- self ._animation_time = animation_time # in seconds
135- self ._angle = (max_angle / 360 ) * 2 * 3.14 # 5 degrees, convert to radians
159+ if scale > self .__class__ .max_scale :
160+ print (
161+ "Warning - IconAnimated: max_scale is constrained by value of "
162+ "IconAnimated.max_scale set by IconAnimated.init_class(): {}" .format (
163+ self .__class__ .max_scale
164+ )
165+ )
166+ self ._scale = max (0 , min (scale , self .__class__ .max_scale ))
167+
168+ self ._animation_time = animation_time # in seconds
169+ self ._angle = (angle / 360 ) * 2 * pi # in degrees, convert to radians
136170 self ._zoomed = False # state variable for zoom status
137171
138172 def zoom_animation (self , touch_point ):
@@ -144,14 +178,35 @@ def zoom_animation(self, touch_point):
144178 """
145179
146180 if self ._animation_time > 0 :
147- ###
148- ## Update the zoom palette and bitmap buffers and append the tilegrid
149- ###
181+ try :
182+ _image , _palette = adafruit_imageload .load (self ._icon )
183+
184+ if len (self .__class__ .palette_buffer ) < len (_palette ) + 1 :
185+ self ._animation_time = 0 # skip any animation
186+ print (
187+ "Warning: IconAnimated - icon bitmap exceeds IconAnimated.max_color_depth;"
188+ " defaulting to no animation"
189+ )
190+
191+ except NotImplementedError :
192+ self ._animation_time = 0 # skip any animation
193+ print (
194+ "Warning: IconAnimated - True color BMP unsupported for animation;"
195+ " defaulting to no animation"
196+ )
197+
198+ if self ._animation_time > 0 :
150199
151- _image , _palette = adafruit_imageload .load (self ._icon )
152200 animation_bitmap = self .__class__ .bitmap_buffer
153201 animation_palette = self .__class__ .palette_buffer
154202
203+ # store the current display refresh setting
204+ refresh_status = self .__class__ .display .auto_refresh
205+
206+ ###
207+ ## Update the zoom palette and bitmap buffers and append the tilegrid
208+ ###
209+
155210 # copy the image palette, add a transparent color at the end
156211 for i , color in enumerate (_palette ):
157212 animation_palette [i ] = color
@@ -172,29 +227,37 @@ def zoom_animation(self, touch_point):
172227 )
173228 animation_tilegrid .x = - (animation_bitmap .width - _image .width ) // 2
174229 animation_tilegrid .y = - (animation_bitmap .height - _image .height ) // 2
230+
231+ self .__class__ .display .auto_refresh = False # set auto_refresh off
232+ self [0 ].hidden = True # hide the original icon
175233 self .append (animation_tilegrid ) # add to the self group.
176234
177235 # Animation: zoom larger
178236 start_time = time .monotonic ()
237+
179238 while True :
180239 elapsed_time = time .monotonic () - start_time
181240 position = min (
182241 1.0 , easein (elapsed_time / self ._animation_time )
183242 ) # fractional position
243+ animation_bitmap .fill (len (animation_palette ) - 1 )
184244 bitmaptools .rotozoom (
185245 dest_bitmap = animation_bitmap ,
186246 ox = animation_bitmap .width // 2 ,
187247 oy = animation_bitmap .height // 2 ,
188248 source_bitmap = _image ,
189249 px = _image .width // 2 ,
190250 py = _image .height // 2 ,
191- scale = 1.0 # start scaling at 1.0
192- + position * (self ._max_scale - 1.0 ),
193- angle = position * self ._angle / 2 ,
251+ scale = 1.0 + position * (self ._scale - 1.0 ), # start scaling at 1.0
252+ angle = position * self ._angle ,
194253 )
195254 self .__class__ .display .refresh ()
196255 if elapsed_time > self ._animation_time :
197256 break
257+
258+ # set display.auto_refresh back to original value
259+ self .__class__ .display .auto_refresh = refresh_status
260+
198261 del _image
199262 del _palette
200263 gc .collect ()
@@ -209,11 +272,16 @@ def zoom_out_animation(self, touch_point):
209272 :return: None
210273 """
211274
212- _image , _palette = adafruit_imageload .load (self ._icon )
213- animation_bitmap = self .__class__ .bitmap_buffer
214- animation_palette = self .__class__ .palette_buffer
215-
216275 if (self ._animation_time > 0 ) and self ._zoomed :
276+ _image , _palette = adafruit_imageload .load (self ._icon )
277+ animation_bitmap = self .__class__ .bitmap_buffer
278+ animation_palette = self .__class__ .palette_buffer
279+
280+ # store the current display refresh setting
281+ refresh_status = self .__class__ .display .auto_refresh
282+
283+ self .__class__ .display .auto_refresh = False # set auto_refresh off
284+
217285 # Animation: shrink down to the original size
218286 start_time = time .monotonic ()
219287 while True :
@@ -227,15 +295,21 @@ def zoom_out_animation(self, touch_point):
227295 source_bitmap = _image ,
228296 px = _image .width // 2 ,
229297 py = _image .height // 2 ,
230- scale = 1.0 + position * (self ._max_scale - 1.0 ),
231- angle = position * self ._angle / 2 ,
298+ scale = 1.0 + position * (self ._scale - 1.0 ),
299+ angle = position * self ._angle ,
232300 )
233301 self .__class__ .display .refresh ()
234302 if elapsed_time > self ._animation_time :
235303 break
236304
237305 # clean up the zoom display elements
238- self .pop (- 1 ) # remove self from the group
306+ self [0 ].hidden = False # unhide the original icon
307+ self .pop (- 1 ) # remove zoom tilegrid from the group
308+ self .__class__ .display .refresh ()
309+
310+ # set display.auto_refresh back to original value
311+ self .__class__ .display .auto_refresh = refresh_status
312+
239313 del _image
240314 del _palette
241315 gc .collect ()
0 commit comments