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.


    How to make a ui.TextView always show the last line?

    Pythonista
    2
    3
    3338
    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.
    • dgelessus
      dgelessus last edited by

      I'm trying to create a text output using the ui module that functions similarly to the built-in console in that it always jumps to the last line of text when new text is added. Below is my current code. However the way it is done currently the TextView does not jump to the last line, instead it ends up at an arbitrary height near the end. Upon closer examination it turned out that the TextView's content_size doesn't contain the correct size and seems to be fluctuating whenever text is added. Am I doing something wrong here, or is this a bug?

      # -*- coding: utf-8 -*-
      
      import ui
      
      PLACEHOLDER_TEXT = """First, we create a View. This is the base class for pretty much everything that you can put on the screen. A vanilla View is just a colored rectangle, but it can also serve as a container for other views (in this case, a button).
      We set the view’s name to ‘Demo’, this is what will be shown in the title bar, when the view is presented later.
      The view’s background color is set to 'white'. You can use strings (color names and hex), tuples (e.g. (1.0, 0.0, 0.0) for red, (0.0, 1.0, 0.0, 0.5) for half-transparent green), or numbers (e.g. 0.5 for 50% gray). Internally, all these color representations are converted to 4-tuples (r, g, b, a), so when you access the view’s background_color attribute later, you’ll get (1.0, 1.0, 1.0, 1.0), no matter if you set it to 'white', '#ffffff', or just 1.0.
      We create a new Button, setting its title with a keyword argument. When you set a title during initialization, its size is automatically adjusted to fit.
      By setting the View.center attribute, we set the button’s position relative to its parent. In this case, we use half of the parent’s View.width and View.height attributes to center the button.
      Because the view may change its size (e.g. when the device is rotated), we set the button’s View.flex attribute that controls its auto-resizing behavior. In this case, we want the left, right, top, and bottom margins to be flexible, so that the button stays centered in its container.
      We set the button’s Button.action attribute to the function that is defined at the top of the script. The function must accept a single parameter, which is conventionally named sender. The parameter will be the button that triggered the event, so you can use a single action for multiple buttons, and determine what to do depending on which button caused the event.
      We’re done setting up the button, so we add it as a child of the container view, using the View.add_subview() method.
      Finally, we call the View.present() method to get the main view on screen. Views can be presented with different styles. On the iPhone, all views are presented in full-screen, but on the iPad, you can choose between 'sheet', 'popover' and 'fullscreen'.
      """
      
      class CustomIO(object):
          root = None
          txts = None
          inp = None
          out = None
          out_str = ""
          
          def __init__(self):
              # set up root view
              self.root = ui.View(name="CustomIO", flex="WH")
              
              # set up text area wrapper view
              self.txts = ui.View(flex="WH")
              self.root.add_subview(self.txts)
              
              # set up input text field
              self.inp = ui.TextField(flex="WT")
              self.txts.add_subview(self.inp)
              self.inp.height = 32
              self.inp.y = self.inp.superview.height - (self.inp.height + 14)
              self.inp.bordered = False
              
              # set up output text view
              self.out = ui.TextView()
              self.txts.add_subview(self.out)
              self.out.height = self.out.superview.height - (self.inp.height + 14)
              self.out.flex = "WH"
              self.out.editable = False
              
              # set up common text area settings
              self.out.delegate = self.inp.delegate = self
          
          def run(self):
              self.root.present("panel")
              self.inp.begin_editing()
          
          # output stream methods
          softspace = 0
          
          def flush(self):
              self.out.text = self.out_str
              
              # automatically scroll output to last line
              self.out.content_offset = (0, self.out.content_size[1] - self.out.height) # this is the bugged line
          
          def write(self, data):
              self.out_str += data
          
          def writelines(self, lines):
              for line in lines:
                  self.write(line + "\n")
          
          # ui.TextField.delegate methods
          def textfield_did_begin_editing(self, textfield):
              if textfield == self.inp:
                  # adjust text field not to be covered by onscreen keyboard
                  def _set_frame():
                      kbf = ui.get_keyboard_frame()
                      if kbf[1] > 0:
                          self.txts.height = self.root.height - kbf[3]
                  ui.delay(_set_frame, 0.55) # wait for the keyboard to slide up
          
          def textfield_should_return(self, textfield):
              self.write(PLACEHOLDER_TEXT)
              self.flush()
              return True
      
      if __name__ == "__main__":
          global io
          
          io = CustomIO()
          io.run()
      
      1 Reply Last reply Reply Quote 0
      • JonB
        JonB last edited by

        It would appear that the content_offset is basically the offset to the top of the scroll bar, while content_size measures to the bottom.

        The following i your main class may help debug this:

            def scrollview_did_scroll(self,scrollview):
                import console
                console.hud_alert('{}/{}'.format(scrollview.content_offset[1],scrollview.content_size[1])
        
        1 Reply Last reply Reply Quote 0
        • JonB
          JonB last edited by

          The problem I think is that your flush() is inside the ui loop, nude red events maybe are getting things out of whack, like the content size perhaps doesn't get updated until after you leave the loop? I found a delay works, as does in_background. Replace your original set to this.

                  @ui.in_background
                  def scroll():
                      self.out.content_offset = (0, self.out.content_size[1] -self.out.height) # this is the bugged line
                  scroll()
          
          1 Reply Last reply Reply Quote 0
          • First post
            Last post
          Powered by NodeBB Forums | Contributors