Scripter - Pythonista UI animation framework built on ui.View.update()
-
Note: The usage and API has changed a bit since this first post. Please check the README.md in the repository for latest docs.
Single-file, no-dependencies, no-surprises UI animation framework powered by ui.View.update, that enables writing intuitive animation scripts using generators:
@script def sample(self): self.move_to(50, 200) self.pulse('red') yield self.hide()
The above script, when called as a function, will at the same time move the view and pulse the background in red, then hide it after the previous actions are complete. Scripter supports an arbitrarily complex combination of sections separated by yields, and calling several sub-scripts to get the combo you need, all coded in an intuitive and reliable way.
Check the repository or download the scripter.py file and run it to see a demo featuring showing and hiding, movement, color changes, counting, rotation, controlling several views simultaneously, animation easing functions, interaction with custom ui.View.draw, as well as pausing or cancelling the animation.
Checking the demo script code is probably the best way to get started, as real documentation is still on the todo list.
Features:
- Inherits ui.View, so you can enable it by inheriting Scripter to use all effects easily as in the example above.
- Or use as an animation control center by creating a Scripter instance, then presenting it or placing it as a hidden subview somewhere. All effects support providing the target view as a parameter.
- Animation primitives:
set_value
(typically for arange
)slide_value
timer
- Animation effects:
hide
,show
move_to
slide_color
pulse
(a color)rotate
- ... more to come, feature requests welcome
- Cancel all animations or a specific script
- Pause and continue all animations
yield 'wait'
- shorthand for pausing for the default duration (0.5 seconds), often needed to make animations feel more naturalslide_value
accepts anease_func
parameter, for even more natural-feeling animations. Check the curves to pick the right function for the occasion.
Note: As of Sep 15, 2017, ui.View.update is only available in Pythonista 3 beta.
-
current_func = lambda s,t,d:s+curve_sinodial(t)*d should work (I am outside and I will post the proper working code later.)
-
@enceladus, thanks! Need to test this, but looks like all of the functions there are directly useable with Scripter.
-
So yes, you can use these with any of the
slide_x
scripts for example:import scene_drawing slide_value(view, 'y', 100, ease_func=scene_drawing.curve_bounce_out)
I could not find the functions documented anywhere, so here’s a reference:
-
Here is a simple demo example that I used for testing. I hope it is useful.
from scripter import * import ui import scene_drawing @script def custom_action(my_view): move(my_view, 200, 200) pulse(my_view, 'red') yield 1 hide(my_view) @script def custom_reverse_action(my_view): show(my_view) yield move(my_view, 100, 20) pulse(my_view, 'black') toggle = True def button_action(sender): global toggle s = sender.superview ease_func = scene_drawing.curve_sinodial #ease_func = scene_drawing.curve_ease_in #ease_func = scene_drawing.curve_ease_out #ease_func = scene_drawing.curve_ease_back_in #ease_func = scene_drawing.curve_ease_back_out l = s['label1'] title = sender.title if title == 'move': if toggle: move(l, 200, 200, ease_func=ease_func) else: move(l, 100,20, ease_func=ease_func) elif title == 'hide': if toggle: hide(l, ease_func=ease_func) else: show(l, ease_func=ease_func) elif title == 'rotate': if toggle: rotate(l, 30, ease_func=ease_func) else: rotate(l, 0, ease_func=ease_func) elif title == 'custom': if toggle: custom_action(l) else: custom_reverse_action(l) elif title == 'color': if toggle: slide_color(l, 'text_color', 'green') else: slide_color(l, 'text_color', 'black') elif title == 'count': if toggle: set_value(l, 'text', range(1,101), lambda c: f'count: {c}') else: set_value(l, 'text', range(100, 0, -1), lambda c: 'Text to be animated' if c == 1 else f'count: {c}') elif title == 'fly_out': if toggle: fly_out(l, 'down') else: move(l, 100, 20) elif title == 'font_sz': if toggle: slide_value(l, 'font', 40, start_value=20, map_func=lambda sz: ('Helvetica', sz) ) else: slide_value(l, 'font', 20, start_value=40, map_func=lambda sz: ('Helvetica', sz) ) toggle = not toggle v = ui.View(frame=(0,0,400,400)) l = ui.Label(text='Text to be animated', font=('Helvetica', 20), name='label1', frame=(100,20,200,100)) b1 = ui.Button(title='move', frame=(20, 300, 80,50), action=button_action) b2 = ui.Button(title='hide', frame=(120, 300, 80,50), action=button_action) b3 = ui.Button(title='rotate', frame=(220, 300, 80,50), action=button_action) b4 = ui.Button(title='custom', frame=(320, 300, 80,50), action=button_action) b5 = ui.Button(title='color', frame=(20, 350, 80,50), action=button_action) b6 = ui.Button(title='count', frame=(120, 350, 80,50), action=button_action) b7 = ui.Button(title='fly_out', frame=(220, 350, 80,50), action=button_action) b8 = ui.Button(title='font_sz', frame=(320, 350, 80,50), action=button_action) v.add_subview(l) v.add_subview(b1) v.add_subview(b2) v.add_subview(b3) v.add_subview(b4) v.add_subview(b5) v.add_subview(b6) v.add_subview(b7) v.add_subview(b8) v.present('sheet')
-
@enceladus, thanks again. Looks like something that I was thinking of doing, splitting out the demo from the main code, and having buttons to launch specific effects. Would you mind if I developed this further and included it in the repo?
-
Fell free to use whatever way you want. Anyway it is mostly your code.
-
@enceladus, in your
custom_action
, you are yielding 1 (second), which is not doing what it intuitively should, i.e. wait for 1 second after the previous actions have completed. Currently you need to have ayield
followed by theyield 1
.This confirms the misgivings I have had about the
yield wait
syntax. I would want it to work the way you used it.
-
In the latest version
yield
now works as you would expect, i.e.:move(view, 100, 100) yield 2 pulse(view)
Also, scene_drawing easing functions are wrapped, so you can use them without separately importing them.
-
ok. Thanks.
-
This is way cool @mikael and really good extension @enceladus. Whenever I can spare the time I will try to extend it into a basic universal animation studio on all platforms. Sounds really challenging though but isn’t challenge the cradle of creativity?.
-
Check the first post for an image of scripter-demo.py. This additional script fulfills several roles:
- My testbed for checking that everything works
- Demo of all effects (click ”All”)
- ”Animation studio” where you can tweak the duration and easing functions to get the right effect
”Colors” demonstrates running an effect continuously and canceling it.
Code for the effects is shown as they are run, and can be easily copy-pasted elsewhere.
Tweaks to the core functionality include:
- Ending a script and starting the next one happen in the same call to
update
, removing any stutter caused by a no-op last call to a script. - Added some more effects like
rotate_by
to have feature parity with Scene animations - Added some more effects like
wobble
just for fun - Added some more easing functions - check the reference for the 21 alternatives.
-
Added a not-at-all-essential
roll_to
effect that takes into account the distance to be traveled and rotates the view an appropriate amount at the same time to make it look natural. Showcased below as a menu reveal effect.Also added Vector in the scripter.py, as it is often convenient for effects.