Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
ui.Animate wider usage
-
@omz, is it possible to fudge something so ui.animate works a little differently than it does at the moment.
It would be very convenient if you could call ui.animate with a start, finish and increment along with its duration parameter. And it passes back the value on each call to the defined function.
I am pretty sure ui.animate is written in objective c as I could not find it in the ui file.
It may simple to write for you, but not for me. But I think this functionality would be welcomed by many here.
But again, if there is a way to fudge it so I can rely on the same mechanism, that would be great.
-
I tried the below, but it appears you don't update the objects attrs as you do the transition. I just tried to fake it using the alpha value, but it's not updated
yes I see increments is a stupid argument now duration defines the incrementsdef animate(self, sender = None): print 'in animate...' def animation(): self.alpha = 0 # fade out self.value = self.alpha ui.animate(animation, duration=2)
-
Could you give me a concrete example of an effect you're trying to achieve?
-
@omz , just trying to animate/draw the class below. Meaning just set the value from 0.0 to 1.0 for the duration specified. I have tried this with a method with @ui.in_background. I got some strange results. I am sure it was just me doing something stupid, but just seems like the mechanism is already there.
class CircleProgress(ui.View): def __init__(self, *args , **kwargs): self.min = 0. self.max = 1.0 self.v = 0. self.increments = .01 self.margin = (0,0,0,0) self.drawing_on = True self.alpha = 1 self.fill_color = 'red' self.stroke_color = 'black' self.font =('Arial Rounded MT Bold', 18) self.label_on = True self.set_attrs(**kwargs) btn = ui.Button(name = 'inc') btn.action = self.animate btn.width = self.width btn.height = self.height btn.flex = 'wh' self.add_subview(btn) btn.bring_to_front() def inc(self, sender = None): self.value += self.increments if self.value > self.max: self.value = self.min self.set_needs_display() def dec(self): self.value -= self.increments if self.value < self.min: self.value = self.max self.set_needs_display() def value(self, v): if v < self.min or v > self.max: return self.value = v self.set_needs_display() def draw(self): if not self.drawing_on : return cr = ui.Rect(*self.bounds).inset(*self.margin) p = max(0.0, min(1.0, self.value)) r = cr.width / 2 path = ui.Path.oval(*cr) path.line_width = .5 ui.set_color(self.stroke_color) path.stroke() center = ui.Point(r, r) path.move_to(center.x, center.y) start = radians(-90) end = start + p * radians(360) path.add_arc(r, r, r, start, end) path.close() ui.set_color(self.fill_color) path.eo_fill_rule = True path.fill() self.draw_label() def draw_label(self): if not self.drawing_on : return if not self.label_on: return cr = ui.Rect(*self.bounds) ui.set_color('purple') s = str('{:.0%}'.format(self.v)) dim = ui.measure_string(s, max_width=cr.width, font=self.font, alignment=ui.ALIGN_CENTER, line_break_mode=ui.LB_TRUNCATE_TAIL) lb_rect = ui.Rect(0,0, dim[0], dim[1]) lb_rect.center(cr.center()) ui.draw_string(s, lb_rect , font=self.font, color='black', alignment=ui.ALIGN_CENTER, line_break_mode=ui.LB_TRUNCATE_TAIL) def set_attrs(self, **kwargs): for k,v in kwargs.iteritems(): if hasattr(self, k): setattr(self, k, v) def animate(self, sender = None): print 'in animate...' def animation(): self.alpha = 0 # fade out self.value = self.alpha self.set_needs_display() ui.animate(animation, duration=2)
-
@omz this also relates. In Pythonista startup I have
from objc_util import * UIView.beginAnimations_(None) UIView.setAnimationDuration_(0)
From you.
It also stops ui.animate() animating , which makes sense. When I am testing now, i comment out these lines And shut down Pythonista , and restart. Is it possible to easily reverse this in code. I tried to pass UIView.beginAnimations_(None) 1 instead of None. Just crashed.
Again, just useful for testing. I prefer to have the Animations off most of the time. But if inside a .py file I can turn them on, then off again, that would be great.
-
UIView.commitAnimations()
will close out the beginAnimations and effectively undo the duration of none.
Your above code seems to mix value as a method, and value as a variable, no? Perhaps you are missing a property decorator?
Also, not sure you need or should put a set needs display inside the animation function. I think that would only be executed once. I think you want to set heeds display when the value changes, but no other times. If you are trying to animate just the alpha, then what you have works. If you are trying to animate changing value, this approach will not work, you will need to have your own thread, or call ui.delay which calls itself, etc. animate can only animate changes to properties, such as frame, colors, transforms, basically things that ios knows how to draw and knows how to interpolate. iOS does not onow how to interpolate a custom draw function.
-
As @JonB already pointed out,
ui.animate
isn't really suitable for animating custom attributes/drawing.You can get the effect you're looking for by combining
ui.animate
(for fading in/out) withui.delay
(for changing the progress and redrawing). I posted an example of a simple animation usingui.delay
in this thread (not exactly what you want, but the technique would be similar). -
@JonB , thanks. Yes I had changed the value property aend realized I need the set_needs_display() in the animate method. I had just been changing things around
-
-
@JonB , I know this question sort of seems stupid. But I was looking up flex on the forum. I have never really felt in control of it. Anyway, I remembered the animation you did a long time ago this_post
But if you had wanted to draw out realtime b.x, b.y, b.height , b.width for example this would be have been impossible as far as I can see. Using this method that is.
Look it's ok, I accept the way it is. Just my use case did not really give a clear idea about the possible uses.But anyone reading this and have difficulties getting your head around flex, @JonB ' s code post at the link is visual and very helpful
-
@omz , @JonB or @anyoneelse.
I did the below just using ui.Delay based on the conversations above. Again, it's simple, but often the handiest things are.So it's just a class trying to give a framework for a callback over a duration with the number range 0.0 to 1.0. Just trying to emulate part of ui.animate. The time for me is the tricky thing. But what I have done seems to accurate to amount the 100ths of a second.
Just overall I don't know if the approach is a good one or not. Maybe there are some really bad flaws in this approach. Any feedback welcome, scathing feedback is fine.
It's a little long for posting. I thought about a gist, but thought I have a better chance to get feedback if it's listed here
import ui import time, warnings class CallMeBack(object): ''' CLASS - CallMeBack Purpose to call a user supplied function 100 times with simlar units of elapased time between calls with a number between 0.0 and 1.0 for the passed duration. This is an attempt to mimick the part of @omz's animate function, that you can set and forget to get a series of numbers between 0 - 1.0 over a specfic duration without binding to any attributes. just passes the value to the supplied function/method its only work in progress. i am certin will have to refactor. i am guessing i will need to have some @classmethod decorators to make it as flexible as @omz's ui.animate. this is at least a start. A pause method? maybe does not make sense ''' def __init__(self, func, duration = 1.0, begin = True, on_completion = None, obj_ref = None): self.iterations = 0 # a counter of the num of iterations done self.duration = duration # the time to spead 100 callbacks over self.time_unit = 0 # 1/100th of the duration self._value = 0 # current value self.func = func # the callers func that is called # if supplied, calls this function after the 100 iterations self.on_completion = on_completion self.start_time = 0 # is set once the start method is invoked self.finish_time = 0 # used for debug. measure how long we took self.working = False # a flag, to protect from being called # whilst running. we are not reenterant # so its possible to store a reference to a object, which you can # recover in the callback function self.obj_ref = obj_ref # start from init with begin = True *arg if begin: self.start() def start(self, duration = None): # the timing and process start method. can be called from __init__ # or here externally # aviod being called while running. if self.working: warnings.warn('CallMeBack cannot be called whilst it is running.') return # block being called again until we finish self.working = True if duration: self.duration = duration self.start_time = time.time() self.time_unit = self.duration / 100. # would be nice to have exp, log etc... ui.delay(self._work_linear(), 0) def _work_linear(self): # the method that is called 100 times at evenlyish time intervals # which in turns calls the callers function with numbers 0.0 to 1.0 # i know, this can be better. for now its ok to spell it out _expected_time = self.iterations * self.time_unit _real_time =time.time() - self.start_time _diff = _expected_time - _real_time if self.iterations == 100: # hmmm, call last time self.func(self, 1.0) # we are finished self.finish_time = time.time() ui.cancel_delays() print 'duration {}, total time {}, difference {}'.format(self.duration , self.finish_time - self.start_time, (self.finish_time - self.start_time) - self.duration) # if a completion routine has been defined, is called here if self.on_completion: self.on_completion(self) # reset some values, so calling multiple times work self.reset() self.working = False return # call the callers function with ref to this object and the current # value self._value = self.iterations / 100. self.func(self, self._value) self.iterations += 1 # call ui.delay with .95% of the time unit we have. # this seems ok at the moment. if a lot of processing happens # in the callers function, it will start to fall behind # hofully _diff counteracts it ui.delay(self._work_linear, (self.time_unit ) + _diff) @property def value(self): return self._value @property def finished(self): return self.working def reset(self): # reset some vars, in the event we are started again self.iterations = 0 self._value = 0 self.working = False def cancel(self, ignore_completion = False, finish_with = None): # for manual cancelling # finish_with just allows your func to be called a last time # with a value like 1.0 Could provide a more appealling effect ui.cancel_delays() if finish_with: self.func(finish_with) if self.on_completion: if not ignore_completion: self.on_completion(self)
-
Was not sure how the below pattern would work with something real. It worked fine.
def test_animate(self, sender = None): def inc(sender, v): self.value(v) cb = CallMeBack(inc, duration = 5.0) # another version def test_animate(self, sender = None): self.alpha = 0 def inc(sender, v): self.value(v) self.alpha = v cb = CallMeBack(inc, duration = 5.0)