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.


    [Lab] on the fly grid to help position ui elements

    Pythonista
    ui.controls lab grid
    4
    11
    7101
    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

      This is still Lab, because still thinking though it. But the idea is to make a grid of a rectangle returning a list of lists (2 dimensional array) populated with ui.Rects.
      This gives you a type of spreadsheet access to your grid.

      My thinking is this an alternative to positioning ui elements in a view using ratios etc.

      So place them using your grid reference. It's a little wasteful as you calculate all the Rects for the grid, you only may use one or 2 of the references. But it should be so fast it does not really matter.

      The reason to go this way, I think we can more easily relate to positioning items in a grid or in relation to a grid item.

      Hmmm, I don't think I am explaing this well. The code below, I use 2 methods of creating a grid. One based on passing in the number of required rows and columns, the other based on width and height of the cells. Other grids could be created using different metrics that make sense.

      I know the code is nothing earth shattering. It's more about the concept. I also realise that the the functions written could be done in a calculated fashion. Aka. A virtual grid. Only calculate the values Upon request.

      But to start this is ok. Can be refined.

      Not sure about other users, but for me often placing items in a view is a pain and a lot of relative object calculations.

      This way, you can slice the view in a way that makes sense. Position your objects. Then slice the same view again to position other items that don't fit into the previous grid, so on and so on.
      Most things will fit into a grid. But often a functional view's items will not be on the same grid. Hmmm....spaghetti talk...

      Anyway, the below example is pretty crappy. But if you can try and think in terms of sizing and placing a ui.element inside a view.

      # Pythonista Forum - @Phuket2
      import ui, editor
      
      def grid_rc_(bounds, rows=1, columns=1):
      	# a grid based on rows and columns
      	# return a list of lists of ui.Rects
      	r = ui.Rect(*bounds)
      	w = r.width / columns
      	h = r.height / rows
      	rl = []
      	for i in range(rows):
      		lst = []
      		for j in range(columns):
      			lst.append(ui.Rect(j*w, h*i, w, h))
      		rl.append(lst)
      	return rl
      
      
      def grid_wh_(bounds, w, h):
      	# a grid based on widths and heights
      	# return a list of lists of ui.Rects
      	r = ui.Rect(*bounds)
      
      	rl = []
      	for i in range(int(r.height / h)):
      		lst = []
      		for j in range(int(r.width / w)):
      			lst.append(ui.Rect(j*w, h*i, w, h))
      		rl.append(lst)
      	return rl
      	
      class MyClass(ui.View):
      	def __init__(self, *args, **kwargs):
      		super().__init__(*args, **kwargs)
      		# create the grids... static here. easy to call adhoc anytime,
      		# on any rect to give you a grid as per the params
      		self.grid_rc = grid_rc_(self.bounds, rows=3, columns=9)
      		self.grid_wh = grid_wh_(self.bounds, w=10, h=10)
      
      	def draw(self):
      		rc = self.grid_rc
      		wh = self.grid_wh
      		
      		# using row, column grid
      		ui.set_color('teal')
      		s = ui.Path.rect(*rc[0][8])
      		s.fill()
      		
      		s = ui.Path.rect(*rc[1][0])
      		s.fill()
      		
      		s = ui.Path.rect(*rc[2][4])
      		s.fill()
      		
      		# using wh grid
      		ui.set_color('red')
      		s = ui.Path.rect(*wh[5][20])
      		s.fill()
      		
      		s = ui.Path.rect(*wh[0][0])
      		s.fill()
      		
      if __name__ == '__main__':
      	_use_theme = True
      	w, h = 600, 800
      	f = (0, 0, w, h)
      	style = 'sheet'
      	
      	mc = MyClass(frame=f, bg_color='white')
      	
      	if not _use_theme:
      		mc.present(style=style, animated=False)
      	else:
      		editor.present_themed(mc, theme_name='Oceanic', style=style, animated=False)
      				
      
      1 Reply Last reply Reply Quote 0
      • Phuket2
        Phuket2 last edited by

        A func to return a flattened list using a list comprehension. Found it on stackoverflow. Acutually, I could have also written it, but i was looking to make sure there was not some other Python magic that could be done. Can be a useful func when you have a list of lists and you just want to iterate over them easily.

        def flattened_list(lst):
        	# flatten the list array, the code from stackoverflow
        	return [item for sublist in lst for item in sublist]
        
        1 Reply Last reply Reply Quote 0
        • Phuket2
          Phuket2 last edited by

          Ok, here is an example using ui.Buttons. It's still a pretty generic example. But gets closer to my meaning.
          But if you disregard the function grid_rc_ it's a line or 2. Again, this not necessarily a real world example (but it could be).

          # Pythonista Forum - @Phuket2
          import ui, editor
          
          def grid_rc_(bounds, rows=1, columns=1):
          	# a grid based on rows and columns
          	# return a list of lists of ui.Rects
          	r = ui.Rect(*bounds)
          	w = r.width / columns
          	h = r.height / rows
          	rl = []
          	for i in range(rows):
          		lst = []
          		for j in range(columns):
          			lst.append(ui.Rect(j*w, h*i, w, h))
          		rl.append(lst)
          	return rl
          	
          def flattened_list(lst):
          	# flatten the list array, the code from stackoverflow
          	return [item for sublist in lst for item in sublist]
          		
          class MyClass(ui.View):
          	def __init__(self, *args, **kwargs):
          		super().__init__(*args, **kwargs)
          		self.make_view()
          		
          	def make_view(self):
          		rows = 10
          		cols = 5
          		
          		# get a flatten lst of the specified grid
          		lst = flattened_list(grid_rc_(self.bounds, rows, cols))
          
          		for i, r in enumerate(lst):
          			r = ui.Rect(*r.inset(5, 5))
          			btn = ui.Button(name=str(i), frame=r)
          			btn.title = str(i)
          			btn.border_width = .5
          			btn.corner_radius = btn.width * .1
          			btn.action = self.btn_action
          			self.add_subview(btn)
          			
          	def btn_action(self, sender):
          		print('btn -', sender.name)
          		
          if __name__ == '__main__':
          	_use_theme = False
          	w, h = 600, 800
          	f = (0, 0, w, h)
          	style = 'sheet'
          	
          	mc = MyClass(frame=f, bg_color='white')
          	
          	if not _use_theme:
          		mc.present(style=style, animated=False)
          	else:
          		editor.present_themed(mc, theme_name='Oceanic', style=style, animated=False)
          				
          

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

            The same example, but getting a little stupid using list comprehensions. Actually I haven't tried this type of list comprehension before.
            But ok, we need the method create_ui_obj, however if and when ui elements handle all kwargs, this method would not be required.

            Even though it's not so smart, it can spark some ideas.

            # Pythonista Forum - @Phuket2
            import ui, editor
            
            def grid_rc_(bounds, rows=1, columns=1):
            	# a grid based on rows and columns
            	# return a list of lists of ui.Rects
            	r = ui.Rect(*bounds)
            	w = r.width / columns
            	h = r.height / rows
            	rl = []
            	for i in range(rows):
            		lst = []
            		for j in range(columns):
            			lst.append(ui.Rect(j*w, h*i, w, h))
            		rl.append(lst)
            	return rl
            	
            def flattened_list(lst):
            	# flatten the list array, the code from stackoverflow
            	return [item for sublist in lst for item in sublist]
            		
            class MyClass(ui.View):
            	def __init__(self, *args, **kwargs):
            		super().__init__(*args, **kwargs)
            		self.make_view()
            		
            	def make_view(self):
            		rows = 20
            		cols = 15
            		# another way, its a little crazy...but maybe @ccc likes it :)
            		[self.add_subview(self.create_ui_obj(ui.Button,
            		name=str(i),frame=f.inset(5,5), border_width=.5,
            		corner_radius= 12, title=str(i), action = self.btn_action,
            		bg_color='teal', tint_color='white'))
            		for i, f in enumerate(flattened_list(grid_rc_(self.bounds,
            rows, cols)))]
            
            
            	def create_ui_obj(self, ui_type, **kwargs):
            		obj = ui_type()
            		for k, v in kwargs.items():
            			if hasattr(obj, k):
            				setattr(obj, k, v)
            		return obj
            			
            	def btn_action(self, sender):
            		print('btn -', sender.name)
            		
            if __name__ == '__main__':
            	_use_theme = False
            	w, h = 600, 800
            	f = (0, 0, w, h)
            	style = 'sheet'
            	
            	mc = MyClass(frame=f, bg_color='white')
            	
            	if not _use_theme:
            		mc.present(style=style, animated=False)
            	else:
            		editor.present_themed(mc, theme_name='Oceanic', style=style, animated=False)			
            
            1 Reply Last reply Reply Quote 0
            • Phuket2
              Phuket2 last edited by Phuket2

              Hmm, to be size and orientation friendly the last post's make_view should be as below. Forgot to set the flex attr to 'lrtb'

              	def make_view(self):
              		rows = 20
              		cols = 15
              		# another way, its a little crazy...but maybe @ccc likes it :)
              		[self.add_subview(self.create_ui_obj(ui.Button,
              		name=str(i),frame=f.inset(5,5), border_width=.5,
              		corner_radius= 6, title=str(i), action = self.btn_action,
              		bg_color='maroon', tint_color='white', flex='tlbrwh'))
              		for i, f in enumerate(flattened_list(grid_rc_(self.bounds, rows, cols)))]
              

              Edit: sorry another screw up. The flex should be 'tlbrwh', modified the code also 🙄

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

                This is great @Phuket2, exactly what I was looking for. Just one question. Do you know what I need to change to make this work in Python 2?

                I’m getting the error:

                in __init__
                    super().__init__(*args, **kwargs)
                TypeError: super() takes at least 1 
                argument (0 given)
                

                I’ve had a bit of a Google and played around. I can get it to run in Python 2 if I comment out the line:
                super().init(*args, **kwargs)
                But the resulting grid of buttons doesn’t look the same as with Python 3. The button widths collapse down to a minimum size.

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

                  super().init(*args, **kwargs)

                  ui.View(self, *args,**kwargs)

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

                    Thanks for your quick response @JonB. If I replace

                    super().init(*args, **kwargs)
                    

                    with

                    ui.View(self, *args,**kwargs)
                    

                    it runs but I just get a blank screen.

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

                      @niz Calling super() without any arguments only works in Python 3. The equivalent Python 2 code is super(<classname>, self), where <classname> is the name of the class that you're currently in. This version of super works on both Python 2 and 3.

                      Note that you have to write the class name in the super call by hand. You cannot use type(self) or self.__class__ - if you do, the super call won't work properly. (See this Stack Overflow question.)

                      niz 1 Reply Last reply Reply Quote 0
                      • niz
                        niz @dgelessus last edited by

                        @dgelessus Thank you. This works perfectly. When I’d Googled this I found a couple of examples that said to add <classname> but they neglected to mention you also needed self.

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

                          @niz whoops, i meant to type

                          ui.View.__init__(self, *args, **kwargs)
                          
                          1 Reply Last reply Reply Quote 0
                          • First post
                            Last post
                          Powered by NodeBB Forums | Contributors