├── Animate.gif ├── DearPyGui_Animate ├── dearpygui_animate.py └── dearpygui_animate_demo.py ├── LICENSE └── README.md /Animate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrtnRitter/DearPyGui_Animate/07b9fed10c0e28e3c0519e218647fd1e564934d1/Animate.gif -------------------------------------------------------------------------------- /DearPyGui_Animate/dearpygui_animate.py: -------------------------------------------------------------------------------- 1 | """ 2 | --------------------------------------------- 3 | dearpygui animations add-on 4 | 5 | https://github.com/mrtnRitter/DearPyGui_Animate 6 | 7 | v0.12 8 | ---------------------------------------------- 9 | """ 10 | 11 | # ----------------------------------------------------------------------------- 12 | # Imports 13 | # ----------------------------------------------------------------------------- 14 | 15 | import dearpygui.dearpygui as dpg 16 | 17 | # ----------------------------------------------------------------------------- 18 | # Global Registers 19 | # ----------------------------------------------------------------------------- 20 | 21 | animations = [] 22 | delta_positions = [] 23 | delta_sizes = [] 24 | delta_opacities = [] 25 | 26 | # ----------------------------------------------------------------------------- 27 | # Main Functions 28 | # ----------------------------------------------------------------------------- 29 | 30 | def add(type, object, startval, endval, ease, duration, **kwargs): 31 | """ 32 | adds a new animation to animations register 33 | """ 34 | 35 | # fix min-values: smallest size window = 32x32, smallest size item = 1x1 36 | if type == "size": 37 | if dpg.get_item_type(object) == "mvAppItemType::Window": 38 | for i in range(2): 39 | if startval[i] < 32: 40 | startval[i] = 32 41 | 42 | elif endval[i] < 32: 43 | endval[i] = 32 44 | else: 45 | for i in range(2): 46 | if startval[i] < 1: 47 | startval[i] = 1 48 | 49 | elif endval[i] < 1: 50 | endval[i] = 1 51 | 52 | # rewrite endval to distance, all calculations are based on distance 53 | try: 54 | distance = [endval[0] - startval[0], endval[1] - startval[1]] 55 | except Exception: 56 | distance = endval - startval 57 | 58 | options = { 59 | "name": "", 60 | "timeoffset": 0, 61 | "loop": "", 62 | "callback": "", 63 | "callback_data": "", 64 | "early_callback": "", 65 | "early_callback_data": "" 66 | } 67 | options.update(kwargs) 68 | 69 | starttime = dpg.get_total_time() + options["timeoffset"] 70 | framecounter = 0 71 | last_ease = 0 72 | loopcounter = 0 73 | isplaying = False 74 | ispaused = False 75 | isreversed = False 76 | 77 | new_animation = [ 78 | options["name"], 79 | type, 80 | object, 81 | startval, 82 | distance, 83 | ease, 84 | duration, 85 | starttime, 86 | framecounter, 87 | last_ease, 88 | options["loop"], 89 | loopcounter, 90 | options["callback"], 91 | options["callback_data"], 92 | options["early_callback"], 93 | options["early_callback_data"], 94 | isplaying, 95 | ispaused, 96 | isreversed 97 | ] 98 | 99 | global animations 100 | animations.append(new_animation) 101 | 102 | 103 | def run(): 104 | """ 105 | Animation data-set layout: 106 | 107 | animation[0] = animation name 108 | animation[1] = animation type 109 | animation[2] = object name 110 | animation[3] = start value 111 | animation[4] = distance 112 | animation[5] = ease 113 | animation[6] = duration 114 | animation[7] = starttime 115 | animation[8] = frame counter 116 | animation[9] = last ease 117 | animation[10] = loop 118 | animation[11] = loop counter 119 | animation[12] = callback function 120 | animation[13] = function data 121 | animation[14] = early callback 122 | animation[15] = early callback data 123 | animation[16] = isplaying 124 | animation[17] = ispaused 125 | animation[18] = isreversed 126 | """ 127 | 128 | animations_updated = [] 129 | callbacks = {} 130 | global animations 131 | 132 | for animation in animations: 133 | 134 | if dpg.get_total_time() >= animation[7] and not animation[17]: 135 | 136 | if animation[14] and animation[8] == 0: 137 | callbacks[animation[14]] = (animation[2], animation[15]) 138 | 139 | animation[16] = True 140 | frame = animation[8] / animation[6] 141 | ease = BezierTransistion(frame, animation[5]) 142 | 143 | if animation[1] == "position": 144 | add_delta_positions(animation, ease) 145 | 146 | elif animation[1] == "size": 147 | add_delta_sizes(animation, ease) 148 | 149 | elif animation[1] == "opacity": 150 | add_delta_opacities(animation, ease) 151 | 152 | animation[9] = ease 153 | 154 | if animation[8] < animation[6]: 155 | if not animation[18]: 156 | animation[8] += 1 157 | else: 158 | if animation[8] == 0: 159 | animation[18] = False 160 | animation[8] = 1 161 | else: 162 | animation[8] -= 1 163 | animations_updated.append(animation) 164 | 165 | elif animation[8] == animation[6]: 166 | if animation[10]: 167 | set_loop(animation, animations_updated) 168 | 169 | if animation[12]: 170 | callbacks[animation[12]] = (animation[2], animation[13]) 171 | 172 | else: 173 | animations_updated.append(animation) 174 | 175 | set_pos() 176 | set_size() 177 | set_opacity() 178 | 179 | animations = animations_updated 180 | 181 | for func, dat in callbacks.items(): 182 | func(dat[0], dat[1]) 183 | 184 | 185 | def play(animation_name): 186 | """ 187 | resumes an animation 188 | """ 189 | 190 | global animations 191 | 192 | for animation in animations: 193 | if animation[0] == animation_name: 194 | animation[17] = False 195 | 196 | 197 | def pause(animation_name): 198 | """ 199 | pauses an animation 200 | """ 201 | 202 | global animations 203 | 204 | for animation in animations: 205 | if animation[0] == animation_name: 206 | animation[17] = True 207 | 208 | 209 | def remove(animation_name): 210 | """ 211 | removes an animation from animations register 212 | """ 213 | 214 | animations_updated = [] 215 | delta_positions_updated = [] 216 | delta_sizes_updated = [] 217 | delta_opacities_updated = [] 218 | object_anitype = [] 219 | global animations 220 | global delta_positions 221 | global delta_sizes 222 | global delta_opacities 223 | 224 | for animation in animations: 225 | if not animation[0] == animation_name: 226 | animations_updated.append(animation) 227 | else: 228 | object_anitype = [animation[2], animation[1]] 229 | 230 | if object_anitype: 231 | found = False 232 | for ani in animations_updated: 233 | if ani[2] == object_anitype[0] and ani[1] == object_anitype[1]: 234 | found = True 235 | break 236 | 237 | if not found: 238 | if object_anitype[1] == "position": 239 | for entry in delta_positions: 240 | if not entry[0] == object_anitype[0]: 241 | delta_positions_updated.append(entry) 242 | delta_positions = delta_positions_updated 243 | 244 | elif object_anitype[1] == "size": 245 | for entry in delta_sizes: 246 | if not entry[0] == object_anitype[0]: 247 | delta_sizes_updated.append(entry) 248 | delta_sizes = delta_sizes_updated 249 | 250 | elif object_anitype[1] == "opacity": 251 | for entry in delta_opacities: 252 | if not entry[0] == object_anitype[0]: 253 | delta_opacities_updated.append(entry) 254 | delta_opacities = delta_opacities_updated 255 | 256 | animations = animations_updated 257 | 258 | 259 | def get(*args): 260 | """ 261 | return animation data as requested 262 | """ 263 | 264 | return_data = [] 265 | global animations 266 | 267 | for animation in animations: 268 | for entry in args: 269 | if entry == "name": 270 | return_data.append(animation[0]) 271 | 272 | if entry == "type": 273 | return_data.append(animation[1]) 274 | 275 | if entry == "object": 276 | return_data.append(animation[2]) 277 | 278 | if entry == "startval": 279 | return_data.append(animation[3]) 280 | 281 | if entry == "endval": 282 | try: 283 | endval = [animation[3][0] + animation[4][0], animation[3][1] + animation[4][1]] 284 | except Exception: 285 | endval = animation[3] + animation[4] 286 | return_data.append(endval) 287 | 288 | if entry == "ease": 289 | return_data.append(animation[5]) 290 | 291 | if entry == "duration": 292 | return_data.append(animation[6]) 293 | 294 | if entry == "starttime": 295 | return_data.append(animation[7]) 296 | 297 | if entry == "framecounter": 298 | return_data.append(animation[8]) 299 | 300 | if entry == "loop": 301 | return_data.append(animation[10]) 302 | 303 | if entry == "loopcounter": 304 | return_data.append(animation[11]) 305 | 306 | if entry == "callback": 307 | return_data.append(animation[12]) 308 | 309 | if entry == "callback_data": 310 | return_data.append(animation[13]) 311 | 312 | if entry == "early_callback": 313 | return_data.append(animation[14]) 314 | 315 | if entry == "early_callback_data": 316 | return_data.append(animation[15]) 317 | 318 | if entry == "isplaying": 319 | return_data.append(animation[16]) 320 | 321 | if entry == "ispaused": 322 | return_data.append(animation[17]) 323 | 324 | if not return_data: 325 | return False 326 | 327 | else: 328 | return return_data 329 | 330 | 331 | # ----------------------------------------------------------------------------- 332 | # Helper Functions 333 | # ----------------------------------------------------------------------------- 334 | 335 | def BezierTransistion(search, handles): 336 | """ 337 | solving y (progress) of bezier curve for given x (time) 338 | using the newton-raphson method 339 | """ 340 | 341 | h1x, h1y, h2x, h2y = handles 342 | 343 | cx = 3 * h1x 344 | bx = 3 * (h2x - h1x) - cx 345 | ax = 1 - cx - bx 346 | 347 | t = search 348 | 349 | for i in range(100): 350 | x = (ax * t ** 3 + bx * t ** 2 + cx * t) - search 351 | 352 | if round(x, 4) == 0: 353 | break 354 | 355 | dx = 3.0 * ax * t ** 2 + 2.0 * bx * t + cx 356 | 357 | t -= (x / dx) 358 | 359 | return 3 * t * (1 - t) ** 2 * h1y + 3 * t ** 2 * (1 - t) * h2y + t ** 3 360 | 361 | 362 | def set_loop(animation, animations_updated): 363 | """ 364 | prepare animation for next loop iteration 365 | """ 366 | 367 | if animation[10] == "ping-pong": 368 | animation[18] = True 369 | animation[8] -= 1 370 | animation[9] = 1 371 | 372 | elif animation[10] == "cycle": 373 | animation[8] = 0 374 | animation[9] = 0 375 | 376 | elif animation[10] == "continue": 377 | try: 378 | animation[3] = [animation[3][0] + animation[4][0], animation[3][1] + animation[4][1]] 379 | except Exception: 380 | animation[3] += animation[4] 381 | animation[8] = 0 382 | animation[9] = 0 383 | 384 | animation[11] += 1 385 | animations_updated.append(animation) 386 | 387 | 388 | def add_delta_positions(animation, ease): 389 | """ 390 | collects delta movements of all position animations for a certain item 391 | """ 392 | 393 | global delta_positions 394 | 395 | for item in delta_positions: 396 | if animation[2] == item[0]: 397 | 398 | x_step = animation[4][0] * (ease - animation[9]) 399 | y_step = animation[4][1] * (ease - animation[9]) 400 | 401 | item[1] += x_step 402 | item[2] += y_step 403 | 404 | if animation[8] < animation[6] or animation[10]: 405 | item[3] = True 406 | 407 | if animation[10] == "cycle" and animation[8] == animation[6]: 408 | item[3] = False 409 | 410 | if animation[8] == animation[6] and not item[3]: 411 | item[3] = False 412 | 413 | break 414 | else: 415 | delta_positions.append([animation[2], animation[3][0], animation[3][1], True]) 416 | 417 | 418 | def add_delta_sizes(animation, ease): 419 | """ 420 | collects delta movements of all size animations for a certain item 421 | """ 422 | 423 | global delta_sizes 424 | 425 | for item in delta_sizes: 426 | if animation[2] == item[0]: 427 | w_step = animation[4][0] * (ease - animation[9]) 428 | h_step = animation[4][1] * (ease - animation[9]) 429 | 430 | item[1] += w_step 431 | item[2] += h_step 432 | 433 | if animation[8] < animation[6] or animation[10]: 434 | item[3] = True 435 | 436 | if animation[10] == "cycle" and animation[8] == animation[6]: 437 | item[3] = False 438 | 439 | if animation[8] == animation[6] and not item[3]: 440 | item[3] = False 441 | 442 | break 443 | else: 444 | delta_sizes.append([animation[2], animation[3][0], animation[3][1], True]) 445 | 446 | 447 | def add_delta_opacities(animation, ease): 448 | """ 449 | collects delta movements of all opacity animations for a certain item 450 | """ 451 | 452 | global delta_opacities 453 | 454 | for item in delta_opacities: 455 | if animation[2] == item[0]: 456 | o_step = animation[4] * (ease - animation[9]) 457 | 458 | item[1] += o_step 459 | 460 | if animation[8] < animation[6] or animation[10]: 461 | item[2] = True 462 | 463 | if animation[10] == "cycle" and animation[8] == animation[6]: 464 | item[2] = False 465 | 466 | if animation[8] == animation[6] and not item[2]: 467 | item[2] = False 468 | 469 | break 470 | else: 471 | delta_opacities.append([animation[2], animation[3], True]) 472 | 473 | 474 | def set_pos(): 475 | """ 476 | moves the item 477 | """ 478 | 479 | global delta_positions 480 | 481 | items_updated = [] 482 | 483 | for item in delta_positions: 484 | if item[3] is None: 485 | items_updated.append(item) 486 | continue 487 | 488 | elif item[3]: 489 | x_int = int(item[1]) 490 | y_int = int(item[2]) 491 | 492 | item[3] = None 493 | 494 | items_updated.append(item) 495 | 496 | else: 497 | x_int = round(item[1]) 498 | y_int = round(item[2]) 499 | 500 | dpg.set_item_pos(item[0], [x_int, y_int]) 501 | 502 | delta_positions = items_updated 503 | 504 | 505 | def set_size(): 506 | """ 507 | set items size 508 | """ 509 | 510 | global delta_sizes 511 | 512 | items_updated = [] 513 | 514 | for item in delta_sizes: 515 | if item[3] is None: 516 | items_updated.append(item) 517 | continue 518 | 519 | elif item[3]: 520 | w_int = int(item[1]) 521 | h_int = int(item[2]) 522 | 523 | item[3] = None 524 | 525 | items_updated.append(item) 526 | 527 | else: 528 | w_int = round(item[1]) 529 | h_int = round(item[2]) 530 | 531 | dpg.set_item_width(item[0], w_int) 532 | dpg.set_item_height(item[0], h_int) 533 | 534 | delta_sizes = items_updated 535 | 536 | 537 | def dpg_get_alpha_style(item): 538 | theme = dpg.get_item_theme(item) 539 | if theme is None: 540 | theme = dpg.add_theme() 541 | theme_component = dpg.add_theme_component(dpg.mvAll, parent=theme) 542 | alpha_style = dpg.add_theme_style(dpg.mvStyleVar_Alpha, 1, category=dpg.mvThemeCat_Core, parent=theme_component) 543 | dpg.bind_item_theme(item, theme) 544 | return alpha_style 545 | 546 | all_components = dpg.get_item_children(theme, 1) 547 | theme_component = None 548 | for component in all_components: 549 | if dpg.get_item_configuration(component)['item_type'] == dpg.mvAll: 550 | theme_component = component 551 | break 552 | if theme_component is None: 553 | theme_component = dpg.add_theme_component(parent=theme) 554 | 555 | all_styles = dpg.get_item_children(theme_component, 1) 556 | alpha_style = None 557 | for style in all_styles: 558 | if dpg.get_item_configuration(style)['target'] == dpg.mvStyleVar_Alpha: 559 | alpha_style = style 560 | break 561 | if alpha_style is None: 562 | alpha_style = dpg.add_theme_style(dpg.mvStyleVar_Alpha, 1, category=dpg.mvThemeCat_Core, parent=theme_component) 563 | return alpha_style 564 | 565 | 566 | def set_opacity(): 567 | """ 568 | set items opacity 569 | """ 570 | 571 | global delta_opacities 572 | 573 | items_updated = [] 574 | 575 | for item in delta_opacities: 576 | if item[2] is None: 577 | items_updated.append(item) 578 | continue 579 | 580 | elif item[2]: 581 | item[2] = None 582 | items_updated.append(item) 583 | 584 | if dpg.get_item_type(item[0]) == "mvAppItemType::mvText": 585 | new_color = dpg.get_item_configuration(item[0])["color"] 586 | new_color = list(map(lambda color: int(color * 255), new_color[:3:])) 587 | 588 | new_color.append(item[1] * 255) 589 | 590 | dpg.configure_item(item[0], color=new_color) 591 | else: 592 | dpg.set_value(dpg_get_alpha_style(item[0]), [item[1]]) 593 | 594 | delta_opacities = items_updated 595 | -------------------------------------------------------------------------------- /DearPyGui_Animate/dearpygui_animate_demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo for dearpygui_animate add-on 3 | 4 | https://github.com/mrtnRitter/DearPyGui_Animate 5 | 6 | """ 7 | 8 | import dearpygui.dearpygui as dpg 9 | 10 | import dearpygui_animate as animate 11 | 12 | 13 | # ----------------------------------------------------------------------------- 14 | # Render Callback 15 | # ----------------------------------------------------------------------------- 16 | 17 | 18 | def onUpdate(): 19 | """ 20 | render callback function 21 | """ 22 | animate.run() 23 | update_running_animations() 24 | 25 | 26 | def update_running_animations(): 27 | playing = animate.get("isplaying") 28 | 29 | if playing: 30 | running = 0 31 | 32 | for play in playing: 33 | if play: 34 | running += 1 35 | 36 | dpg.set_value("running_animations", "animations running: " + str(running)) 37 | 38 | else: 39 | dpg.set_value("running_animations", "animations running: 0") 40 | 41 | 42 | # ----------------------------------------------------------------------------- 43 | # Main Menu Related 44 | # ----------------------------------------------------------------------------- 45 | 46 | def show_buttons(sender, data): 47 | # shorthand to unhide items before running animation 48 | animate.add("opacity", "Info", 0, 1, [.64, .12, .72, .86], 20, timeoffset=10 / 60) 49 | animate.add("opacity", "Animate Position", 0, 1, [.64, .12, .72, .86], 20, timeoffset=15 / 60, early_callback=lambda sender, data: dpg.show_item("Animate Position")) 50 | animate.add("opacity", "Animate Size", 0, 1, [.64, .12, .72, .86], 20, timeoffset=20 / 60, early_callback=lambda sender, data: dpg.show_item("Animate Size")) 51 | animate.add("opacity", "Animate Opacity", 0, 1, [.64, .12, .72, .86], 20, timeoffset=25 / 60, early_callback=lambda sender, data: dpg.show_item("Animate Opacity")) 52 | 53 | 54 | def gotoDemo(data): 55 | if data == "position": 56 | call = demo_position 57 | elif data == "size": 58 | call = demo_size 59 | elif data == "opacity": 60 | call = demo_opacity 61 | 62 | animate.add("position", "Demo", [562, 225], [20, 20], [.51, .05, .5, .9], 40) 63 | animate.add("size", "Demo", [156, 170], [156, 80], [.51, .05, .5, .9], 40) 64 | animate.add("opacity", "Demo", 1, 0.5, [.51, .05, .5, .9], 40, callback=call) 65 | 66 | 67 | # ----------------------------------------------------------------------------- 68 | # Position Demo Related 69 | # ----------------------------------------------------------------------------- 70 | 71 | def demo_position(sender, data): 72 | # default values 73 | dpg.delete_item("pos_info") 74 | dpg.add_text(tag="pos_info", default_value="Animations can be stagged,\nindividual values will add up", parent="Position Demo", before="spacing") 75 | dpg.set_item_pos("Position Demo", [1500, -100]) 76 | animate.add("opacity", "Position Demo", 0, 1, [.51, .05, .5, .9], 1) 77 | dpg.show_item("Position Demo") 78 | 79 | # animations overlapping in time adding up their values 80 | animate.add("position", "Position Demo", [1280, -100], [200, -100], [.51, .05, .5, .9], 600) 81 | animate.add("position", "Position Demo", [1280, -100], [1280, 300], [.51, .05, .5, .9], 600) 82 | animate.add("position", "Position Demo", [1280, -100], [1000, 0], [.51, .05, .5, .9], 30) 83 | animate.add("position", "Position Demo", [0, 0], [50, 50], [.51, .05, .5, .9], 100, timeoffset=4) 84 | animate.add("position", "Position Demo", [0, 0], [200, 0], [.51, .05, .5, .9], 100, timeoffset=5) 85 | animate.add("position", "Position Demo", [0, 0], [0, 200], [.51, .05, .5, .9], 100, timeoffset=6) 86 | animate.add("position", "Position Demo", [0, 0], [0, -200], [.51, .05, .5, .9], 100, timeoffset=7) 87 | animate.add("position", "Position Demo", [0, 0], [500, -300], [.51, .05, .5, .9], 60, timeoffset=9) 88 | 89 | animate.add("position", "Position Demo", [670, 150], [500, 300], [.51, .05, .5, .9], 10, timeoffset=10) 90 | 91 | # animations cancel each other out, because they are exactly the same but with different directions 92 | animate.add("position", "Position Demo", [500, 300], [500, 0], [.51, .05, .5, .9], 300, timeoffset=11, early_callback=update_demo_position_text) 93 | animate.add("position", "Position Demo", [500, 300], [500, 600], [.51, .05, .5, .9], 300, timeoffset=11) 94 | 95 | animate.add("opacity", "Position Demo", 1, 0, [.51, .05, .5, .9], 20, timeoffset=18, callback=remove_pos_demo) 96 | 97 | 98 | def update_demo_position_text(sender, data): 99 | dpg.delete_item("pos_info") 100 | dpg.add_text(tag="pos_info", default_value="Now two animations are\npulling against each other\n... really!", parent="Position Demo", before="spacing") 101 | 102 | 103 | def remove_pos_demo(sender, data): 104 | dpg.hide_item("Position Demo") 105 | animate.add("position", "Demo", [20, 20], [562, 225], [.51, .05, .5, .9], 40) 106 | animate.add("size", "Demo", [156, 80], [156, 170], [.51, .05, .5, .9], 40) 107 | animate.add("opacity", "Demo", 0.5, 1, [.51, .05, .5, .9], 40) 108 | 109 | 110 | # ----------------------------------------------------------------------------- 111 | # Size Demo Related 112 | # ----------------------------------------------------------------------------- 113 | 114 | def demo_size(sender, data): 115 | # default values 116 | animate.add("opacity", "Size Demo", 1, 0, [.51, .05, .5, .9], 1) 117 | animate.add("size", "Size Demo", [220, 120], [220, 120], [.51, .05, .5, .9], 1) 118 | animate.add("position", "Size Demo", [530, 260], [530, 260], [.51, .05, .5, .9], 1) 119 | animate.add("size", "continued", [204, 20], [204, 20], [.51, .05, .5, .9], 1) 120 | animate.add("size", "paused", [204, 20], [204, 20], [.51, .05, .5, .9], 1) 121 | animate.add("size", "terminated", [204, 20], [204, 20], [.51, .05, .5, .9], 1) 122 | 123 | animate.add("opacity", "Size Demo", 0, 1, [.51, .05, .5, .9], 60, timeoffset=2 / 60, early_callback=lambda sender, data: dpg.show_item("Size Demo")) 124 | 125 | animate.add("size", "Size Demo", [220, 120], [500, 360], [.51, .05, .5, .9], 120, loop="ping-pong", name="size_loop") 126 | animate.add("position", "Size Demo", [530, 260], [390, 70], [.51, .05, .5, .9], 120, loop="ping-pong", name="pos_loop") 127 | animate.add("size", "continued", [204, 20], [484, 100], [.51, .05, .5, .9], 120, loop="ping-pong", name="size_loop_btn1") 128 | animate.add("size", "paused", [204, 20], [484, 100], [.51, .05, .5, .9], 120, loop="ping-pong", name="size_loop_btn2") 129 | animate.add("size", "terminated", [204, 20], [484, 100], [.51, .05, .5, .9], 120, loop="ping-pong", name="size_loop_btn3") 130 | 131 | animate.pause("size_loop") 132 | animate.pause("pos_loop") 133 | animate.pause("size_loop_btn1") 134 | animate.pause("size_loop_btn2") 135 | animate.pause("size_loop_btn3") 136 | 137 | 138 | def cont(sender, data): 139 | animate.play("size_loop") 140 | animate.play("pos_loop") 141 | animate.play("size_loop_btn1") 142 | animate.play("size_loop_btn2") 143 | animate.play("size_loop_btn3") 144 | 145 | 146 | def pause(sender, data): 147 | animate.pause("size_loop") 148 | animate.pause("pos_loop") 149 | animate.pause("size_loop_btn1") 150 | animate.pause("size_loop_btn2") 151 | animate.pause("size_loop_btn3") 152 | 153 | 154 | def remove(sender, data): 155 | animate.remove("size_loop") 156 | animate.remove("pos_loop") 157 | animate.remove("size_loop_btn1") 158 | animate.remove("size_loop_btn2") 159 | animate.remove("size_loop_btn3") 160 | 161 | 162 | def remove_size_demo(sender, data): 163 | animate.remove("size_loop") 164 | animate.remove("pos_loop") 165 | animate.remove("size_loop_btn1") 166 | animate.remove("size_loop_btn2") 167 | animate.remove("size_loop_btn3") 168 | dpg.hide_item("Size Demo") 169 | animate.add("position", "Demo", [20, 20], [562, 225], [.51, .05, .5, .9], 40) 170 | animate.add("size", "Demo", [156, 80], [156, 170], [.51, .05, .5, .9], 40) 171 | animate.add("opacity", "Demo", 0.5, 1, [.51, .05, .5, .9], 40) 172 | 173 | 174 | # ----------------------------------------------------------------------------- 175 | # Opacity Demo Related 176 | # ----------------------------------------------------------------------------- 177 | 178 | def demo_opacity(sender, data): 179 | animate.add("opacity", "Opacity Demo1", 0, 1, [.51, .05, .5, .9], 60, timeoffset=2 / 60, early_callback=lambda sender, data: dpg.show_item("Opacity Demo1"), loop="ping-pong", name="top1") 180 | animate.add("opacity", "Opacity Demo2", 0, 1, [.51, .05, .5, .9], 60, timeoffset=22 / 60, early_callback=lambda sender, data: dpg.show_item("Opacity Demo2"), loop="ping-pong", name="top2") 181 | animate.add("opacity", "Opacity Demo3", 0, 1, [.51, .05, .5, .9], 60, timeoffset=42 / 60, early_callback=lambda sender, data: dpg.show_item("Opacity Demo3"), loop="ping-pong", name="top3") 182 | animate.add("opacity", "Opacity Demo4", 0, 1, [.51, .05, .5, .9], 60, timeoffset=62 / 60, early_callback=lambda sender, data: dpg.show_item("Opacity Demo4"), loop="ping-pong", name="top4") 183 | 184 | animate.add("opacity", "Loop1", 0.4, 1, [.51, .05, .5, .9], 10, timeoffset=122 / 60, loop="ping-pong", name="pp_loop") 185 | animate.add("position", "Loop1", [565, 800], [565, 300], [.51, .05, .5, .9], 30, timeoffset=125 / 60, early_callback=lambda sender, data: dpg.show_item("Loop1")) 186 | 187 | 188 | def loop_cycle(sender, data): 189 | animate.add("position", "Loop1", [565, 300], [1500, 300], [.51, .05, .5, .9], 30, callback=lambda sender, data: dpg.hide_item("Loop1")) 190 | animate.remove("pp_loop") 191 | 192 | animate.add("opacity", "Loop2", 0, 1, [.51, .05, .5, .9], 30, timeoffset=1, early_callback=lambda sender, data: dpg.show_item("Loop2")) 193 | animate.add("size", "Loop2", [0, 0], [180, 120], [.06, .54, .11, .98], 85, timeoffset=1, loop="cycle", name="cycleloop_size") 194 | animate.add("position", "Loop2", [639, 339], [565, 300], [.06, .54, .11, .98], 85, timeoffset=1, loop="cycle", name="cycleloop_pos") 195 | 196 | 197 | def loop_continue(sender, data): 198 | animate.remove("cycleloop_size") 199 | animate.remove("cycleloop_pos") 200 | 201 | x, y = dpg.get_item_pos("Loop2") 202 | animate.add("position", "Loop2", [x, y], [x, 800], [.06, .54, .11, .98], 20) 203 | animate.add("opacity", "Loop2", 1, 0, [.06, .54, .11, .98], 20, callback=lambda sender, data: dpg.hide_item("Loop2")) 204 | 205 | animate.add("position", "Loop3", [-300, 300], [-250, 300], [.01, .97, .1, .98], 30, loop="continue", callback=checkforEnd, name="cont_loop") 206 | dpg.show_item("Loop3") 207 | 208 | 209 | def checkforEnd(sender, data): 210 | x = dpg.get_item_pos("Loop3")[0] 211 | 212 | if x > 1200: 213 | loop_close("Loop3", None) 214 | 215 | 216 | def loop_close(sender, data): 217 | animate.remove("cont_loop") 218 | dpg.hide_item("Loop3") 219 | 220 | animate.remove("top1") 221 | animate.remove("top2") 222 | animate.remove("top3") 223 | animate.remove("top4") 224 | dpg.hide_item("Opacity Demo1") 225 | dpg.hide_item("Opacity Demo2") 226 | dpg.hide_item("Opacity Demo3") 227 | dpg.hide_item("Opacity Demo4") 228 | 229 | animate.add("position", "Demo", [20, 20], [562, 225], [.51, .05, .5, .9], 40) 230 | animate.add("size", "Demo", [156, 80], [156, 170], [.51, .05, .5, .9], 40) 231 | animate.add("opacity", "Demo", 0.5, 1, [.51, .05, .5, .9], 40) 232 | 233 | 234 | # ----------------------------------------------------------------------------- 235 | # Windows 236 | # ----------------------------------------------------------------------------- 237 | 238 | dpg.create_context() 239 | dpg.create_viewport(title="dearpygui_animate D E M O", width=1280, height=720) 240 | 241 | with dpg.theme() as global_theme: 242 | with dpg.theme_component(dpg.mvAll): 243 | dpg.add_theme_style(dpg.mvStyleVar_WindowTitleAlign, 0.5, 0.5, category=dpg.mvThemeCat_Core) 244 | dpg.add_theme_style(dpg.mvStyleVar_WindowRounding, 7, category=dpg.mvThemeCat_Core) 245 | dpg.add_theme_color(dpg.mvThemeCol_Text, (255, 255, 255, 255), category=dpg.mvThemeCat_Core) 246 | dpg.add_theme_color(dpg.mvThemeCol_TextDisabled, (128, 128, 128, 255), category=dpg.mvThemeCat_Core) 247 | dpg.add_theme_color(dpg.mvThemeCol_WindowBg, (15, 15, 15, 240), category=dpg.mvThemeCat_Core) 248 | dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (255, 255, 255, 0), category=dpg.mvThemeCat_Core) 249 | dpg.add_theme_color(dpg.mvThemeCol_PopupBg, (20, 20, 20, 240), category=dpg.mvThemeCat_Core) 250 | dpg.add_theme_color(dpg.mvThemeCol_Border, (110, 110, 128, 128), category=dpg.mvThemeCat_Core) 251 | dpg.add_theme_color(dpg.mvThemeCol_BorderShadow, (0, 0, 0, 0), category=dpg.mvThemeCat_Core) 252 | dpg.add_theme_color(dpg.mvThemeCol_FrameBg, (51, 54, 56, 138), category=dpg.mvThemeCat_Core) 253 | dpg.add_theme_color(dpg.mvThemeCol_FrameBgHovered, (102, 102, 102, 102), category=dpg.mvThemeCat_Core) 254 | dpg.add_theme_color(dpg.mvThemeCol_FrameBgActive, (46, 46, 46, 171), category=dpg.mvThemeCat_Core) 255 | dpg.add_theme_color(dpg.mvThemeCol_TitleBg, (10, 10, 10, 255), category=dpg.mvThemeCat_Core) 256 | dpg.add_theme_color(dpg.mvThemeCol_TitleBgActive, (74, 74, 74, 255), category=dpg.mvThemeCat_Core) 257 | dpg.add_theme_color(dpg.mvThemeCol_TitleBgCollapsed, (0, 0, 0, 130), category=dpg.mvThemeCat_Core) 258 | dpg.add_theme_color(dpg.mvThemeCol_MenuBarBg, (36, 36, 36, 255), category=dpg.mvThemeCat_Core) 259 | dpg.add_theme_color(dpg.mvThemeCol_ScrollbarBg, (5, 5, 5, 135), category=dpg.mvThemeCat_Core) 260 | dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrab, (79, 79, 79, 255), category=dpg.mvThemeCat_Core) 261 | dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrabHovered, (105, 105, 105, 255), category=dpg.mvThemeCat_Core) 262 | dpg.add_theme_color(dpg.mvThemeCol_ScrollbarGrabActive, (130, 130, 130, 255), category=dpg.mvThemeCat_Core) 263 | dpg.add_theme_color(dpg.mvThemeCol_CheckMark, (240, 240, 240, 255), category=dpg.mvThemeCat_Core) 264 | dpg.add_theme_color(dpg.mvThemeCol_SliderGrab, (130, 130, 130, 255), category=dpg.mvThemeCat_Core) 265 | dpg.add_theme_color(dpg.mvThemeCol_SliderGrabActive, (219, 219, 219, 255), category=dpg.mvThemeCat_Core) 266 | dpg.add_theme_color(dpg.mvThemeCol_Button, (112, 112, 112, 102), category=dpg.mvThemeCat_Core) 267 | dpg.add_theme_color(dpg.mvThemeCol_ButtonHovered, (117, 120, 122, 255), category=dpg.mvThemeCat_Core) 268 | dpg.add_theme_color(dpg.mvThemeCol_ButtonActive, (107, 107, 107, 255), category=dpg.mvThemeCat_Core) 269 | dpg.add_theme_color(dpg.mvThemeCol_Header, (179, 179, 179, 79), category=dpg.mvThemeCat_Core) 270 | dpg.add_theme_color(dpg.mvThemeCol_HeaderHovered, (179, 179, 179, 204), category=dpg.mvThemeCat_Core) 271 | dpg.add_theme_color(dpg.mvThemeCol_HeaderActive, (122, 128, 133, 255), category=dpg.mvThemeCat_Core) 272 | dpg.add_theme_color(dpg.mvThemeCol_Separator, (110, 110, 128, 128), category=dpg.mvThemeCat_Core) 273 | dpg.add_theme_color(dpg.mvThemeCol_SeparatorHovered, (184, 184, 184, 199), category=dpg.mvThemeCat_Core) 274 | dpg.add_theme_color(dpg.mvThemeCol_SeparatorActive, (130, 130, 130, 255), category=dpg.mvThemeCat_Core) 275 | dpg.add_theme_color(dpg.mvThemeCol_ResizeGrip, (232, 232, 232, 64), category=dpg.mvThemeCat_Core) 276 | dpg.add_theme_color(dpg.mvThemeCol_ResizeGripHovered, (207, 207, 207, 171), category=dpg.mvThemeCat_Core) 277 | dpg.add_theme_color(dpg.mvThemeCol_ResizeGripActive, (117, 117, 117, 242), category=dpg.mvThemeCat_Core) 278 | dpg.add_theme_color(dpg.mvThemeCol_Tab, (46, 89, 148, 220), category=dpg.mvThemeCat_Core) 279 | dpg.add_theme_color(dpg.mvThemeCol_TabHovered, (66, 150, 250, 204), category=dpg.mvThemeCat_Core) 280 | dpg.add_theme_color(dpg.mvThemeCol_TabActive, (51, 105, 173, 255), category=dpg.mvThemeCat_Core) 281 | dpg.add_theme_color(dpg.mvThemeCol_TabUnfocused, (17, 26, 38, 248), category=dpg.mvThemeCat_Core) 282 | dpg.add_theme_color(dpg.mvThemeCol_TabUnfocusedActive, (35, 67, 108, 255), category=dpg.mvThemeCat_Core) 283 | dpg.add_theme_color(dpg.mvThemeCol_DockingPreview, (66, 150, 250, 179), category=dpg.mvThemeCat_Core) 284 | dpg.add_theme_color(dpg.mvThemeCol_DockingEmptyBg, (51, 51, 51, 255), category=dpg.mvThemeCat_Core) 285 | dpg.add_theme_color(dpg.mvThemeCol_PlotLines, (156, 156, 156, 255), category=dpg.mvThemeCat_Core) 286 | dpg.add_theme_color(dpg.mvThemeCol_PlotLinesHovered, (255, 110, 89, 255), category=dpg.mvThemeCat_Core) 287 | dpg.add_theme_color(dpg.mvThemeCol_PlotHistogram, (186, 153, 38, 255), category=dpg.mvThemeCat_Core) 288 | dpg.add_theme_color(dpg.mvThemeCol_PlotHistogramHovered, (255, 153, 0, 255), category=dpg.mvThemeCat_Core) 289 | dpg.add_theme_color(dpg.mvThemeCol_TextSelectedBg, (222, 222, 222, 89), category=dpg.mvThemeCat_Core) 290 | dpg.add_theme_color(dpg.mvThemeCol_DragDropTarget, (255, 255, 0, 230), category=dpg.mvThemeCat_Core) 291 | dpg.add_theme_color(dpg.mvThemeCol_NavHighlight, (153, 153, 153, 255), category=dpg.mvThemeCat_Core) 292 | dpg.add_theme_color(dpg.mvThemeCol_NavWindowingHighlight, (255, 255, 255, 179), category=dpg.mvThemeCat_Core) 293 | dpg.add_theme_color(dpg.mvThemeCol_NavWindowingDimBg, (204, 204, 204, 51), category=dpg.mvThemeCat_Core) 294 | dpg.add_theme_color(dpg.mvThemeCol_ModalWindowDimBg, (204, 204, 204, 89), category=dpg.mvThemeCat_Core) 295 | 296 | dpg.bind_theme(global_theme) 297 | 298 | with dpg.window(label="Demo", tag="Demo", width=36, height=32, min_size=[36, 32], no_resize=True, no_move=True, no_close=True, no_collapse=True, no_scrollbar=True): 299 | dpg.add_text("This demo will show \nsome basic functions\nof DearPyGui_Animate", tag="Info", parent="Demo", color=[255, 255, 255, 0]) 300 | dpg.add_spacer(height=5, parent="Demo") 301 | dpg.add_button(tag="Animate Position", label="Animate Position", parent="Demo", width=140, callback=lambda: gotoDemo("position")) 302 | dpg.add_button(tag="Animate Size", label="Animate Size", parent="Demo", width=140, callback=lambda: gotoDemo("size")) 303 | dpg.add_button(tag="Animate Opacity", label="Animate Opacity", parent="Demo", width=140, callback=lambda: gotoDemo("opacity")) 304 | dpg.hide_item("Animate Position") 305 | dpg.hide_item("Animate Size") 306 | dpg.hide_item("Animate Opacity") 307 | 308 | with dpg.window(label="Position Demo", tag="Position Demo", width=220, height=110, no_resize=True, no_move=True, no_close=True, no_collapse=True, no_scrollbar=True): 309 | dpg.add_text(tag="pos_info", default_value="Animations can be stagged,\nindividual values will add up") 310 | with dpg.group(tag="spacing"): 311 | dpg.add_spacer(height=3) 312 | dpg.add_text("", tag="running_animations") 313 | dpg.hide_item("Position Demo") 314 | 315 | with dpg.window(label="Size Demo", tag="Size Demo", width=220, height=120, pos=[530, 260], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, on_close=remove_size_demo): 316 | dpg.add_text("At any time animations can be", tag="size_info") 317 | dpg.add_button(label="continued", tag="continued", width=204, callback=cont) 318 | dpg.add_button(label="paused", tag="paused", width=204, callback=pause) 319 | dpg.add_button(label="terminated", tag="terminated", width=204, callback=remove) 320 | dpg.hide_item("Size Demo") 321 | 322 | with dpg.window(tag="Opacity Demo1", width=150, height=80, min_size=[150, 80], pos=[300, 100], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 323 | dpg.add_spacer(height=6) 324 | dpg.add_text(tag="op_info_1", default_value=" Animations") 325 | dpg.hide_item("Opacity Demo1") 326 | 327 | with dpg.window(tag="Opacity Demo2", width=150, height=80, min_size=[150, 80], pos=[500, 100], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 328 | dpg.add_spacer(height=6) 329 | dpg.add_text(tag="op_info_2", default_value=" can") 330 | dpg.hide_item("Opacity Demo2") 331 | 332 | with dpg.window(tag="Opacity Demo3", width=150, height=80, min_size=[150, 80], pos=[700, 100], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 333 | dpg.add_spacer(height=6) 334 | dpg.add_text(tag="op_info_3", default_value=" be") 335 | dpg.hide_item("Opacity Demo3") 336 | 337 | with dpg.window(tag="Opacity Demo4", width=150, height=80, min_size=[150, 80], pos=[900, 100], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 338 | dpg.add_spacer(height=6) 339 | dpg.add_text(tag="op_info_4", default_value=" looped") 340 | dpg.hide_item("Opacity Demo4") 341 | 342 | with dpg.window(tag="Loop1", width=180, height=120, pos=[565, 300], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 343 | dpg.add_text(tag="loop_info_1", default_value=" ping-pong") 344 | dpg.add_text(tag="loop_des_1", default_value="Moves from start to end,\nmoves back to start,\nrepeat") 345 | dpg.add_spacer(height=3) 346 | dpg.add_button(tag="next_loop_1", width=164, label="next", callback=loop_cycle) 347 | dpg.hide_item("Loop1") 348 | 349 | with dpg.window(tag="Loop2", width=180, height=110, pos=[565, 300], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 350 | dpg.add_text(tag="loop_info_2", default_value=" cycle") 351 | dpg.add_text(tag="loop_des_2", default_value="Moves from start to end,\nrepeat\n") 352 | dpg.add_spacer(height=7) 353 | dpg.add_button(tag="next_loop_2", width=164, label="next", callback=loop_continue) 354 | dpg.hide_item("Loop2") 355 | 356 | with dpg.window(tag="Loop3", width=180, height=120, pos=[565, 300], no_resize=True, no_move=True, no_collapse=True, no_scrollbar=True, no_close=True, no_title_bar=True): 357 | dpg.add_text(tag="loop_info_3", default_value=" continue") 358 | dpg.add_text(tag="loop_des_3", default_value="Moves from start to end\ntakes end as start\nrepeats movement") 359 | dpg.add_spacer(height=3) 360 | dpg.add_button(tag="next_loop_3", width=164, label="close", callback=loop_close) 361 | dpg.hide_item("Loop3") 362 | 363 | # Start Animation 364 | animate.add("position", "Demo", [622, 800], [622, 304], [0, .06, .2, .99], 60) 365 | animate.add("opacity", "Demo", 0, 1, [.57, .06, .61, .86], 60) 366 | animate.add("size", "Demo", [36, 32], [156, 32], [0, .99, .47, 1], 30, timeoffset=1.5, callback=show_buttons) 367 | animate.add("size", "Demo", [156, 32], [156, 170], [0, .65, .59, .92], 30, timeoffset=2) 368 | animate.add("position", "Demo", [622, 304], [562, 304], [0, .99, .47, 1], 30, timeoffset=1.5) 369 | animate.add("position", "Demo", [562, 304], [562, 225], [0, .65, .59, .92], 30, timeoffset=2) 370 | 371 | dpg.setup_dearpygui() 372 | dpg.show_viewport() 373 | while dpg.is_dearpygui_running(): 374 | onUpdate() 375 | dpg.render_dearpygui_frame() 376 | dpg.destroy_context() 377 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Martin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **DearPyGui_Animate** is an add-on written on top of [DearPyGUI](https://github.com/hoffstadt/DearPyGui) to make UI animations possible. 2 | 3 | Updated and tested for DearPyGui 1.8.0 thanks to [IvanNazaruk](https://github.com/IvanNazaruk). 4 | 5 | 6 | 7 | --- 8 | 9 | **Features:** 10 | * add, delay, pause, continue, loop, remove animations 11 | * get various animation data for best flow control 12 | * animations are bezier driven to support every individual easing (see https://cubic-bezier.com/) 13 | * partial animations will add up to one global animation 14 | * support for callbacks when animation starts, as well as when animation ends 15 | * support for position, size and opacity 16 | 17 | --- 18 | 19 | 20 | **Setup:** 21 | 22 | ```python 23 | import dearpygui.dearpygui as dpg 24 | import dearpygui_animate as animate 25 | 26 | dpg.create_context() 27 | dpg.create_viewport(title="dearpygui_animate D E M O", width=1280, height=720) 28 | 29 | with dpg.window(label="Demo", tag="Demo", width=200, height=100): 30 | dpg.add_text("Hello World!") 31 | 32 | animate.add("position", "Demo", [622, 800], [622, 304], [0, .06, .2, .99], 60) 33 | animate.add("opacity", "Demo", 0, 1, [.57, .06, .61, .86], 60) 34 | 35 | dpg.setup_dearpygui() 36 | dpg.show_viewport() 37 | while dpg.is_dearpygui_running(): 38 | animate.run() 39 | dpg.render_dearpygui_frame() 40 | dpg.destroy_context() 41 | 42 | ``` 43 | 44 | --- 45 | 46 | **Usage:** 47 | 48 | Please see the [Demo](https://github.com/mrtnRitter/DearPyGui_Animate/blob/main/DearPyGui_Animate/dearpygui_animate_demo.py) for some examples of how DearPyGui_Animate can be used. 49 | 50 | --- 51 | 52 | **API:** 53 | 54 | See [Wiki](https://github.com/mrtnRitter/DearPyGui_Animate/wiki) 55 | 56 | --- 57 | 58 | **Known limitations:** 59 | 60 | actual minimum size for windows is 32x32 61 | > windows cannot be smaller than this, but dearpygui_animate will handle smaller values ([0,0] will be translated to [32,32] automatically) 62 | 63 | actual minimum size for items is 1x1 (tested for buttons only!) 64 | > items cannot be smaller than this, but dearpygui_animate will handle smaller values ([0,0] will be translated to [1,1] automatically) 65 | 66 | --- 67 | **Bugs:** 68 | 69 | If there is a theme that is attached not only to a changeable/animated object, then other objects will change their values. 70 | To reproduce: 71 | 72 | ```python 73 | import dearpygui.dearpygui as dpg 74 | 75 | import dearpygui_animate as animate 76 | 77 | dpg.create_context() 78 | dpg.create_viewport() 79 | 80 | with dpg.theme() as button_theme: 81 | with dpg.theme_component(dpg.mvButton): 82 | dpg.add_theme_color(dpg.mvThemeCol_Text, (0, 255, 0, 255), category=dpg.mvThemeCat_Core) 83 | 84 | with dpg.window(): 85 | btn1 = dpg.add_button(label="Test 1") 86 | btn2 = dpg.add_button(label="Test 2") 87 | 88 | dpg.bind_item_theme(btn1, button_theme) 89 | dpg.bind_item_theme(btn2, button_theme) 90 | 91 | animate.add("opacity", btn1, 0, 1, [.57, .06, .61, .86], 60, loop="ping-pong") 92 | 93 | dpg.setup_dearpygui() 94 | dpg.show_viewport() 95 | while dpg.is_dearpygui_running(): 96 | animate.run() 97 | dpg.render_dearpygui_frame() 98 | dpg.destroy_context() 99 | ``` 100 | ![0](https://user-images.githubusercontent.com/46572469/216398135-8d8ab4a8-2d07-4cb7-9b6c-a0766fe8f52b.gif) 101 | 102 | #### The workaround is to wrap each future animated object into a group: 103 | ```python 104 | import dearpygui.dearpygui as dpg 105 | 106 | import dearpygui_animate as animate 107 | 108 | dpg.create_context() 109 | dpg.create_viewport() 110 | 111 | with dpg.theme() as button_theme: 112 | with dpg.theme_component(dpg.mvButton): 113 | dpg.add_theme_color(dpg.mvThemeCol_Text, (0, 255, 0, 255), category=dpg.mvThemeCat_Core) 114 | 115 | with dpg.window(): 116 | with dpg.group() as btn1_group: 117 | btn1 = dpg.add_button(label="Test 1") 118 | btn2 = dpg.add_button(label="Test 2") 119 | 120 | dpg.bind_item_theme(btn1, button_theme) 121 | dpg.bind_item_theme(btn2, button_theme) 122 | 123 | animate.add("opacity", btn1_group, 0, 1, [.57, .06, .61, .86], 60, loop="ping-pong") 124 | 125 | dpg.setup_dearpygui() 126 | dpg.show_viewport() 127 | while dpg.is_dearpygui_running(): 128 | animate.run() 129 | dpg.render_dearpygui_frame() 130 | dpg.destroy_context() 131 | ``` 132 | ![0](https://user-images.githubusercontent.com/46572469/216398380-faae36b5-6cbc-455f-a0f8-0890db0d8347.gif) 133 | 134 | 135 | Unfortunately for `dpg.window()` you need to create a new theme 136 | 137 | 138 | --- 139 | 140 | *DearPyGUI_Animate* is licensed under the [MIT License](https://github.com/hoffstadt/DearPyGui/blob/master/LICENSE). 141 | --------------------------------------------------------------------------------