Forum Archive

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

Phuket2

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)

Phuket2

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]
Phuket2

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)

Phuket2

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)            
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 🙄

niz

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.

JonB

super().init(args, *kwargs)

ui.View(self, args,*kwargs)

niz

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.

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

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

JonB

@niz whoops, i meant to type

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