Created
August 26, 2015 11:39
-
-
Save anonymous/c11f3cdd98c76a73ee0a to your computer and use it in GitHub Desktop.
vpv.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
# coding: utf-8 | |
# Pythonista 1.6 | |
# many things just copied from omz starter code | |
# all the bad stuff, thats me :) | |
# still learning | |
###### VirtualView ####### | |
# for displaying large amounts of data | |
# needed for 1.6 at the moment, if importing from same dir | |
import sys | |
if '.' not in sys.path: | |
sys.path.insert(0, '.') | |
# using Faker to demo....data | |
from faker import Faker | |
fake = Faker() | |
import time | |
from time import strftime | |
''' | |
17 Aug 2015 | |
1. color_demo, added random color when clicked. | |
18 Aug 2015 | |
1. using gistcheck.py to commit this file to gisthub. | |
i thought it was working, its not working though. | |
i want to punch it, drives me crazy. | |
2. **** Important ***** | |
no need to add a button for a hit test now. when you create your cell, you can set self.action to a method/function. if the cell is clicked/hit, the self.action will be called if defined. | |
Changed all the demo cells to work off the new mechanism. | |
3. changing the click mechanism had a side effect. the cell was not being released from memory when setting self.action = some_method. In the release | |
function i now set self.action = None, the memory leak stopped. | |
4. added 'Top' and 'Bottom' menu items to the titlebar. just to take you to the top abd bottom of the list. | |
5. the initial cell count is 100 million. its a VirtualView. 5 or 500 million cells should not matter. if you are not comportable with that number, just change _item_count below to a smaller number! | |
6. changed the ui.Label width to be able to display the number correcly. did it the lazy way :( | |
20 Aug 2015 | |
1. i stopped using self.bounds etc for calculations. the scrollview is used now as it should have been. this is required if you need to but anyother view inside the same containing view. | |
22 Aug 2015 | |
1. added a scroll navigator. lets you move through the list in percent increments | |
2. adder Faker for more real world data examples | |
3. have a list called subview_destroy_register.if a view is appended to this list, the release method will remove the view | |
4. added a Stress class. this just keeps scrolling the list, using ui.delay. the nice thing about this is that no code is requied inside the virtualview class. | |
23 Aug 2015 | |
1. added the concept of a table. there is a new class TableHeader that can be added to the top of the view, and i did a demo of making columns. because in the colums, the views are views inside views, there is a list subview_destroy_register you can append views to. when the release is called, any view in that list is removed from the view. we think about this more. maybe, i am just being stuip. maybe i should just walk all the subviews and remove them. | |
26 Aug 2015 | |
1. added a Class ThreadedCell. Like all of this is, its just work in progress. the idea is that will try and get slow loading resouces from the web etc. i have just tried to simulate this with time.sleep at the moment. but pretty sure urs going to get more involved than that once i start using something like requests. some fun and homework coming my way :) | |
General Notes | |
============================================= | |
1. when creating a cell/row and set a callback function, other than the ones already definded, you should explicitly set the action field to none in the release method of the VirtualCell class. | |
if you dont do this, your objects will not be freed from memory resulting in a memory leak. | |
To-do, ideas | |
============================================ | |
1. to create cells on a thread. maybe the cell is trying to get a image from a url etc. but a lot for me to think about. | |
''' | |
import ui | |
from random import randint | |
import random | |
import time, sys | |
import console | |
# determining if Phytonista 1.5 or 1.6xxx | |
__ver__ = 0 | |
try: | |
import dialogs | |
__ver__ = 1.6 | |
except ImportError: | |
__ver__ = 1.5 | |
# some pythonista built in image names for demo | |
images = ['Confounded', 'Crying_2', 'Disappointed' , 'Dizzy', 'Flushed', 'Smiling_3', 'Winking', 'Tired', 'Unamused', 'Fisted_Hand', 'Index_Finger_Up_1', 'Four_Leaf_Clover', 'Herb', 'Mushroom', 'Baby_Chick_1', 'Bactrian_Camel', 'Boar', 'Cat_Face_Grinning','Cat_Face_Heart-Shaped_Eyes', 'Cow_Face', 'Honeybee'] | |
ani_images = ['pzl:BallBlue', 'pzl:BallGray'] | |
btn_decorators = {'checkmark' : 'iob:ios7_checkmark_outline_24' ,'information' :'iob:ios7_information_24', 'price' : 'iob:ios7_pricetag_24'} | |
# for tableview style...will expand on this later | |
# i think will use a list of named tuples. Column should include, width, alignment, possibly font etc. | |
table_def = [('Name'), ('Company'), ('User Name'), ('CT')] | |
# some helper functions | |
def random_color(): | |
# for demo purposes | |
return(randint(0,255) / 255., randint(0,255) / 255., randint(0,255) / 255.) | |
def invert_rgb(color): | |
return(255 - color[0], 255 - color[1], 255 - color[2], 0) | |
def rand_image(): | |
# just return an image from, a list (subset) | |
# of pythonista built images. | |
# testing/demo purposes | |
return ui.Image.named(images[randint(0,len(images) -1)]) | |
def inset_frame(f, left, top, w, h): | |
return (f[0] + left, f[1] + top, f[2] + w, f[3] + h) | |
# end helper functions | |
# for demo purposes, can use image_demo, color_demo, row_demo, row_style_1, create_table_row | |
_cell_type = 'row_style_1' | |
# for demo purposes, can set the item_size here | |
# item_size is width, height. if either w,h = 0 | |
# then the width or height or both will be the | |
# value of the presented view | |
_item_size = ( 128, 44) | |
# just to make demo life a little easier. | |
# if using 'row_type' as cell, sets the width to 0 | |
if 'row' in _cell_type: | |
_item_size = (0, _item_size[1]) | |
# can provide data in 2 ways when creating the | |
# VirtualView. You can pass item_count, no data. | |
# or you can pass a list of whatever. | |
# passing item_count negates having to do | |
# something like range(100000) to pass in. | |
_item_count = 1000000 # 1,000,000 | |
#should be ok. What is nice, is ui.ScrollView is | |
# clean in terms of memory usage. not to imply that | |
# it would be wrong. just nice it is not. | |
_buf_size = 80 | |
# presentation style | |
_pres_style = 'sheet' | |
#to use when have alternating row colors, with rowtype cells. | |
_alt_row_colors = ('lightyellow', 'orange') | |
# whether or not to add a scroll navigation bar | |
_add_nav_bar = True | |
# whether or not to add a title header | |
_add_table_header =False | |
# if this is true, then after 2 seconds, the list | |
# start to scroll itself. will scroll as fast as it | |
# can. good for testing for memory leaks, data | |
# inconsistences etc... | |
_STRESS_TEST = False | |
# if True, uses the ThreadedCell Class. Just trying | |
# to build something at the moment... | |
_USE_THREADED_CELL = True | |
import threading | |
class ThreadedCell(ui.View): | |
def __init__(self, caller, item_index, data_item, w, h, cell_type = None): | |
self.caller = caller | |
self.item_index = item_index | |
self.width, self.height = w, h | |
self.data_item = data_item | |
self.action = None | |
self.action_in_hilight_cell = None | |
self.t = None | |
self.event = None | |
self.create_cell() | |
def create_cell(self): | |
self.background_color = 'gray' | |
self.border_width = .5 | |
self.border_color = 'white' | |
lb = ui.Label(name = 'status', frame = self.frame) | |
lb.text = 'loading' | |
lb.text_color = 'white' | |
lb.alignment = ui.ALIGN_CENTER | |
self.add_subview(lb) | |
self.add_item_id_label() | |
# make sure we only run the Thread once | |
if not self.t: | |
self.t=threading.Thread(target=self.fetch_data) | |
# signalling mech. | |
self.event = threading.Event() | |
self.t.start() | |
def add_item_id_label(self): | |
# just add a label to see the item_index. | |
# handy for debugging | |
txt = '{:,}'.format(self.item_index) | |
w, h = 10 * len(txt), 20 | |
f = (self.width - w, 0, w , h) | |
lb = ui.Label( frame = f) | |
lb.frame = f | |
lb.background_color = 'black' | |
lb.text_color = 'white' | |
lb.text = txt | |
lb.alignment = ui.ALIGN_RIGHT | |
self.add_subview(lb) | |
lb.bring_to_front() | |
def fetch_data(self): | |
# this is trying to simulate waiting for data | |
# from something like a web resource... | |
# i guess this will get more problematic when | |
# other threaded resources like response is | |
# used | |
# signalling for the thread | |
e = self.event | |
# first item | |
if e.isSet(): return | |
time.sleep(random.random()) | |
# second item | |
if e.isSet(): return | |
time.sleep(random.random()) | |
# third item | |
if e.isSet(): return | |
time.sleep(random.random()) | |
# Fourth item | |
if e.isSet(): return | |
time.sleep(random.random()) | |
if e.isSet(): return | |
self['status'].text = 'Loaded' | |
if e.isSet(): return | |
self.background_color = 'orange' | |
def release(self): | |
# if the thread is alive, signal it to exit | |
# i think i should be using join here. havnt | |
# got my head around that yet. | |
if self.t.isAlive(): | |
self.event.set() | |
# remove ourself from the superview | |
# maybe this is a stupid idea. Maybe | |
# smarter for the caller to remove us? | |
self.superview.remove_subview(self) | |
# this would not always be required. but | |
# i found when a button for example has a | |
# action attached, even internal to this | |
# class. Memory is not being released unless | |
# the subviews are removed. thats only one | |
# example. could be others, if using other | |
# callbacks such as delegates. | |
for sv in self.subviews: | |
self.remove_subview(sv) | |
# if i dont do this, the cell is not resleased | |
# from memory! | |
self.action = None | |
self.action_in_hilight_cell = None | |
self.t = None | |
class VirtualCell(ui.View): | |
def __init__(self, caller, item_index, data_item, w, h, cell_type = None): | |
""" | |
A cell object to be used by the VirtualView | |
Class. | |
Attributes: | |
caller: | |
the instanciating object | |
item_index: | |
the items index as displayed by the Virtual | |
data_item: | |
the data to be drawn for the cell. can be None if the VirtualView is not using data | |
w: | |
the width of the cell being requested | |
h: | |
the height of the cell being requested | |
cell_type: | |
the idea is that the ui elements for a cell are built in their own method. Making it easier to create different cell types. | |
i guess this could have been done a few ways, inheritance or just a copy abd paste to make a new cell. | |
As i get better, i hope to improve the design, but this is what i have for now. | |
if cell_type is None, will call create_cell_contents method from init. otherwise you could create your own methods and call them appropriately. | |
i try to give an example. | |
action_in_hilight_cell: | |
""" | |
# fake some data with Faker | |
data_item = [fake.name(), fake.company(), fake.user_name(), fake.country_code()] | |
self.caller = caller | |
self.item_index = item_index | |
self.width, self.height = w, h | |
self.data_item = data_item | |
self.action = None | |
self.action_in_hilight_cell = None | |
# just an idea to register views that are | |
# subviews of other views that you want to be | |
# sure are removed so memory is released | |
self.subview_destroy_register = [] | |
# for row_demo cell_type | |
self.selected = False | |
# just for demo/testing purposes | |
# would normally just call create_cell_contents | |
if not cell_type: | |
self.create_cell_contents() | |
elif cell_type == 'image_demo': | |
self.create_image_cell_contents() | |
elif cell_type == 'color_demo': | |
self.create_color_cell_contents() | |
elif cell_type == 'row_demo': | |
self.row_demo_contents() | |
elif cell_type == 'row_style_1': | |
self.row_style_1_contents() | |
elif cell_type == 'create_table_row': | |
self.create_table_row() | |
else: | |
# if we dont recognise the cell_type | |
# call the default | |
self.create_cell_contents() | |
if 'row' not in cell_type: | |
# adds a label to the cell with the | |
# item_index in the top right corner. | |
# for debugging only | |
pass | |
self.add_item_id_label() | |
def create_cell_contents(self): | |
# the place you would typically create your | |
# cells ui elements | |
pass | |
def create_col_cell(self,col_num, w): | |
col_cell = ui.View(name = str(col_num), frame = (0,0,w,self.height)) | |
f = inset_frame(col_cell.frame, 3, 3, 0,0) | |
lb = ui.Label(name = 'title', frame = f) | |
lb.name = 'title' | |
col_cell.add_subview(lb) | |
# if the view is added to self.subview_destroy_register, it will be | |
self.subview_destroy_register.append(lb) | |
col_cell.border_width = .5 | |
return col_cell | |
def create_table_row(self): | |
cols = 4 | |
col_width = self.width / cols | |
for i in range(0,cols): | |
col_view = self.create_col_cell(i, col_width ) | |
col_view['title'].text = self.data_item[i] | |
col_view.x = (col_width * i) | |
self.add_subview(col_view) | |
self.action = self.cell_row_click | |
self._set_row_alt_color() | |
def create_color_cell_contents(self): | |
# demo of using random color swatchs | |
self.background_color = random_color() | |
self.action = self.set_bgcolor_to_random | |
def create_image_cell_contents(self): | |
# demo of displaying images. Just changes | |
# the image when the cell is clicked | |
img = ui.ImageView(name = 'img', frame = self.frame) | |
img.image = rand_image() | |
self.add_subview(img) | |
self.action = self.set_random_image | |
def row_demo_contents(self): | |
# demo of virtual rows. is not smart as the | |
# ui.TableView does this a billion times better | |
self.flex = 'W' | |
self.ignore_width = True | |
lb = ui.Label(frame = self.frame) | |
lb.text = '{:,}'.format(self.item_index) | |
lb.text = self.data_item[0] | |
lb.alignment = ui.ALIGN_LEFT | |
lb.font = ('Verdana', 20) | |
lb.x += 30 | |
lb.width -= 30 | |
lb.center = self.center | |
self.add_subview(lb) | |
self.action = self.cell_row_click | |
self.border_width = .5 | |
self._set_row_alt_color() | |
def row_style_1_contents(self): | |
_margin = 5 | |
# title text | |
f = (38, 0, self.width , self.height *.65) | |
lb = ui.Label(name = 'title_text' , frame = f ) | |
lb.text = self.data_item[0] | |
lb.font = ('Melno', 36) | |
self.add_subview(lb) | |
# subtitle | |
f = (38, lb.y + lb.height, lb.width, self.height - lb.height) | |
lb2 = ui.Label(name = 'subtitle', frame = f) | |
lb2.text = self.data_item[1] | |
lb2.font = ('DIN Condensed', 16) | |
self.add_subview(lb2) | |
# icon/image | |
f = (0,0, 32, 32) | |
img = ui.ImageView(name = 'img', frame = f) | |
img.image = rand_image() | |
img.y = (self.height - img.height) / 2 | |
self.add_subview(img) | |
self.border_width =.3 | |
# info button | |
btn = ui.Button('info', title = '') | |
btn.frame = self.frame | |
btn.width = btn.height = 32 | |
btn.x = self.width - btn.width | |
btn.y = (self.height - img.height) / 2 | |
btn.font = ('<system-bold>', 22) | |
btn.background_image = ui.Image.named(btn_decorators['checkmark']) | |
self.add_subview(btn) | |
self.action = self.cell_row_click | |
self.action_in_hilight_cell = self.set_random_image | |
self._set_row_alt_color() | |
# action functions for when we are clicked, cell | |
# type to define. otherwise None | |
def set_bgcolor_to_random(self): | |
self.background_color = random_color() | |
def set_random_image(self): | |
iv = self['img'] | |
iv.image = None | |
iv.image = rand_image() | |
def _set_row_alt_color(self): | |
if not self.item_index % 2: | |
self.background_color = _alt_row_colors[0] | |
else: | |
self.background_color = _alt_row_colors[1] | |
@ui.in_background | |
def cell_row_click(self): | |
# called when clicking on a row, with row_demo cell_type | |
# my stupid attempt to try to simulate a row click. | |
# i have tried a few things here. seems like | |
# over kill. the current way, just using invert | |
# rgb. not a great method. but illustrates what | |
# can be done for now | |
if self.selected : return | |
self.selected = True | |
old_color = self.background_color | |
self.background_color = invert_rgb(self.background_color) | |
if self.action_in_hilight_cell: | |
self.action_in_hilight_cell() | |
time.sleep(.12) | |
self.background_color = old_color | |
self.selected = False | |
def add_item_id_label(self): | |
# just add a label to see the item_index. | |
# handy for debugging | |
txt = '{:,}'.format(self.item_index) | |
w, h = 10 * len(txt), 20 | |
f = (self.width - w, 0, w , h) | |
lb = ui.Label( frame = f) | |
lb.frame = f | |
lb.background_color = 'black' | |
lb.text_color = 'white' | |
lb.text = txt | |
lb.alignment = ui.ALIGN_RIGHT | |
self.add_subview(lb) | |
lb.bring_to_front() | |
def layout(self): | |
pass | |
# no need to add a btn to the view for a click | |
# event. this works very well. | |
# As long as we are ok with the whole cell | |
# ignoring, touch_began, touch_moved. Of course, | |
# other opporturnities to use these events. | |
# KISS at the moment :) | |
def touch_ended(self, touch): | |
if self.action: | |
self.action() | |
# explicity call this method, to free up | |
# resources. whatever they maybe. dont rely on | |
# __del__ | |
def release(self): | |
# remove ourself from the superview | |
# maybe this is a stupid idea. Maybe | |
# smarter for the caller to remove us? | |
self.superview.remove_subview(self) | |
# this would not always be required. but | |
# i found when a button for example has a | |
# action attached, even internal to this | |
# class. Memory is not being released unless | |
# the subviews are removed. thats only one | |
# example. could be others, if using other | |
# callbacks such as delegates. | |
for sv in self.subviews: | |
self.remove_subview(sv) | |
# hmmmm, thinking about this. without walking | |
# through the whole tree. can register a subview | |
# to be released, if its not reachable from | |
# from the top layer.... | |
for sv in self.subview_destroy_register: | |
self.remove_subview(sv) | |
# if i dont do this, the cell is not resleased | |
# from memory! | |
self.action = None | |
self.action_in_hilight_cell = None | |
#def __del__(self): | |
# This does get called. but from what i read, | |
# better to have a explicit function, rather | |
# than rely on __del__. i am not good enough | |
# to understand it. So for now, i call the | |
# classes release method explicitly. | |
# Seems a shame. Would make cleaning up so | |
# much more natural. In my view. | |
#print '__del__ was called on item {}'.format(self.item_index) | |
#pass | |
class VirtualView(ui.View): | |
def __init__(self, vw, vh, item_size, buf_size = 0, items= None, item_count = 0): | |
""" | |
A VirtualView for displaying large | |
amounts of data in scrollable area. | |
Attributes: | |
item_width: | |
width of cell | |
item_height: | |
heigth of cell | |
buf_size: | |
items: | |
item_count: | |
""" | |
# set the width and height of the view | |
self.width = vw | |
self.height = vh | |
# buf_size gets set in the layout method. | |
# currently if its smaller than one screen | |
# of items, its resized to be able to hold | |
# at least one screen of items | |
self.buf_size = buf_size | |
# a list of VirtualCell objects, the number of | |
# buffered Cells, dedends on bufsize | |
self.buffer = [] | |
# calculated in layout method | |
self.visible_rows = 0 | |
self.items_per_row = 0 | |
# store the the w,h like this for ease of reading | |
self.item_width = item_size[0] | |
self.item_height = item_size[1] | |
# flags to indicate that the width of height or | |
# both should be overwridden. if item_width or | |
# item height is 0, the width or height if the | |
# scrollview is used | |
self.override_width = False | |
self.override_height = False | |
if self.item_width == 0: | |
self.override_width = True | |
if self.item_height == 0: | |
self.override_height = True | |
# to make sure we are called by layout | |
self.flex = 'WH' | |
# set up a scrollview, with delagate | |
#self.sv = ui.ScrollView(flex='WH') | |
self.sv = ui.ScrollView(flex='WH') | |
self.sv.delegate = self | |
self.add_subview(self.sv) | |
# a list of data to be used | |
self.items = items | |
# num_items is only set to non zero value if | |
# you dont require data. | |
# eg. you could pass 1,000,000 to item_count | |
# to have a virtual view of a 1million items. | |
# of course you could use a range() for items | |
# but for large numbers, is not efficent. | |
self.num_items = item_count | |
# just demo/debug menu title btns | |
btn = ui.ButtonItem(title = 'Top') | |
btn.action = self.goto_top | |
# spacer button :( | |
btn1 = ui.ButtonItem(title =' ' * 5) | |
btn2 = ui.ButtonItem(title ='Bottom') | |
btn2.action = self.goto_end | |
self.right_button_items = [btn2, btn1, btn] | |
# some debug vars | |
self.created_cells = 0 | |
self.deleted_cells = 0 | |
self.used_buffered_cells = 0 | |
self.start = time.time() | |
# add a navigation bar | |
self.nav_bar = None | |
if _add_nav_bar: | |
self.nav_bar = NavClass( 32, increments = 5, callback = self.goto_percent) | |
self.add_subview(self.nav_bar) | |
#add table header | |
self.table_header = None | |
if _add_table_header: | |
self.table_header = TableHeader(self.width, 32, table_def) | |
self.add_subview(self.table_header) | |
def goto_top(self, sender): | |
self.sv.content_offset = (0,0) | |
def goto_end(self, sender): | |
self.sv.content_offset = (0, self.sv.content_size[1] - self.height) | |
def goto_percent(self, sender): | |
scroll_value = self.sv.content_size[1] | |
percent = float(sender.name) / 10. | |
new_scroll_value = (0, scroll_value * percent) | |
self.sv.content_offset = new_scroll_value | |
# this method returns the data item count | |
def item_count(self): | |
# we dont access len(self.items) in the code, | |
# we call this method. maybe no data items, | |
# just a int for the count. | |
return self.num_items if self.num_items > 0 else len(self.items) | |
# ui.View callback | |
def layout(self): | |
w,h = self.bounds[2:] | |
# if we have added a navigation bar | |
if self.nav_bar: | |
w -= self.nav_bar.width | |
# if we have a header row | |
if self.table_header: | |
h -= self.table_header.height | |
# set the content height of the scrollview | |
sv = self.sv | |
sv.width = w | |
sv.height = h | |
if self.table_header: | |
sv.y = self.table_header.height | |
# see if we are overriding the item_size | |
if self.override_width and self.override_height: | |
self.item_width , self.item_heigth = sv.bounds[2:] | |
elif self.override_width: | |
self.item_width = w | |
elif self.override_height: | |
self.item_height = h | |
# number of visible rows | |
self.visible_rows = int(h / self.item_height) | |
# adding 2 addtional visible rows here. nicer | |
# scrolling. extra line and comment, just so | |
# its not just a magic + 2 | |
self.visible_rows += 2 | |
# items per row | |
self.items_per_row =int(w / self.item_width) | |
# maximum num of rows | |
max_rows = (self.item_count() / self.items_per_row) | |
# add an extra row if not an exact multiple | |
if self.item_count() % self.items_per_row <> 0: | |
max_rows += 1 | |
sv.content_size =(w, max_rows * self.item_height) | |
# adjust the the navigation bar if we have one | |
if self.nav_bar: | |
nav_bar = self.nav_bar | |
nav_bar.x = self.width - nav_bar.width | |
#nav_bar.heigth = self.height | |
nav_bar.user_layout(self.height) | |
if self.table_header: | |
self.table_header.user_layout(self.width) | |
# the way the buffer is implemented it does | |
# not work correctly if it is smaller than | |
# the number of items on the screen. | |
# can be done, more a performance issue | |
# so the buffer is resized here if required. | |
min_buf_size = self.visible_rows * self.items_per_row | |
if self.buf_size < min_buf_size: | |
self.buf_size = min_buf_size | |
# clear the buffer, remove all the subviews | |
# needed when the orientation changes, is not | |
# called on all presentation styles. Not an | |
# issue, layout is called by ui when req. | |
for cell in self.buffer: | |
cell.release() | |
del cell | |
self.deleted_cells += 1 # debug | |
self.buffer = [] | |
# ui.View callback | |
def draw(self): | |
# useful to use the draw method of ui | |
# on startup and orientation change we | |
# are called as expected. | |
# this method is also called explictly from | |
# scrollview_did_scroll | |
v_offset = self.sv.content_offset[1] | |
# get the first visible row | |
row = int(v_offset / self.item_height) | |
# get the visible items to draw | |
visible_items = self.items_visible() | |
# draw each item | |
for item_index in visible_items: | |
self.draw_item(item_index) | |
# ui callback, ui.scrollview delegate | |
def scrollview_did_scroll(self, scrollview): | |
self.draw() | |
def draw_item(self, item_index): | |
# if the item_index is found in the buffer | |
# we know the cell is still present in the | |
# scrollview. so we exit, we dont need to | |
# recreate the cell | |
for cell in self.buffer: | |
if cell.item_index == item_index: | |
self.used_buffered_cells += 1 # debug | |
return | |
# create a cell (view), that is added to the | |
# subviews of the scrollview. once the buffer | |
# is full, the oldest cell is removed from | |
# the scrollview.subviews | |
data_item = None | |
if self.num_items == 0: | |
data_item = self.items[item_index] | |
if _USE_THREADED_CELL: | |
cell = ThreadedCell(self, item_index, data_item, self.item_width, self.item_height, cell_type = _cell_type) | |
else: | |
cell = VirtualCell(self, item_index, data_item, self.item_width, self.item_height, cell_type = _cell_type) | |
self.created_cells += 1 | |
self.sv.add_subview(cell) | |
cell.frame = self.frame_for_item(item_index) | |
# maintain the buffer | |
self.buffer.append(cell) | |
if len(self.buffer) > self.buf_size: | |
cell = self.buffer[0] | |
cell.release() | |
del cell | |
self.buffer.pop(0) | |
self.deleted_cells += 1 # debug | |
if len(self.sv.subviews) > len(self.buffer): | |
raise StandardError('Views are not being released from memory correctly.') | |
# get the frame for the given item index | |
def frame_for_item(self, item_index): | |
w, h = self.sv.bounds[2:] | |
items_per_row = self.items_per_row | |
row = item_index / items_per_row | |
col = item_index % items_per_row | |
if items_per_row == 1: | |
x_spacing = (w - (items_per_row * self.item_width)) / (items_per_row) | |
else: | |
x_spacing = (w - (items_per_row * self.item_width)) / (items_per_row-1) | |
return (col*(self.item_width + x_spacing), row*self.item_height, self.item_width, self.item_height) | |
# get the visible indices as a range... | |
def items_visible(self): | |
y = self.sv.content_offset[1] | |
w, h = self.sv.bounds[2:] | |
items_per_row = self.items_per_row | |
num_visible_rows = self.visible_rows | |
first_visible_row = max(0, int(y / self.item_height)) | |
range_start = first_visible_row * items_per_row | |
range_end = min(self.item_count(), range_start + num_visible_rows * items_per_row) | |
return range(range_start, range_end) | |
# ui.View callback | |
def will_close(self): | |
# do some clean up, make sure nothing | |
# left behind in memory. | |
# maybe this is not need, but easy to do it | |
for cell in self.buffer: | |
cell.release() | |
del cell | |
self.deleted_cells += 1 | |
self.buffer = [] | |
# print out some debug info | |
print 'Created Cells = {}, Deleted Cells = {}, Used buffered Cells = {}'.format(self.created_cells, self.deleted_cells, self.used_buffered_cells) | |
elapased_time = time.time() - self.start | |
#print strftime('%I:%M:%S',elapased_time ) | |
#ui.cancel_delays() | |
class NavClass(ui.View): | |
def __init__(self, width, increments = 10, callback = None): | |
self.hit_callback = callback | |
self.width = width | |
btn_font = ('<system-bold>', 12) | |
for i in range((100 / increments)): | |
inc = str((i * increments)/10.) | |
btn = ui.Button(name = str(inc)) | |
btn.title = str( i * increments) + '%' | |
btn.action = self.hit | |
btn.font = btn_font | |
self.add_subview(btn) | |
self.background_color = 'orange' | |
def user_layout(self, height): | |
# when the parent classes layout method is | |
# called, we manually call thus method to | |
# redraw our views objects | |
self.height = height | |
w = self.width | |
h = height / len(self.subviews) | |
for i, btn in enumerate(self.subviews): | |
btn.frame = (0, i * h, w, h ) | |
def hit(self, sender): | |
# communicate with parent | |
if self.hit_callback: | |
self.hit_callback(sender) | |
class TableHeader(ui.View): | |
def __init__(self,w, h, tb_def): | |
self.num_cols = len(tb_def) | |
self.table_def = tb_def | |
print self.num_cols | |
self.width = w | |
self.height = h | |
col_width = w / self.num_cols | |
header_font = ('Menlo', 18) | |
for i, rec in enumerate(tb_def): | |
col_header = ui.Label(name = str(i)) | |
col_header.border_width =.5 | |
col_header.border_color = 'white' | |
col_header.alignment = ui.ALIGN_CENTER | |
col_header.text = rec | |
col_header.background_color = 'black' | |
col_header.text_color = 'white' | |
col_header.font = header_font | |
col_header.border_width = .5 | |
self.add_subview(col_header) | |
def user_layout(self, w): | |
self.width = w | |
col_width = w / self.num_cols | |
for i, rec in enumerate(self.table_def): | |
f = (i * col_width, 0, col_width, self.height) | |
lb = self[str(i)] | |
lb.frame = f | |
class Stress(object): | |
# an auto scroller for VirtualView class | |
# to stress test it. | |
def __init__(self, obj, delay = .1): | |
import console, time | |
# trying for saftey | |
ui.cancel_delays() | |
# no sleeping... | |
console.set_idle_timer_disabled(True) | |
self.obj = obj | |
self.delay = delay | |
self.busy = False | |
# record the start time | |
self.start = time.time() | |
ui.delay(self.auto_scroll, 2) | |
def auto_scroll(self): | |
# this busy signal, probably not required. | |
# but will keep it in anyway. its a small price | |
# to pay for extra saftey. | |
#print sys.getrefcount(self) | |
if self.busy: | |
ui.cancel_delays() | |
#ui.delay(self.auto_scroll, self.delay) | |
return | |
# thanks @JonB | |
# if the view is not onscreen, we delete ourself | |
# moments later our __del__ is called... | |
if not self.obj.on_screen: | |
print 'off screen' | |
ui.cancel_delays() | |
#del self | |
return | |
self.busy = True | |
# the scrollview | |
sv = self.obj.sv | |
# current offset of the scrollview | |
v_offset = sv.content_offset[1] | |
# calc new_offset, to be + one page | |
new_offset = v_offset + sv.height | |
# wrap around if we reach the bottom if the list | |
if new_offset > sv.content_size[1]: | |
new_offset = 0 | |
sv.content_offset = (0, new_offset ) | |
ui.cancel_delays() | |
ui.delay(self.auto_scroll, self.delay) | |
self.busy = False | |
if __name__ == '__main__': | |
ui.cancel_delays() # just incase...the stress class sort of needs it | |
# moved these vars here, for testing purposes | |
_USE_THREADED_CELL = True | |
_STRESS_TEST = True | |
_add_table_header = False | |
_item_size = (0, 60) | |
_cell_type = '_row_' | |
_add_nav_bar = False | |
_item_count = 100 * 1000 | |
if __ver__ == 1.6: | |
console.set_font('Menlo', 22) | |
# as our item size is 128,128 defined at the top | |
# gives us a window size to view 4 rows x 4 cols | |
vw = 128 * 4 | |
vh = 66 * 10 | |
else: | |
vw , vh = 540, 576 | |
vv = VirtualView(vw, vh, _item_size, _buf_size, items = None , item_count = _item_count) | |
vv.background_color = 'white' | |
vv.present(_pres_style, hide_title_bar = False ) | |
# this will continue to auto scroll the Virtual View until it the view is closed. | |
if _STRESS_TEST: | |
Stress(vv, delay = 1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment