omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    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.


    Polling from a ui.View (built in timers in ui.Views)

    Pythonista
    timer ui.view
    6
    40
    25311
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Phuket2
      Phuket2 last edited by

      @omz, did you ever go further with the idea of a ui.View having its own timer that you could hook into to do updates etc. ? Personally, I think I would be a great advance for ui.View or Custom ui.Views. I realise there are different ways to do it already. But it's so easy to get it wrong, well at least for me. I am sure you could implement it many ways. For me, I think the best would be a callback same as draw in subclassed ui.Views. Like the draw method, if a timer method is not defined then no additional overhead or speed issues would impact the class. Also some way to set the interval of the callback as well as a way to able to suspend/activate the timer callbacks. I think it could be more flexible if it did not automatically suspend if it's not the foremost view. Eg, you may have a view/s as an overlay/s with the alpha set so it's transparent, but you want to still want it to execute the code in the timer callback method.
      Anyway, I think this would be super helpful. Not sure what others think.

      mikael 1 Reply Last reply Reply Quote 0
      • mikael
        mikael last edited by

        Working with the tools we have, here's a decorator using ui.delay:

        from ui import *
        from functools import partial
        
        def poll(interval):
          def interval_decorator(func):
            def func_wrapper(self, *args, **kwargs):
              with_args = partial(wrapped_func, self, *args, **kwargs)
              delay(with_args, interval)
              return func(self, *args, **kwargs)
            wrapped_func = func_wrapper
            return func_wrapper
          return interval_decorator
        

        Which can be used like this:

        class CustomView(View):
          
          @poll(1)
          def get_text(self, name):
            print("Hello " + name)
            
          def will_close(self):
            cancel_delays()
        
        if __name__ == '__main__':
          v = CustomView()
          v.background_color = 'white'
          v.present('sheet')
        
          v.get_text('John')
        

        ... but as with anything using ui.delay, this is unstable and keeps crashing my Pythonista.

        Phuket2 1 Reply Last reply Reply Quote 0
        • Phuket2
          Phuket2 @mikael last edited by

          @mikael , exactly my point. Not easy to do. Also cancel delays is global in that it cancels all the queue. @omz mentioned before it might be a good idea for him to add something like this to a view. He Did not talk about the implementation. But of course he is in the best position to implement this type of callback that does not break. I have my fingers crossed he will do something 🤑

          1 Reply Last reply Reply Quote 0
          • omz
            omz last edited by

            @Phuket2 I still think it might be a good idea, but I haven't found the time to actually work on this yet.

            1 Reply Last reply Reply Quote 1
            • mikael
              mikael last edited by

              While we are waiting, the version below:

              • does not use cancel_delays
              • does not keep crashing
              • does not need the View to be the root view (i.e. the one that is presented)
              • will go dormant when off screen
              • can be turned on and off per view by setting polling attribute True or False

              I think I will turn this into a Composite component, with the following planned features:

              • can be applied to any view (not just ui.Views)
              • usage is easier - just define the polling methods
              • support UI thread or background execution
              • support chaining of these methods, as many animations need something like that

              Updated code, sorry for the dump, but it is not awfully long:

              #coding: utf-8
              from ui import *
              from functools import partial
              
              def poll(interval):
                def interval_decorator(func):
                  def func_wrapper(self, *args, **kwargs):
                    if self.polling and isinstance(self, View) and self.on_screen:
                      with_args = partial(wrapped_func, self, *args, **kwargs)
                      delay(with_args, interval)
                      return func(self, *args, **kwargs)
                    else:
                      self._polling = False
                  wrapped_func = func_wrapper
                  return func_wrapper
                return interval_decorator
              
              class CustomView(View):
                
                def __init__(self, *args, **kwargs):
                  super().__init__(*args, **kwargs)
                  self._polling = False
                
                @poll(1)
                def get_text(self, name):
                  print("Hello " + name)
                  
                def will_close(self):
                  self.polling = False
                  
                @property
                def polling(self):
                  return self._polling
                  
                @polling.setter
                def polling(self, value):
                  was_polling = self._polling
                  self._polling = value
                  if not was_polling and self._polling:
                    self.get_text('John')
                  
              if __name__ == '__main__':
                v = View()
                v.background_color = 'white'
                v.present('sheet')
              
                c = CustomView()
                v.add_subview(c)
              
                c.polling = True
              
              1 Reply Last reply Reply Quote 0
              • enceladus
                enceladus last edited by

                May be just use scene timer.
                https://gist.github.com/f229353624df386c1beffb864dc2cce0

                import ui, scene
                
                class TimerView(ui.View):
                    class TimerScene(scene.Scene):
                        def update(self):
                            self.view.superview.update()
                            
                    def create_sceneview(self):
                        scene_view = scene.SceneView()
                        scene_view.width = 0
                        scene_view.height = 0
                        scene_view.frame_interval = self.frame_interval
                        scene_view.scene = TimerView.TimerScene()
                        return scene_view
                            
                    def __init__(self, *args, **kwargs):
                        super().__init__(*args, **kwargs)
                        self.frame_interval = kwargs.get('frame_interval', 1)
                        self.add_subview(self.create_sceneview())
                    
                    @property    
                    def start_time(self):
                        return self.subviews[0].scene.t
                        
                    def draw(self):
                        pass
                        
                    def update(self):  
                        self.set_needs_display()
                        
                if __name__ == '__main__':
                    from time import localtime
                    class DigitalClock(TimerView):
                        def __init__(self, *args, **kwargs):
                            super().__init__(*args, **kwargs)
                        def draw(self):
                            t = localtime()
                            ui.draw_string("{:02}:{:02}:{:02}".format(
                                t.tm_hour, t.tm_min, t.tm_sec),
                                font=('Helvetica', 20),
                                rect=(100, 100,0,0),
                                alignment=ui.ALIGN_CENTER)
                        
                    v = DigitalClock(frame=(0,0,300, 300))
                    v.present('sheet')
                
                
                1 Reply Last reply Reply Quote 0
                • Phuket2
                  Phuket2 last edited by

                  Guys,
                  thanks for your code posts. I will give both versions a try out a little later today. Also thanks for your reply @omz.

                  1 Reply Last reply Reply Quote 0
                  • enceladus
                    enceladus last edited by enceladus

                    You can also use async module timer

                    import ui
                    import asyncio
                    from time import localtime
                    
                    class DigitalClock(ui.View):
                        def __init__(self, *args, **kwargs):
                            super().__init__(*args, **kwargs)
                            
                        def draw(self):
                            t = localtime()
                            ui.draw_string("{:02}:{:02}:{:02}".format(
                                t.tm_hour, t.tm_min, t.tm_sec),
                                font=('Helvetica', 20),
                                rect=(100, 100,0,0),
                                alignment=ui.ALIGN_CENTER)
                                
                        def update(self, event_loop):
                            self.set_needs_display()
                            event_loop.call_later(.5, self.update, event_loop)
                        
                    v = DigitalClock(frame=(0,0,300, 300), frame_interval=10)
                    v.present('sheet')
                    
                    event_loop = asyncio.get_event_loop()
                    event_loop.call_soon(v.update, event_loop)
                    event_loop.run_forever()
                    
                    
                    
                    
                    

                    Stop watch example (use the stopwatch.pyui from gist link in previous post)

                    import ui
                    import asyncio
                    
                    class StopWatch(ui.View):
                        def __init__(self, *args, **kwargs):
                            super().__init__(*args, **kwargs)
                            self.value = 0
                            self.state = 'stop'
                            
                        def draw(self):
                            t0 = (self.value//(600*60), self.value//600, self.value//10)
                            t1 = (t0[0], t0[1]%60, t0[2]%60)
                            ui.draw_string("{:02}:{:02}:{:02}".format(*t1),
                                font=('Helvetica', 20),
                                rect=(150, 0, 0, 0),
                                color='black',
                                alignment=ui.ALIGN_CENTER)
                            
                        def update(self, event_loop):
                            if self.state == 'run':
                                self.value += 1
                            self.set_needs_display()
                            event_loop.call_later(.1, self.update, event_loop)
                    
                    def button_action(sender):
                        v1 = sender.superview['view1']    
                        if sender.title == 'Reset':
                            v1.value = 0
                            v1.state = 'stop'
                        elif sender.title == 'Start':
                            v1.value = 0
                            v1.state = 'run'
                        elif sender.title == 'Stop':
                            v1.state = 'stop'
                        
                       
                    v = ui.load_view()
                    v.present('sheet') 
                    
                    event_loop = asyncio.get_event_loop()
                    event_loop.call_soon(v['view1'].update, event_loop)
                    event_loop.run_forever()       
                    
                    
                    
                    Phuket2 2 Replies Last reply Reply Quote 2
                    • Phuket2
                      Phuket2 @enceladus last edited by

                      This post is deleted!
                      1 Reply Last reply Reply Quote 0
                      • Phuket2
                        Phuket2 @enceladus last edited by

                        @enceladus , they thanks for your code samples also.

                        I modified your update method in the first example. Just quickly, so it would stop after closing the window. Guessing more checks should be done.

                        def update(self, event_loop):
                                if not self.on_screen:
                                    event_loop.stop()
                                    return
                                    
                                self.set_needs_display()
                                event_loop.call_later(.5, self.update, event_loop)
                        

                        According to the docs the BaseEventLoop class is not thread safe. I am not good enough to under the implications/restrictions of this both as a standalone as well how it would interact with Pythonista's Threads. Any insights would be appreciated.
                        Again, for me at least is just to have a reliable and controllable way to easily add a way to call a so called poll/update method on a class, including a ui.View async.

                        Out of the above implementations again I am not good enough to say which is the best implementation. Appears asyncio is py3+ only. Which is fine by me, other might be looking for a 2.7 solution also.

                        1 Reply Last reply Reply Quote 0
                        • enceladus
                          enceladus last edited by

                          I think that calls in event loop are run on the same thread. Button action (in stop watch example) should be put in the same event loop so that there are no thread-safety issues. As you have mentioned there could be other issues. (My experience in asyncio module is very limited and I am in learning mode.)

                          def button_action(sender):
                              event_loop.call_soon(button_action_event_loop, sender)
                              
                          def button_action_event_loop(sender):
                              v1 = sender.superview['view1']    
                              if sender.title == 'Reset':
                                  v1.value = 0
                                  v1.state = 'stop'
                              elif sender.title == 'Start':
                                  v1.value = 0
                                  v1.state = 'run'
                              elif sender.title == 'Stop':
                                  v1.state = 'stop'
                          
                          
                          1 Reply Last reply Reply Quote 0
                          • omz
                            omz last edited by

                            @Phuket2 I've added something to the latest beta. From the release notes:

                            In custom ui.View subclasses, you can now implement an update method (no arguments, except for self) that gets called automatically by an internal timer. To make this work, you also have to set the new update_interval attribute. It specifies how often the timer fires (e.g. set to 1.0, the timer fires every second). It defaults to 0.0 (disable update timer) because otherwise this new feature might break existing code that happens to implement a method called update.

                            I hope this works for you.

                            Phuket2 1 Reply Last reply Reply Quote 2
                            • Phuket2
                              Phuket2 @omz last edited by

                              @omz , yup pulled another rabbit out of the hat :) was so excited when I seen the email this morning for the new beta. You have seemed to have done a lot. Just did a quick test with the update method. Appears to work perfectly. Just had a button on a form to start and stop the updates by setting the update interval. Very nice. I will do some more tests with opening Dialogs over the top etc... but many thanks, I think this will make a lot of ppl happy. Ok, back to exploring the new features :)

                              1 Reply Last reply Reply Quote 0
                              • technoway
                                technoway last edited by

                                Is the update_interval, and update, attributes in the latest pythonista?
                                If now, can I get the beta?

                                By the way, it would be nice if the update method was an empty method (just "pass") that already existed, so users could set the update_interval and update method in the subclassed view's init method.

                                And whatever already exists, or is done in the future, thank you for a truly great product!

                                1 Reply Last reply Reply Quote 0
                                • omz
                                  omz last edited by omz

                                  @technoway It's currently only in the beta. With regards to an empty method in the base class, setting the method in __init__ etc., that's not really possible because the act of implementing the method is a signal to change the behavior (hope that makes sense). The entire update timer machinery isn't initialized at all if your View class doesn't implement update.

                                  mikael 1 Reply Last reply Reply Quote 0
                                  • technoway
                                    technoway last edited by

                                    Thank you for the quick reply.

                                    Am I allowed to install the beta version, and if so, where is it?

                                    I am running Pythonista on an iPhone (and iPad too, but I need this particular functionality on my iPhone).

                                    I have purchased both the Python 2.x and the Python 3.x versions of Pythonista for my iPhone.

                                    1 Reply Last reply Reply Quote 0
                                    • technoway
                                      technoway last edited by

                                      Ah, I found the message about sending my apple ID through the e-mail. I'll do that. Thanks again, and sorry for all the noise. (I did search before my last post, but missed the relevant post).

                                      1 Reply Last reply Reply Quote 0
                                      • omz
                                        omz last edited by

                                        @technoway You should have a beta invite in your email (please check your spam folder if it isn't there).

                                        mikael 1 Reply Last reply Reply Quote 0
                                        • mikael
                                          mikael @omz last edited by

                                          @omz, arrgh, spam. I have been patiently waiting for my invite, so long that the spam folder has been purged already. Is it easy for you to resend my invite, or should I just send another request? Sorry.

                                          1 Reply Last reply Reply Quote 0
                                          • omz
                                            omz last edited by

                                            @technoway I sent the invite to the Gmail address you sent me yesterday. Did you just purge your spam folder?

                                            mikael 1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post
                                            Powered by NodeBB Forums | Contributors