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
-
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)
-
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]
-
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)
-
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)
-
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 🙄
-
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. -
super().init(*args, **kwargs)
ui.View(self, *args,**kwargs)
-
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.
-
@niz Calling
super()
without any arguments only works in Python 3. The equivalent Python 2 code issuper(<classname>, self)
, where<classname>
is the name of the class that you're currently in. This version ofsuper
works on both Python 2 and 3.Note that you have to write the class name in the
super
call by hand. You cannot usetype(self)
orself.__class__
- if you do, thesuper
call won't work properly. (See this Stack Overflow question.) -
@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.
-
@niz whoops, i meant to type
ui.View.__init__(self, *args, **kwargs)