Skip to content

Instantly share code, notes, and snippets.

@s0h3ck
Forked from jb1123/lockerlayout.py
Last active April 24, 2018 21:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save s0h3ck/da79c4b7ff0913fce78d4f22f2edf051 to your computer and use it in GitHub Desktop.
Save s0h3ck/da79c4b7ff0913fce78d4f22f2edf051 to your computer and use it in GitHub Desktop.
LockerLayout - adding kivy widgets to predefined boxes in desired sequence
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.layout import Layout
from kivy.properties import NumericProperty, VariableListProperty, ListProperty, OptionProperty, DictProperty
from kivy.uix.button import Button
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
class MyApp(App):
title='LockerLayout'
def build(self):
locker = LockerLayout(locker_size=[.05, .8, .05], orientation='horizontal')
for i in range(10):
locker.add_widget(Button(text=str(i)), index=i)
return locker
if __name__ == '__main__':
MyApp().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment