Created
September 27, 2016 13:26
-
-
Save Phuket2/9cbfd97d9f2638059e50210a210365c6 to your computer and use it in GitHub Desktop.
simple_view2.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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