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.


    [Share] Position a ui.control in a view

    Pythonista
    ui.controls share ui.view
    5
    15
    15745
    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

      Lol, here we go again. This is simple code. But very useful when testing.it can be useful beyond that also. But it's very limited and basic. But it's just a base idea or the impetus for a better idea.

      But I think about a parser that could do something like c = br-bc. Meaning Center = bottom right - bottom Center.
      Or c = c + 10L so centred + 10 points to the left. Also could support percentages etc... But the biggest trick of all is to keep it simple. I mean the API.

      I am sure some will say , x,y is simple enough already. Personally I think it's a pain.

      Anyway, still food for thought...

      # Phuket2 , Pythonista Forums (Python profiency, not much)
      # works for python 2 or 3
      import ui
      
      def _make_button(title):
      	btn = ui.Button()
      	btn.width = 80
      	btn.height = 32
      	btn.border_width = .5
      	btn.title = title 
      	return btn
      		
      def do_position(obj, pos_code='', pad = 0):
      	'''
      	do_position:
      		a very simple positioning function for ui elements. as simple 
      		as it is can be useful. especially for testing.   
      		
      		args:
      			1. obj - a ui object Instance such as a ui.Button
      			
      			2. pos_code - 1 to x letters or combinations in the set
      			('c', 't', 'l','b', 'r'). eg 'c' will postion the obj in 
      			the middle of the parent view. 'tc' or 'ct' will position
      			the obj at the top center if the screen.
      			
      			3. pad - is to give a type of margin from the edges.  
      			for t and l pad is added, for b and r pad is subtracted.
      			c is not effected by pad
      		
      		returns: a tuple (boolean, ui.Rect) 
      			if False, exited early, eg, not yet added to a view. 
      			ui.Rect will be set as (0, 0, 0, 0)
      			if True, the ui.Rect is set to the frame of the object. 
      			regardless if it moved or not.  
      			if both cases ui.Rect will be a valid ui.Rect
      	'''
      	
      	# if the control is not added to a view, i.e no superview, we cant 
      	# do much other than return
      	if not obj.superview:
      		return (False, ui.Rect())
      	
      	# we only reference superview.bounds. hopefully this is correct!
      	r = ui.Rect(*obj.superview.bounds)
      	
      	# in the func we only deal with lowercase pos_code. we normalise
      	# the case so upper or lower or a mixture can be used as input
      	pos_code = pos_code.lower()
      	
      	# c for center is a special case. does not know if you want vertical
      	# or horizontal center. we delete c from input if it exists and 
      	# make sure its the first operation. then we dont get side effects
      	if 'c' in pos_code:
      		pos_code = 'c' + pos_code.replace('c', '')
      		
      	for i in range(len(pos_code)):
      		code = pos_code[i]
      		if code is 'c':
      			obj.center = r.center()
      		elif code is 'l':
      			obj.x = r.min_x + pad
      		elif code is 'r':
      			obj.x = r.max_x - (obj.width+pad)
      		elif code is 't':
      			obj.y = r.min_y + pad
      		elif code is 'b':
      			obj.y = r.max_y - (obj.height + pad)
      		elif code is 'r':
      			obj.y = r.max_y - (obj.height + pad)
      		
      	return (True, ui.Rect(*obj.frame))
      
      
      hide_title_bar = False
      style = ''	
      
      w = 800
      h = 600
      
      f = (0, 0, w, h)
      pos_list=['c','tl','tc','tr','lc', 'cr', 'bl', 'bc', 'br']
      v = ui.View(frame = f, bg_color = 'lightyellow')
      v.present(style = style, hide_title_bar = hide_title_bar )
      
      for pos_code in pos_list:
      	btn = _make_button(pos_code)
      	v.add_subview(btn)
      	# do_position, can only be called after its been added to a view.
      	# does need to be like this, but it makes sense to do it this way.
      	do_position(btn, pos_code, 30)
      
      1 Reply Last reply Reply Quote 0
      • cook
        cook last edited by

        @phuket2 I like the idea of this!

        Here's a thought:
        For vertical alignment you have another term you can use:

        Top
        Middle
        Bottom

        Phuket2 1 Reply Last reply Reply Quote 1
        • Phuket2
          Phuket2 @cook last edited by

          @cook , if you pass a single char like 't' or 'b' , the x value is not changed for example. So the object will be positioned at this location without changes it x value. So I am pretty sure you can do as you suggest now.

          Each char in the pos_code is evaluated as a single operation. Center 'c' in the stream of chars is treated differently. If c exists in the stream, it's deleted and append to the front of the stream.
          This is just because of the way I handle c. The way I handle it, c effects both the x and y in a single operation. Without me patching this , the order would become important. Like 'bc' would just result with the object centred instead of it being centred on the bottom.

          Did I understand you correctly? If not please say. I am eager to improve this

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

            elif code is 'r': appears twice in the code above.

            I like @cook approach:

            | tl | tc | tr |
            | ml | mc | mr |
            | bl | bc | br |
            

            The code should first center the control in the view and then:

            pos_code = (pos_code or '').lower()
            if 't' in pos_code:
                move control to top of view
            elif 'b' in pos_code:
                move control to bottom of view
            if 'l' in pos_code:
                move control to left of view
            elif 'r' in pos_code:
                move control to right of view
            

            This means that:

            • pos_code of 'mc', 'c', 'm', 'junk', or even the empty string on None will center the control in X and Y.
            • pos_code of 'R' or 'r" will right justify the control in the view.
            • pos_code of 'BR', 'RB', 'rb', 'r...B', 'rabid' will place the control at the bottom right of the view.
            • 't' takes presidence over 'm' or 'b' if there are multiple in pos_code.
            • 'l' takes presidence over 'c' or 'r' if there are multiple in pos_code.
            Phuket2 1 Reply Last reply Reply Quote 0
            • ccc
              ccc last edited by ccc

              Of course, a pos_code of Black Label will put the control at the bottom left of the view where it belongs.

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

                @ccc , lol, I am going to have at least 3 or 4 more whiskeys before trying to digest your comments 😱😬
                But really

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

                  @ccc , @cook

                  Is the following in line with achieving your suggestions?

                  import ui
                  
                  def _make_button(title):
                  	btn = ui.Button()
                  	btn.width = 80
                  	btn.height = 32
                  	btn.border_width = .5
                  	btn.title = title 
                  	return btn
                  		
                  def do_position(obj, pos_code='', pad = 0):
                  	if not obj.superview:
                  		return (False, ui.Rect())
                  			
                  	# we only reference superview.bounds. hopefully this is correct!
                  	r = ui.Rect(*obj.superview.bounds)
                  	
                  	#obj.center = r.center()
                  	if 'c' in pos_code:
                  		pos_code = 'c' + pos_code.replace('c', '')
                  		
                  	pos_code = (pos_code or '').lower()
                  	for i in range(len(pos_code)):
                  		code = pos_code[i]
                  		if code is 'c':
                  			obj.center = r.center()
                  		elif code is 'l':
                  			obj.x = r.min_x + pad
                  		elif code is 'r':
                  			obj.x = r.max_x - (obj.width+pad)
                  		elif code is 't':
                  			obj.y = r.min_y + pad
                  		elif code is 'b':
                  			obj.y = r.max_y - (obj.height + pad)
                  		elif code is 'm':
                  			obj.y = (r.height / 2) - (obj.height / 2)
                  		
                  	return (True, ui.Rect(*obj.frame))
                  
                  
                  hide_title_bar = False
                  style = ''	
                  w = 800
                  h = 600
                  f = (0, 0, w, h)
                  pos_list=['tl', 'tc', 'tr', 'ml', 'mc', 'rm', 'bl', 'bc', 'br' ]
                  v = ui.View(frame = f, bg_color = 'lightyellow')
                  v.present(style = style, hide_title_bar = hide_title_bar )
                  
                  for pos_code in pos_list:
                  	btn = _make_button(pos_code)
                  	v.add_subview(btn)
                  	# do_position, can only be called after its been added to a view.
                  	# does need to be like this, but it makes sense to do it this way.
                  	do_position(btn, pos_code, 30)
                  
                  1 Reply Last reply Reply Quote 0
                  • ccc
                    ccc last edited by ccc

                    def do_position(obj, pos_code='', pad=0):
                        if not obj.superview:
                            return (False, ui.Rect())
                                
                        # we only reference superview.bounds. hopefully this is correct!
                        r = obj.superview.bounds
                        obj.center = r.center()
                            
                        pos_code = (pos_code or '').lower()
                        if 'l' in pos_code:
                            obj.x = r.min_x + pad
                        elif 'r' in pos_code:
                            obj.x = r.max_x - (obj.width+pad)
                        if 't' in pos_code:
                            obj.y = r.min_y + pad
                        elif 'b' in pos_code:
                            obj.y = r.max_y - (obj.height + pad)
                            
                        return (True, obj.frame)
                    
                    Phuket2 1 Reply Last reply Reply Quote 0
                    • Phuket2
                      Phuket2 @ccc last edited by

                      @ccc , ok. I see what you mean now. I my function with your version and it worked as expected. Shorter code also. But same in functionality (basically).

                      But wouldn't my approach be better suited to exapanding the functionality? I am not making a statement, I am asking. This function is just sort of ok. Mainly good for testing.

                      Maybe this function can't be expanded to be really functional in real world. And I know the problem, sizing is not considered.

                      I have often thought about the grid system used in the old battleship game before for both positioning and sizing objects. Ok, it's just a grid. But maybe it's a better route? Not sure.

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

                        Oh yeah man... The code I gave you was merely focused on the gracefully dealing with garbage input for nine boxes that your question was about.

                        Laying out components in a UI is a massive topic and (like almost all UI-related issues) not a topic that I have any expertise in.

                        You might check out different layout mangers that Java uses to get some ideas: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html The GridBag might be of special interest.

                        I think that Pythonista approach does a nice job of letting you lay things out well without going cross-eyed trying to understand the complexities of layout managers.

                        Build the battleship game and I will kick your butt... You on the beach and me in the mountains.

                        Phuket2 1 Reply Last reply Reply Quote 0
                        • ccc
                          ccc last edited by

                          One last item...

                          for i in range(len(pos_code)):
                                  code = pos_code[i]
                          
                          # becomes...
                          
                          for code in pos_code:
                          
                          1 Reply Last reply Reply Quote 0
                          • Phuket2
                            Phuket2 @ccc last edited by

                            @ccc , I agree. Pythonista gives you a big head start. But it's wanting. But I am sure not because of anything else other than time.
                            But imagine if the wrench menu was available in the designer and there was a Designer Module.
                            Example you want to evenly distribute 3 objects in a view or a subview. At the moment it's a pain (manual job). But if we could run some code from the wrench menu for example with a designer module exposing, selected items, superview etc...making views in the designer could be so easy. That's only one example, but you could do a lot.
                            I do understand, this would be a fairly large under taking for omz. Well, I think. Maybe he is in good shape for this. One thing that gives me a hint is that you can copy , and then paste attrs to another object. Also copying between different UIFiles seems to work properly, to the root or a subview or a subview of a subview.

                            Many battleship games still out there. I am not saying I am great. I just know I loved it as a kid. For our generation it was a lot of fun. It will probably come back into fashion at some point the way the marvel comics have. Who would have thought 😱😬

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

                              @ccc @phuket2 you had a lot going on here with this and I didn't see. Sorry! Ian's original post got me thinking about layout a lot, and I started on something as well. I really like how you can do a lot of auto layout in html with css or javascript and it's really useful. I think the same is possible for UI. So far I have:

                              • distribute horizontally (equal distribution) with padding is possible
                              • distribute vertically (equal distribution) with padding is possible
                              • adjust width/height/x/y by percent (rather than by points or pixels)

                              Working on a grid distribution.

                              None of this is too hard or rocket science. Just a bunch of numbers. I'm sure anyone could do it...
                              It's really different than what @phuket2 has above though...!!

                              I don't know why I didn't think to do this before... I seem to use a kind of distribution for doing buttons at times, but having it in a module would be much easier. Will share once I've got kinks worked out...

                              With what I have already I could do @phuket2 's battleship grid without the headache! But I want to improve the functions to make it even more straightforward.

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

                                another approach might be a more pythonic implementation of ios layout constraints. These are incredibly powerful, though a bit of a pain to use.

                                Another approach:

                                Years ago, I tried my hand at implementing layout controllers sort of similar to the java equivalents. One difficulty was that there was no way at the time to specify a "preferred size" or minimum size.. though now with custom attributes it is possible.

                                The idea was to override add_subview to properly handle layout of the elements, then different container types would layout the subviews appropriately.

                                I implemented a flowcontainer, and a box layout before I lost interest....
                                https://github.com/jsbain/uicomponents.git
                                see uicontainer.py and BoxLayout.py

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

                                  @Phuket2 As I mentioned in other forum post I have added the example file position_img_shape_in_custom_view.py in textlayout repository.
                                  https://github.com/balachandrana/textlayout.
                                  You can do a git pull if you have already got this.

                                  It is not just limited to pyui. We could use it in custom views for placement of various shapes and images. May be I will post an example later.

                                  This example illustrates using placement of images and shapes in custom view using textlayout module. The textlayout module is generic enough to use in other types of applications. Here is the portion of code illustrating the layout of images and shapes.

                                  Only one image or shape (element) is shown at a time and you can do a tap to change the current element.

                                  
                                  layout_text = '''
                                  ********
                                  i--*****
                                  |--*****
                                  **s--***
                                  ********
                                  ****s--*
                                  ********
                                  ********
                                  '''
                                  ui_element_map = {
                                          'i': ('image', Img),
                                          's': ('shape', Shape)
                                  }
                                  
                                  1 Reply Last reply Reply Quote 0
                                  • First post
                                    Last post
                                  Powered by NodeBB Forums | Contributors