Skip to content

Instantly share code, notes, and snippets.

@jb1123
Created April 24, 2018 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jb1123/fab6d28b593bb22236da8e846f0d061d to your computer and use it in GitHub Desktop.
Save jb1123/fab6d28b593bb22236da8e846f0d061d to your computer and use it in GitHub Desktop.
LockerLayout - adding kivy widgets to predefined boxes in desired sequence
class LockerLayout(Layout):
"""Spacing between children, in pixels."""
spacing = NumericProperty(0)
"""Padding between layout box and children: [padding_left, padding_top, padding_right, padding_bottom]"""
padding = VariableListProperty()
"""size of lockers for attaching widgets, layout space divided among lockers proportionally to locker_size"""
locker_size = ListProperty()
"""Orientation of the layout."""
orientation = OptionProperty('horizontal', options=('horizontal', 'vertical'))
"""locker box size ad position"""
lockerdata = DictProperty()
"""child to lockerid map"""
child2locker = DictProperty()
def __init__(self, **kwargs):
super(LockerLayout, self).__init__(**kwargs)
update = self._trigger_layout
fbind = self.fbind
fbind('spacing', update)
fbind('padding', update)
fbind('children', update)
fbind('orientation', update)
fbind('parent', update)
fbind('size', update)
fbind('pos', update)
fbind('locker_size', update)
fbind('resize_children', update)
fbind('children2locker', update)
fbind('lockerdata', update)
def do_layout(self, *args):
children = self.children
self.update_lockerdata()
for c in children:
# if locker data for child are not ready get_lockerdata(c) returns None
ld = self.get_lockerdata(c)
if ld is None:
continue
w, h = ld['width'], ld['height']
# size
shw, shh = c.size_hint
shw_min, shh_min = c.size_hint_min
shw_max, shh_max = c.size_hint_max
if shw is not None and shh is not None:
c_w = shw * w
c_h = shh * h
if shw_min is not None and c_w < shw_min:
c_w = shw_min
elif shw_max is not None and c_w > shw_max:
c_w = shw_max
if shh_min is not None and c_h < shh_min:
c_h = shh_min
elif shh_max is not None and c_h > shh_max:
c_h = shh_max
c.size = c_w, c_h
elif shw is not None:
c_w = shw * w
if shw_min is not None and c_w < shw_min:
c_w = shw_min
elif shw_max is not None and c_w > shw_max:
c_w = shw_max
c.width = c_w
elif shh is not None:
c_h = shh * h
if shh_min is not None and c_h < shh_min:
c_h = shh_min
elif shh_max is not None and c_h > shh_max:
c_h = shh_max
c.height = c_h
# pos
# ld = self.get_lockerdata(c)
x, y, w, h = ld['x'], ld['y'], ld['width'], ld['height']
c.x, c.y = x, y
for key, value in c.pos_hint.items():
if key == 'x':
c.x = x + value * w
elif key == 'right':
c.right = x + value * w
elif key == 'pos':
c.pos = x + value[0] * w, y + value[1] * h
elif key == 'y':
c.y = y + value * h
elif key == 'top':
c.top = y + value * h
elif key == 'center':
c.center = x + value[0] * w, y + value[1] * h
elif key == 'center_x':
c.center_x = x + value * w
elif key == 'center_y':
c.center_y = y + value * h
def update_lockerdata(self, *args):
locker_size = self.locker_size
if min(locker_size) <= 0:
oborra('some locker bins are <= 0 : ',
'{}'.format({str(i): x for i, x in enumerate(locker_size) if x <=0}))
padding_left, padding_top, padding_right, padding_bottom = self.padding
spacing = self.spacing
padding_x = padding_left + padding_right
padding_y = padding_top + padding_bottom
orientation = self.orientation
locker_total = float(sum(locker_size))
x = self.x + padding_left
y = self.y + padding_bottom
if orientation == 'horizontal':
chilren_space = self.width - padding_x - (len(locker_size) - 1) * spacing
height = self.height - padding_y
for lid, ls in enumerate(locker_size):
# width is a fraction of available children_space
width = round(ls / locker_total * chilren_space)
# update locker bin data
self.set_lockerdata(lid, x, y, width, height)
# prepare for next bin
x += width + spacing
else:
# orientation is an option property vertical orientation here
chilren_space = self.height - padding_y - (len(locker_size) - 1) * spacing
width = self.width - padding_x
for lid, ls in enumerate(locker_size):
# height is a fraction of available children_space
height = round(ls / locker_total * chilren_space)
self.set_lockerdata(lid, x, y, width, height)
y += height + spacing
def set_lockerdata(self, lid, x, y, width, height):
# update locker bin data, calculates all key values form x, y, width, height
lockerdata = self.lockerdata
if lid not in lockerdata:
lockerdata[lid] = {}
lockerdata[lid]['x'] = x
lockerdata[lid]['y'] = y
lockerdata[lid]['pos'] = (x, y)
lockerdata[lid]['width'] = width
lockerdata[lid]['height'] = height
lockerdata[lid]['size'] = (width, height)
center_x = x + width / 2.
lockerdata[lid]['center_x'] = center_x
center_y = y + height / 2.
lockerdata[lid]['center_y'] = center_y
lockerdata[lid]['center'] = (center_x, center_y)
lockerdata[lid]['top'] = y + height
lockerdata[lid]['right'] = x + width
def get_lockerdata(self, child):
# covers widgets added with lockerid passed to add_widget and no lockerid attribute
lid = self.get_lockerid(child)
# always consider child lockerid
if hasattr(child, 'lockerid'):
lid = self.update_child2locker(child, child.lockerid)
if lid is None:
return None
lockerdata = self.lockerdata
ld = {}
try:
x = lockerdata[lid[0]]['x']
y = lockerdata[lid[0]]['y']
width = lockerdata[lid[1]]['right'] - lockerdata[lid[0]]['x']
height = lockerdata[lid[1]]['top'] - lockerdata[lid[0]]['y']
ld['x'] = x
ld['y'] = y
ld['pos'] = (x, y)
ld['width'] = width
ld['height'] = height
ld['size'] = (width, height)
center_x = x + width / 2.
ld['center_x'] = center_x
center_y = y + height / 2.
ld['center_y'] = center_y
ld['center'] = (center_x, center_y)
ld['top'] = y + height
ld['right'] = x + width
return ld
except KeyError:
return None
def get_lockerid(self, child):
return self.child2locker.get(child, None)
def add_widget(self, widget, index=0, lockerid=None):
# widget.lockerid has piority over lockerid
super(LockerLayout, self).add_widget(widget, index)
try:
lid = widget.lockerid if lockerid is None else lockerid
except AttributeError:
return
self.update_child2locker(widget, lid)
def update_child2locker(self, child, lid):
# lid can be either int or n-m string representing span over lockers
lid = str(lid)
# get locker id range
try:
lid = map(float, lid.split('-'))[0:2]
except ValueError:
lid = [0]
if len(lid) == 1: lid.append(lid[0])
if lid[0] > lid[1]: lid.reverse()
# lid must be a valid locker_size index list
# lid = [max(0, lid[0]), min(lid[1], len(self.locker_size)-1)]
self.child2locker[child] = lid
return lid
def remove_widget(self, widget):
super(LockerLayout, self).remove_widget(widget)
try:
self.child2locker.pop(widget)
except KeyError:
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment