Skip to content

Instantly share code, notes, and snippets.

@Softwave
Created March 15, 2016 20:52
Show Gist options
  • Save Softwave/eb41cbdb70106ef74c2c to your computer and use it in GitHub Desktop.
Save Softwave/eb41cbdb70106ef74c2c to your computer and use it in GitHub Desktop.
ui.py
from _ui import *
import _ui
import re
import json
import inspect
import sys
import os
def in_background(fn):
import functools
import _ui
def new_fn(*args, **kwargs):
return _ui._dispatch(functools.partial(fn, *args, **kwargs))
return new_fn
class ImageContext (object):
def __init__(self, width, height, scale=0.0):
self.width = width
self.height = height
self.scale = scale
def __enter__(self):
begin_image_context(self.width, self.height, self.scale)
return self
def __exit__(self, type, value, traceback):
end_image_context()
def get_image(self):
return Image.from_image_context()
class GState (object):
def __enter__(self):
import _ui
_ui._save_gstate()
def __exit__(self, type, value, traceback):
import _ui
_ui._restore_gstate()
class ListDataSourceList (list):
def __init__(self, seq, datasource):
list.__init__(self, seq)
self.datasource = datasource
def append(self, item):
list.append(self, item)
self.datasource.reload()
def __setitem__(self, key, value):
list.__setitem__(self, key, value)
self.datasource.reload()
def __delitem__(self, key):
list.__delitem__(self, key)
self.datasource.reload()
def __setslice__(self, i, j, seq):
list.__setslice__(self, i, j, seq)
self.datasource.reload()
def __delslice__(self, i, j):
list.__delslice__(self, i, j)
self.datasource.reload()
class ListDataSource (object):
def __init__(self, items=None):
self.tableview = None
self.reload_disabled = False
self.delete_enabled = True
self.move_enabled = False
self.action = None
self.edit_action = None
self.accessory_action = None
self.tapped_accessory_row = -1
self.selected_row = -1
if items is not None:
self.items = items
else:
self.items = ListDataSourceList([])
self.text_color = None
self.highlight_color = None
self.font = None
self.number_of_lines = 1
def reload(self):
if self.tableview and not self.reload_disabled:
self.tableview.reload()
@property
def items(self):
return self._items
@items.setter
def items(self, value):
self._items = ListDataSourceList(value, self)
self.reload()
def tableview_number_of_sections(self, tv):
self.tableview = tv
return 1
def tableview_number_of_rows(self, tv, section):
return len(self.items)
def tableview_can_delete(self, tv, section, row):
return self.delete_enabled
def tableview_can_move(self, tv, section, row):
return self.move_enabled
def tableview_accessory_button_tapped(self, tv, section, row):
self.tapped_accessory_row = row
if self.accessory_action:
self.accessory_action(self)
def tableview_did_select(self, tv, section, row):
self.selected_row = row
if self.action:
self.action(self)
def tableview_move_row(self, tv, from_section, from_row, to_section, to_row):
if from_row == to_row:
return
moved_item = self.items[from_row]
self.reload_disabled = True
del self.items[from_row]
self.items[to_row:to_row] = [moved_item]
self.reload_disabled = False
if self.edit_action:
self.edit_action(self)
def tableview_delete(self, tv, section, row):
self.reload_disabled = True
del self.items[row]
self.reload_disabled = False
tv.delete_rows([row])
if self.edit_action:
self.edit_action(self)
def tableview_cell_for_row(self, tv, section, row):
item = self.items[row]
cell = TableViewCell()
cell.text_label.number_of_lines = self.number_of_lines
if isinstance(item, dict):
cell.text_label.text = item.get('title', '')
img = item.get('image', None)
if img:
if isinstance(img, basestring):
cell.image_view.image = Image.named(img)
elif isinstance(img, Image):
cell.image_view.image = img
accessory = item.get('accessory_type', 'none')
cell.accessory_type = accessory
else:
cell.text_label.text = str(item)
if self.text_color:
cell.text_label.text_color = self.text_color
if self.highlight_color:
bg_view = View(background_color=self.highlight_color)
cell.selected_background_view = bg_view
if self.font:
cell.text_label.font = self.font
return cell
RECT_REGEX = re.compile(r'\{\{(\-?\d+\.?\d*),\s?(\-?\d+\.?\d*)\},\s?\{(\-?\d+\.?\d*),\s?(\-?\d+\.?\d*)\}\}')
COLOR_REGEX = r'RGBA\((\d+\.?\d*),(\d+\.?\d*),(\d+\.?\d*),(\d+\.?\d*)\)'
ALIGNMENTS = {'left': ALIGN_LEFT, 'right': ALIGN_RIGHT, 'center': ALIGN_CENTER}
CORRECTION_TYPES = {'yes': True, 'no': False, 'default': None}
def _str2rect(rect_str):
m = re.match(RECT_REGEX, rect_str)
if m:
return tuple([float(s) for s in m.groups()])
return None
def _str2color(color_str, default=None):
if not color_str:
return default
m = re.match(COLOR_REGEX, color_str)
if m:
return tuple([float(s) for s in m.groups()])
return default
def _bind_action(v, action_str, f_globals, f_locals, attr_name='action'):
if action_str:
try:
action = eval(action_str, f_globals, f_locals)
if callable(action):
setattr(v, attr_name, action)
else:
sys.stderr.write('Warning: Could not bind action: Not callable\n')
except Exception, e:
sys.stderr.write('Warning: Could not bind action: %s\n' % (e,))
def _view_from_dict(view_dict, f_globals, f_locals):
attrs = view_dict.get('attributes', {})
classname = view_dict.get('class', 'View')
ViewClass = _ui.__dict__.get(classname)
if not ViewClass:
return None
custom_class_str = attrs.get('custom_class')
if custom_class_str:
try:
CustomViewClass = eval(custom_class_str, f_globals, f_locals)
if inspect.isclass(CustomViewClass) and issubclass(CustomViewClass, View):
ViewClass = CustomViewClass
else:
sys.stderr.write('Warning: Invalid custom view class "%s"' % (custom_class_str,))
except Exception, e:
sys.stderr.write('Warning: Could not resolve custom view class: %s\n' % (e,))
if classname == 'NavigationView':
# Special case for ui.NavigationView: Subviews are added to an
# implicitly-created root view instead of the NavigationView itself.
root_view = View()
root_view.name = attrs.get('root_view_name')
root_view.background_color = _str2color(attrs.get('background_color'), 'white')
subview_dicts = view_dict.get('nodes', [])
if subview_dicts:
for d in subview_dicts:
subview = _view_from_dict(d, f_globals, f_locals)
if subview:
root_view.add_subview(subview)
del view_dict['nodes']
v = NavigationView(root_view)
v.title_color = _str2color(attrs.get('title_color'))
v.bar_tint_color = _str2color(attrs.get('title_bar_color'))
else:
v = ViewClass()
v.frame = _str2rect(view_dict.get('frame'))
v.flex = attrs.get('flex', '')
v.alpha = attrs.get('alpha', 1.0)
v.name = attrs.get('name')
v.background_color = _str2color(attrs.get('background_color'), 'clear')
v.tint_color = _str2color(attrs.get('tint_color'))
v.border_width = attrs.get('border_width', 0)
v.border_color = _str2color(attrs.get('border_color'))
v.corner_radius = attrs.get('corner_radius', 0)
if classname == 'Label':
v.text = attrs.get('text', '')
font_name = attrs.get('font_name', '<System>')
font_size = attrs.get('font_size', 17)
v.font = (font_name, font_size)
v.alignment = ALIGNMENTS.get(attrs.get('alignment', 'left'), ALIGN_LEFT)
v.number_of_lines = attrs.get('number_of_lines', 0)
v.text_color = _str2color(attrs.get('text_color'), 'black')
elif classname == 'TextField':
v.text = attrs.get('text', '')
font_name = attrs.get('font_name', '<System>')
font_size = attrs.get('font_size', 17)
v.font = (font_name, font_size)
v.alignment = ALIGNMENTS.get(attrs.get('alignment', 'left'), ALIGN_LEFT)
v.text_color = _str2color(attrs.get('text_color'), 'black')
v.placeholder = attrs.get('placeholder', '')
v.autocorrection_type = CORRECTION_TYPES[attrs.get('autocorrection_type', 'default')]
v.spellchecking_type = CORRECTION_TYPES[attrs.get('spellchecking_type', 'default')]
v.secure = attrs.get('secure', False)
elif classname == 'TextView':
v.text = attrs.get('text', '')
font_name = attrs.get('font_name', '<System>')
font_size = attrs.get('font_size', 17)
v.font = (font_name, font_size)
v.alignment = ALIGNMENTS.get(attrs.get('alignment', 'left'), ALIGN_LEFT)
v.text_color = _str2color(attrs.get('text_color'), 'black')
v.autocorrection_type = CORRECTION_TYPES[attrs.get('autocorrection_type', 'default')]
v.spellchecking_type = CORRECTION_TYPES[attrs.get('spellchecking_type', 'default')]
elif classname == 'Button':
v.title = attrs.get('title', '')
image_name = attrs.get('image_name')
if image_name:
v.image = Image.named(image_name)
font_size = attrs.get('font_size', 15)
font_name = '<System%s>' % ('-Bold' if attrs.get('font_bold') else '',)
v.font = (font_name, font_size)
_bind_action(v, attrs.get('action'), f_globals, f_locals)
elif classname == 'Slider':
v.value = attrs.get('value', 0.5)
v.continuous = attrs.get('continuous', False)
_bind_action(v, attrs.get('action'), f_globals, f_locals)
elif classname == 'Switch':
v.value = attrs.get('value', True)
_bind_action(v, attrs.get('action'), f_globals, f_locals)
elif classname == 'SegmentedControl':
v.segments = attrs.get('segments').split('|')
v.selected_index = 0
_bind_action(v, attrs.get('action'), f_globals, f_locals)
elif classname == 'WebView':
v.scales_page_to_fit = attrs.get('scales_to_fit')
elif classname == 'TableView':
v.row_height = attrs.get('row_height', 44)
v.editing = attrs.get('editing', False)
list_items = attrs.get('data_source_items', '').split('\n')
# TODO: Parse items for accessory type ('>' or '(i)' suffix)
data_source = ListDataSource(list_items)
_bind_action(data_source, attrs.get('data_source_action'), f_globals, f_locals)
_bind_action(data_source, attrs.get('data_source_edit_action'), f_globals, f_locals, 'edit_action')
_bind_action(data_source, attrs.get('data_source_accessory_action'), f_globals, f_locals, 'accessory_action')
data_source.font = ('<System>', attrs.get('data_source_font_size', 18))
data_source.delete_enabled = attrs.get('data_source_delete_enabled', False)
data_source.move_enabled = attrs.get('data_source_move_enabled', False)
data_source.number_of_lines = attrs.get('data_source_number_of_lines')
v.data_source = data_source
v.delegate = data_source
elif classname == 'DatePicker':
v.mode = attrs.get('mode', DATE_PICKER_MODE_DATE)
_bind_action(v, attrs.get('action'), f_globals, f_locals)
elif classname == 'ScrollView':
v.content_size = int(attrs.get('content_width', '0')), int(attrs.get('content_height', '0'))
elif classname == 'ImageView':
image_name = attrs.get('image_name')
if image_name:
v.image = Image.named(image_name)
custom_attr_str = attrs.get('custom_attributes')
if custom_attr_str:
try:
f_locals['this'] = v
custom_attributes = eval(custom_attr_str, f_globals, f_locals)
if isinstance(custom_attributes, dict):
for key, value in custom_attributes.iteritems():
setattr(v, key, value)
elif custom_attributes:
sys.stderr.write('Warning: Custom attributes of view "%s" are not a dict\n' % (v.name,))
except Exception, e:
sys.stderr.write('Warning: Could not load custom attributes of view "%s": %s\n' % (v.name, e))
finally:
del f_locals['this']
subview_dicts = view_dict.get('nodes', [])
for d in subview_dicts:
subview = _view_from_dict(d, f_globals, f_locals)
if subview:
v.add_subview(subview)
if custom_class_str and hasattr(v, 'did_load'):
v.did_load()
return v
def load_view_str(json_str, bindings=None, stackframe=None):
root_list = json.loads(json_str)
if stackframe is None and not bindings:
stackframe = inspect.currentframe().f_back
if root_list:
root_view_dict = root_list[0]
if bindings:
g = bindings
l = bindings
else:
g = stackframe.f_globals
l = stackframe.f_locals
return _view_from_dict(root_view_dict, g, l)
def load_view(pyui_path=None, bindings=None, stackframe=None):
if stackframe is None and not bindings:
stackframe = inspect.currentframe().f_back
if pyui_path is None:
f = inspect.currentframe().f_back
script_path = f.f_globals.get('__file__')
pyui_path = os.path.splitext(script_path)[0] + '.pyui'
if len(os.path.splitext(pyui_path)[1]) == 0:
# The '.pyui' extension can be omitted, but if there is an extension (e.g. '.json'), it should be kept.
pyui_path += '.pyui'
with open(pyui_path) as f:
json_str = f.read()
return load_view_str(json_str, bindings, stackframe)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment