Skip to content

Instantly share code, notes, and snippets.

@Phuket2
Created September 27, 2016 13:26
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 Phuket2/9cbfd97d9f2638059e50210a210365c6 to your computer and use it in GitHub Desktop.
Save Phuket2/9cbfd97d9f2638059e50210a210365c6 to your computer and use it in GitHub Desktop.
simple_view2.py
# Pythonista Forum - @Phuket2
import ui, editor
from collections import deque, namedtuple
# maybe silly, but each view created in here, we add a dynamic attr
# called vid (view id) to each view. i use it in add_labels_to_sv, a
# debug function, not sure how much more useful it can be.
# ammendment, also use in subviews_as_tuples, in this case, its handy
_v_id = 1
class ViewWalker(object):
# Pythonista Forum - @JonB
'''simple iterator for ui.View objects, capable of depth or
breadth first traversal'''
def __init__(self, v, breadthfirst=False):
self._dq = deque([v])
self._breadth = breadthfirst
def __iter__(self):
'''required for iterator objects'''
return self
def __next__(self):
'''required for iterator objects. raise stopiteration
once the queue is empty. '''
if not self._dq:
raise StopIteration
# pop next view...
if self._breadth:
v = self._dq.popleft() # oldest entry (FIFO)
else:
v = self._dq.pop() # newest entry (stack)
# then push its subviews
if hasattr(v, 'subviews'):
self._dq.extend(v.subviews)
return v
def as_list(self, breadthfirst=False):
self._breadth = breadthfirst
return [s for s in self]
def as_list_names(self, breadthfirst=False):
self._breadth = breadthfirst
return [s.name for s in self]
def as_dict(self, breadthfirst=False):
# returns a dict
self._breadth = breadthfirst
return {s.name: s for s in self}
def as_namedtuple(self, breadthfirst=False):
# returns a namedtuple
self._breadth = breadthfirst
d = self.as_dict(breadthfirst)
return namedtuple('ViewObjects', d.keys())(**d)
def subviews_as_tuples(v):
'''
The idea of this function is to add returned named_tuples to
a var in your class. You can then access your named views with
dot notation as well they are all in the same depth. flattened
out. The constraint is that your view names need to be unquie.
eg. call fva = subviews_as_tuples(the_view)
then can access a view called 'content' like-
fva.content.width
'''
d = {s.name: s for s in ViewWalker(v) if hasattr(v, 'vid')}
# hmmm, just a hack, i cant work out why i get a None dict item.
# probably simple...Just cant see it myself.
d.pop(None, None)
return namedtuple('ViewObjects', d.keys())(**d)
def add_labels_to_sv(parent):
# just for debuging. add a label to each subview with the views name
for v in ViewWalker(parent):
if hasattr(v, 'vid'):
lb = ui.Label()
lb.text = str(v.name)
lb.text_color = 'black'
lb.size_to_fit()
lb.center = v.bounds.center()
lb.flex = 'lrtb'
v.add_subview(lb)
def translate_number(wh, n):
# i know can be done better... but thinking will expand this
# wh is either width or height
# n is a number that we translate
if n == 0 or n > 1:
return n
elif n == -1:
return wh
elif n < 1.0:
return wh * n
def apply_kwargs(kwargs, obj):
for k, v in kwargs.items():
if hasattr(obj, k):
setattr(obj, k, v)
def create_content_view(parent, name='cv', margin=(0, 0), **kwargs):
cv = ui.View(name=name, frame=ui.Rect(*parent.frame).inset(*margin))
cv.vid = _v_id
cv.flex = 'wh'
apply_kwargs(kwargs, cv)
return cv
def insert_into_view(view, v_list, v_names=None, vert=True,
v_margin=(0, 0), **kwargs):
# if v_names (view names list), not passed, we make sure we have a
# valid list.
v_names = v_names if v_names else []
# the v_names list is made vaild here. we ensure we have a entry for
# every view that will be created. If the v_names list is shorter
# than the v_list, the list is appened to with generated view names.
# The names are generated in the form {parent.name}-{counter}. To
# help aviod conflicts, the counter starts with the number of
# subviews in the parent. Its ok to pass an incomplete list of names,
# the ones supplied will be used, the unsupplied will be generated.
if len(v_names) < len(v_list):
cnt = len(view.subviews)
for i in range(len(v_names), len(v_list)):
v_names.append('{}_{}'.format(view.name, cnt))
cnt += 1
r = ui.Rect(*view.bounds)
def get_v():
# return based on vertical or horizontal, maybe not good. But
# i find this helpful to try to keep the code generic as i can
return r.height if vert else r.width
# translate the numbers in v_list to absolute numbers, except zero
v_list = [translate_number(get_v(), x) if x else x for x in v_list]
# var is the space left over in the view after the absolute heights
# have been subtracted divided by the number if items that are
# equal to 0
zero_count = v_list.count(0)
# aviod divide by zero error
var = (get_v() - sum(v_list)) / zero_count if zero_count else\
(get_v() - sum(v_list))
# replaces 0 in v_list with var.
v_list = [var if x == 0 else x for x in v_list]
lst = [] # a list of the views created, we return this
xy = 0 # keep track of width or height
# go through v_list, create the views as subviews of cv, and apply
# some attrs to the created views
for i, num in enumerate(v_list):
frame = ui.Rect(0, xy, r.width, num).inset(*v_margin) if vert\
else ui.Rect(xy, 0, num, r.height).inset(*v_margin)
# v_name = v_names[i].get()
v = ui.View(name=v_names[i], frame=frame)
v.vid = _v_id
v.flex = 'whlrtb'
apply_kwargs(kwargs, v)
view.add_subview(v)
xy += num
lst.append(v)
return lst # return a list of the newly created views
def split_view_h(parent, view, v_list, v_names=None, h_gap=0):
# split a view horizontally.
v_names = v_names if v_names else []
if len(v_names) < len(v_list) - 1:
cnt = len(view.subviews)
for i in range(len(v_names), len(v_list)-1):
v_names.append('{}_{}'.format(view.name, cnt))
cnt += 1
r = ui.Rect(*view.frame)
# reduce our width to allow for the h_gap param
r.width -= h_gap * (len(v_list) - 1)
# translate the numbers in v_list to absolute numbers
v_list = [translate_number(r.width, x) if x else x for x in v_list]
# var is the space left over in the view after the absolute heights
# have been subtracted divided by the number if items that are
# equal to 0
zero_count = v_list.count(0)
# aviod divide by zero error
var = (r.width - sum(v_list)) / zero_count if zero_count else\
(r.width - sum(v_list))
# replaces 0 in v_list with var.
v_list = [var if x == 0 else x for x in v_list]
x = r.x
y = r.y
num_views = len(parent.subviews)
lst = [] # a list of the views created, we return this
for i, w in enumerate(v_list):
frame = ui.Rect(x, y, w, r.height)
if i is 0:
# this is the frame we split. we just resize it, the view is
# NOT renamed
view.frame = frame
else:
frame.x += (h_gap * i)
v = ui.View(name=v_names[i-1], frame=frame)
v.vid = _v_id
v.flex = 'whlrtb'
v.border_width = view.border_width
v.corner_radius = view.corner_radius
parent.add_subview(v)
lst.append(v)
x += w
return lst # return a list of the newly created views
class MyClass(ui.View):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.cv = None
self.make_view()
# fva (Flattened View Access) (i think this is great)
# another way, might be to update this __dict__ so you dont have
# to go through a attr. Then would need to make sure the view
# names are valid dict key names and that they dont conflict with
# existing attrs. will look at that later.
# one down fall is that while its dot notation, so its late bound
# (i think thats the jargon). So you dont auto popup members in
# the editor. Seems reasonable.
self.fva = subviews_as_tuples(self.cv)
fva = self.fva
'''
print(fva.btn1.width)
print(fva.content.width)
fva.btn1.bg_color = 'orange'
'''
def make_view(self):
# dont need this, just if you want an enclosing view,
# can be handy. Well more than HANDY. Having an enclosing view
# like this, you can resize this view. As all the subviews flex
# attrs have been set correctly, resizing this view will result
# in the same view, just in the new dimensions inside the parent.
cv = create_content_view(self, margin=(5, 5),
bg_color='pink', corner_radius=6)
self.cv = cv
self.add_subview(cv)
# below make 4 calls and end with a fairly common ui Layout.
# this is very close to the wireframe for making the iOS
# settings view.I could make it exactly like it, but this
# is more natural for the functions now. so rather than try to
# force it to fit, it will work on it more so it can easily be
# exact. i dont think it will take that much
vl = insert_into_view(cv, [44, 0],
v_names=['title', 'content'],
vert=True, v_margin=(4, 4),
border_color='maroon', corner_radius=6,
border_width=.5)
# note: that split_view_h and insert_into_view, can accept a list
# of zeros. below i am passing [0]*2 , which splits the view into
# 2 equal parts. I could have passed [.5, 0], [0, .5], [0, 0] etc
# the result would be the same
vl = split_view_h(cv, cv['title'], [0]*2, ['General'], h_gap=0)
vl = split_view_h(cv, cv['content'], [.5, 0], ['right_p'], h_gap=5)
# add a search and table view
vl = insert_into_view(cv['content'], [44, 0],
v_names=['search', 'table'],
vert=True, v_margin=(0,0),
border_color='maroon', corner_radius=0,
border_width=.5)
# to the search panel add a search field view
vl = insert_into_view(cv['content']['search'], [0],
v_names=['search_fld'],
vert=True, v_margin=(4, 6),
border_color='maroon', corner_radius=6,
border_width=.5)
# to demonstrate, why the cv (Container View) is a good idea.
# modify the numbers below. you can go higher than 1.0, but
# something less than 1 will make more sense, eg .5
# btw, not my idea, seen @omz and other smart guys doing this
cv.width *= 1
cv.height *= 1
cv.center = self.bounds.center()
if __name__ == '__main__':
_use_theme = True
#w, h = 600, 800
#w, h = 375, 667
w, h = 320, 568 #iphone 5 up...
f = (0, 0, w, h)
style = 'sheet'
mc = MyClass(name='Dreaming', 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)
# just debugging, add labels to each view to show the view name
add_labels_to_sv(mc)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment