def __init__(self): self._loading_image = None self._error_image = None self._q_load = collections.deque() self._q_done = collections.deque() self._client = SafeList() self._running = False self._start_wanted = False getClock().schedule_interval(self._update, 1 / 25.)
def __init__(self, **kwargs): kwargs.setdefault('flipangle', 90.) super(MTFlippableWidget, self).__init__(**kwargs) self.flipangle = kwargs.get('flipangle') # For flipping animations self.zangle = 0 self.side = 'front' # Holds children for both sides self.children_front = SafeList() self.children_back = SafeList() self._anim_current = None self._anim_back = Animation(zangle=180) self._anim_front = Animation(zangle=0)
def __init__(self, **kwargs): kwargs.setdefault('show_tabs', False) kwargs.setdefault('duration', 1.) super(MTScreenLayout, self).__init__(**kwargs) self.screens = SafeList() self.screen = None self.previous_screen = None self._switch_t = 1.1 self.duration = kwargs.get('duration') self.container = MTBoxLayout(orientation='vertical') super(MTScreenLayout, self).add_widget(self.container) self.tabs = self.new_tab_layout() self._show_tabs = False self.show_tabs = kwargs.get('show_tabs', False)
def __init__(self, **kwargs): kwargs.setdefault('group', None) super(MTToggleButton, self).__init__(**kwargs) # add the widget to the group if exist. self._group = kwargs.get('group') if self._group is not None: if not self._group in self._groups: MTToggleButton._groups[self._group] = SafeList() ref = weakref.ref(self) MTToggleButton._groups[self._group].append(ref)
def __init__(self, device, id, args): if self.__class__ == Touch: raise NotImplementedError, 'class Touch is abstract' # Uniq ID Touch.__uniq_id += 1 self.uid = Touch.__uniq_id self.device = device # For push/pop self.attr = [] self.default_attrs = ( 'x', 'y', 'z', 'dxpos', 'dypos', 'dzpos', 'oxpos', 'oypos', 'ozpos') # For grab self.grab_list = SafeList() self.grab_exclusive_class = None self.grab_state = False self.grab_current = None # TUIO definition self.id = id self.sx = 0.0 self.sy = 0.0 self.sz = 0.0 self.profile = ('pos', ) # new parameters self.x = 0.0 self.y = 0.0 self.z = 0.0 self.shape = None self.dxpos = None self.dypos = None self.dzpos = None self.oxpos = None self.oypos = None self.ozpos = None self.dsxpos = None self.dsypos = None self.dszpos = None self.osxpos = None self.osypos = None self.oszpos = None self.time_start = getClock().get_time() self.is_double_tap = False self.double_tap_time = 0 self.userdata = {} self.depack(args)
def __init__(self): loading_png_fn = os.path.join(pymt_data_dir, 'loader.png') error_png_fn = os.path.join(pymt_data_dir, 'error.png') self.loading_image = ImageLoader.load(loading_png_fn) self.error_image = ImageLoader.load(error_png_fn) self._q_load = collections.deque() self._q_done = collections.deque() self._client = SafeList() self._running = False self._start_wanted = False getClock().schedule_interval(self._update, 1 / 25.)
def fullscreen(self, *largs, **kwargs): root_win = self.parent.get_parent_window() # save state for restore self.old_children = root_win.children self.old_size = self.size # set new children root_win.children = SafeList() root_win.add_widget(self.container) btn_unfullscreen = MTButton(pos=(root_win.width - 50, root_win.height - 50), size=(50, 50), label='Back') btn_unfullscreen.push_handlers(on_release=self.unfullscreen) root_win.add_widget(btn_unfullscreen) self.size = root_win.size self.container.size = self.size
class MTKineticList(MTStencilContainer): '''This is a kinetic container widget, that allows you to make a kinetic list scrolling in either direction. :Parameters: `align` : string, default to 'center' Alignement of widget inside the row (or col). Can be one of 'center', 'left', 'right' `friction` : float, defaults to 10 The Pseudo-friction of the pseudo-kinetic scrolling. Formula for friction is :: acceleration = 1 + friction * frame_delta_time `padding_x` : int, defaults to 4 The spacing between scrolling items on the x axis `padding_y` : int, defaults to 4 The spacing between scrolling items on the y axis `w_limit` : int, defaults to 1 The limit of items that will appear horizontally. When this is set to a non-zero value the width(in terms of items in the kinetic list) will be w_limit, and the height will continually expand. `h_limit` : int, defaults to 0 Exect opposite of w_limit. If I didn't make either this or w_limit clear go bug xelapond `do_x` : bool, defaults to False Enable scrolling on the X axis `do_y` : bool, defaults to True Enable scrolling on the Y axis `title` : string, defaults to <Title Goes Here> Sets the title of the widget, which appears in 20 point font at the top `deletable` : bool, defaults to True When enabled it allows you to delete children by entering delete mode(red button in upper left) `searchable` : bool, defaults to True When enabled it allows you to enter search mode and filter items `trigger_distance` : int, default to 3 Maximum trigger distance to dispatch event on children (this mean if you move too much, trigger will not happen.) :Styles: `bg-color` : color Background color of the widget `scrollbar-size` : int Size of scrollbar in pixel (use 0 to disable it.) `scrollbar-color` : color Color of scrollbar `scrollbar-margin` : int int int int Margin top/right/bottom/left of scrollbar (left are not used.) :Events: `on_delete` (child) Fired when an item gets deleted. ''' def __init__(self, **kwargs): kwargs.setdefault('friction', 10) kwargs.setdefault('padding_x', 4) kwargs.setdefault('padding_y', 4) kwargs.setdefault('w_limit', 1) kwargs.setdefault('h_limit', 0) kwargs.setdefault('do_x', False) kwargs.setdefault('do_y', True) kwargs.setdefault('title', 'No title') kwargs.setdefault('deletable', True) kwargs.setdefault('searchable', True) kwargs.setdefault('trigger_distance', 3) kwargs.setdefault('align', 'center') super(MTKineticList, self).__init__(**kwargs) self.register_event_type('on_delete') self._a_sinput_out = None self._a_sinput_in = None self.title = Label('') self.sb = None self.sinput = None self.do_x = kwargs.get('do_x') self.do_y = kwargs.get('do_y') self.titletext = kwargs.get('title') self.deletable = kwargs.get('deletable') self.searchable = kwargs.get('searchable') self.friction = kwargs.get('friction') self.padding_x = kwargs.get('padding_x') self.padding_y = kwargs.get('padding_y') self.w_limit = kwargs.get('w_limit') self.h_limit = kwargs.get('h_limit') self.align = kwargs.get('align') self.trigger_distance = kwargs.get('trigger_distance') if self.w_limit and self.h_limit: raise Exception('You cannot limit both axes') elif not(self.w_limit or self.h_limit): raise Exception('You must limit at least one axis') # How far to offset tself.deletable and he axes(used for scrolling/panning) self.xoffset = 0 self.yoffset = 0 # X and Y translation vectors for the kinetic movement self.vx = 0 self.vy = 0 # List of all children, whatever will be the search self.pchildren = [] # For extra blob stats self.touch = {} # Holds widgets not a part of the scrolling(search button, etc) self.widgets = [] self._last_content_size = 0 self._scrollbar_index = 0 self._scrollbar_size = 0 # create the UI part. self._create_ui() def _create_ui(self): # Title Text if self.titletext is not None: self.title = Label( font_size=18, bold=True, anchor_x='center', anchor_y='center', label=self.titletext) self.title.x = self.width/2 + self.x self.title.y = self.height - 20 + self.y # Delete Button if self.deletable: self.db = MTToggleButton( label='X', pos=(self.x + self.width - 80, self.y + self.height - 40), size=(80, 40), cls='kineticlist-delete') self.db.push_handlers(on_press=self.toggle_delete) self.widgets.append(self.db) # Search Button and Input Text Area if self.searchable: self.sb = MTToggleButton( label='S', #Button pos=(self.x, self.y + self.width - 40), size=(80, 40), cls='kineticlist-search') self.sb.push_handlers(on_press=self.toggle_search) self.sb.parent = self self.widgets.append(self.sb) self.sinput = pymt.MTTextInput(pos= (self.x, self.y + self.height - 40), size=(80, 40), style={'font-size': 20}) self.sinput.parent = self self.sinput.push_handlers(on_text_change=self.apply_filter) self.widgets.insert(0, self.sinput) # Animations to hide and show the search text input box self._a_sinput_in = Animation(y=self.y + self.height - 40 - self.sinput.size[1], duration=0.5, f='ease_out_cubic') self._a_sinput_out = Animation(y=self.y + self.height - self.sinput.size[1], duration=0.5, f='ease_out_cubic') def on_delete(self, child): pass def clear(self): self.children = SafeList() self.pchildren = SafeList() self.xoffset = self.yoffset = 0 def add_widget(self, widget, **kwargs): super(MTKineticList, self).add_widget(widget, **kwargs) self.pchildren.append(widget) def remove_widget(self, widget): super(MTKineticList, self).remove_widget(widget) if widget in self.pchildren: self.pchildren.remove(widget) self.dispatch_event('on_delete', widget) def toggle_delete(self, touch): '''Toggles the delete buttons on items Attached to the on_press handler of the delete button(self.db) ''' if self.db.state == 'down': for child in self.children[:]: child.show_delete() else: for child in self.children[:]: child.hide_delete() def toggle_search(self, touch): '''Toggles the search area Attached to the on_press handler of self.sb(the green search button) ''' if self.sb.state == 'down': self._a_sinput_in.animate(self.sinput) else: try: self.sinput.hide_keyboard() except: # There isn't a keyboard, so it throws a ValueError pass self.sinput.label = '' self._a_sinput_out.animate(self.sinput) def apply_filter(self, text): '''Applies the filter to the current children set''' self.search(text, 'label') #Move them so you don't have to scroll up to see them self.yoffset = self.padding_y self.xoffset = self.padding_x def filter(self, pattern, attr): '''Given an attribute of the children, and a pattern, return a list of the children with which pattern is in attr ''' return filter(lambda c: pattern in str(getattr(c, attr)), self.pchildren) def search(self, pattern, attr): '''Apply a search pattern to the current set of children''' result = self.filter(pattern, attr) self.children.clear() for item in result: self.children.append(item) def endsearch(self): '''Resets the children set to the full set''' self.children.clear() for item in self.pchildren: self.children.append(item) def _get_total_width(self, items, axis): '''Given a list of items and an axis, return the space they take up(in pixels) ''' total = 0 if axis == 'width': for item in items: total += (item.width + self.padding_x) elif axis == 'height': for item in items: total += (item.height + self.padding_y) return total def goto_head(self): if not self.h_limit: self.yoffset = -self._get_total_width(self.children, 'height')/self.w_limit + self.size[1] - 100 else: self.xoffset = self._get_total_width(self.children, 'width')/self.h_limit + self.size[0] - 100 def do_layout(self): '''Apply layout to all the items''' t = index = 0 # adapt value for direction w2 = self.width / 2. h2 = self.height / 2. inverse = 0 limit = self.w_limit width_attr = 'width' height_attr = 'height' xoffset = self.xoffset sx = self.x y = self.y + self.yoffset padding_x = self.padding_x padding_y = self.padding_y # inverse if not self.w_limit: inverse = 1 limit = self.h_limit width_attr = 'height' height_attr = 'width' xoffset = self.yoffset y = self.x + self.xoffset padding_x = self.padding_y padding_y = self.padding_x w2, h2 = h2, w2 sx = self.y # calculate size of actual content size = 0 for i in xrange(len(self.children)): if i % limit == 0: maxrange = min(i + limit, len(self.children)) childrens = [self.children[z] for z in range(i, maxrange)] h = max((c.__getattribute__(height_attr) for c in childrens)) size += h + padding_y self._last_content_size = size # add little padding for good looking. y += padding_y ny = y # recalculate position for each children for child in self.children[:]: # each row, calculate the height, advance y and reset x if index % limit == 0: # set y axis to the previous calculated position y = ny # get children in the row maxrange = min(t + limit, len(self.children)) childrens = [self.children[z] for z in range(t, maxrange)] # take the largest height in the current row if len(childrens): h = max((c.__getattribute__(height_attr) for c in childrens)) else: h = 0 # prepare the y axis for next loop ny = y + h + padding_y # reset x for this row. if self.align == 'center': x = sx + w2 + xoffset - \ (self._get_total_width(childrens, width_attr) / 2.) elif self.align == 'left': x = 0 elif self.align == 'right': x = getattr(self, width_attr) - getattr(child, width_attr) - xoffset t += limit # reposition x if not inverse: child.kx = x + padding_x child.ky = y else: child.ky = x + padding_x child.kx = y x += child.__getattribute__(width_attr) + padding_x # Increment index index += 1 def on_touch_down(self, touch): if not self.collide_point(touch.x, touch.y): return # ok, it's a touch for us. grab it ! touch.grab(self) # first, check if own widget take the touch for w in reversed(self.widgets[:]): if w.on_touch_down(touch): return True # initiate kinetic movement. self.vx = self.vy = 0 self.touch[touch.id] = { 'ox': touch.x, 'oy': touch.y, 'xmot': 0, 'ymot': 0, 'travelx' : 0, #How far the blob has traveled total in the x axis 'travely' : 0, #^ } return True def on_touch_move(self, touch): # accept only grabbed touch by us if touch.grab_current != self: return # ok, if it's not a kinetic movement, # dispatch to children if touch.id not in self.touch: for w in reversed(self.widgets[:]): if w.on_touch_move(touch): return True return # it's a kinetic movement, process it. t = self.touch[touch.id] t['xmot'] = touch.x - t['ox'] t['ymot'] = touch.y - t['oy'] t['ox'] = touch.x t['oy'] = touch.y t['travelx'] += abs(t['xmot']) t['travely'] += abs(t['ymot']) self.xoffset += t['xmot'] * self.do_x self.yoffset += t['ymot'] * self.do_y self.ensure_bounding() def on_touch_up(self, touch): # accept only grabbed touch by us if touch.grab_current != self: return # it's an up, ungrab us ! touch.ungrab(self) if touch.id not in self.touch: for w in reversed(self.widgets[:]): if w.on_touch_up(touch): return True return t = self.touch[touch.id] self.vx = t['xmot'] self.vy = t['ymot'] # check if we can transmit event to children if (self.do_x and t['travelx'] > self.trigger_distance) or \ (self.do_y and t['travely'] > self.trigger_distance): return True # ok, the trigger distance is enough, we can dispatch event. # will not work if children grab the touch in down state :/ for child in reversed(self.children[:]): must_break = child.dispatch_event('on_touch_down', touch) old_grab_current = touch.grab_current touch.grab_current = child child.dispatch_event('on_touch_up', touch) touch.grab_current = old_grab_current if must_break: break return True def ensure_bounding(self): size = float(self._last_content_size) if size <= 0: return self._scrollbar_size = 1 self._scrollbar_index = 0 if self.do_y: if size < self.height: self.yoffset = 0 else: self.yoffset = boundary(self.yoffset, -size + self.height, 0) self._scrollbar_size = self.height / size self._scrollbar_index = -self.yoffset / size if self.do_x: if size < self.width: self.xoffset = 0 else: self.xoffset = boundary(self.xoffset, -size + self.width, 0) self._scrollbar_size = self.width / size self._scrollbar_index = -self.xoffset / size def process_kinetic(self): '''Apply kinetic movement to all the items''' dt = getFrameDt() self.vx /= 1 + (self.friction * dt) self.vy /= 1 + (self.friction * dt) self.xoffset += self.vx * self.do_x self.yoffset += self.vy * self.do_y self.ensure_bounding() def draw(self): # background set_color(*self.style.get('bg-color')) drawCSSRectangle(pos=self.pos, size=self.size, style=self.style) # draw children self.stencil_push() for w in self.children[:]: # internal update of children w.update() # optimization to draw only viewed children if self.do_y and (w.y + w.height < self.y or w.y > self.y + self.height): continue if self.do_x and (w.x + w.width < self.x or w.x > self.x + self.width): continue w.on_draw() self.stencil_pop() # draw widgets for w in self.widgets: w.dispatch_event('on_draw') # title bar if self.titletext is not None: set_color(*self.style.get('title-color')) w = 0 if self.searchable: x = 80 w += 80 else: x = 0 if self.deletable: w += 80 drawCSSRectangle(pos=(self.x + x, self.height + self.y - 40), size=(self.width - w, 40), prefix='title', style=self.style) self.title.x = self.width/2 + self.x self.title.y = self.height - 20 + self.y self.title.draw() # scrollbar sb_size = self.style.get('scrollbar-size') if sb_size > 0: mtop, mright, mbottom, mleft = self.style.get('scrollbar-margin') if self.do_y: pos = [self.x + self.width - mright - sb_size, self.y + mbottom] size = [sb_size, self.height - mbottom - mtop] pos[1] += size[1] * self._scrollbar_index size[1] = size[1] * self._scrollbar_size if self.do_x: pos = [self.x + mleft, self.y + self.height - mtop - sb_size] size = [self.width - mleft - mright, sb_size] pos[0] += size[0] * self._scrollbar_index size[0] = size[0] * self._scrollbar_size set_color(*self.style.get('scrollbar-color')) drawRectangle(pos=pos, size=size) def on_draw(self): if not self.visible: return self.do_layout() self.process_kinetic() self.draw()
def clear(self): self.children = SafeList() self.pchildren = SafeList() self.xoffset = self.yoffset = 0
def __init__(self, **kwargs): kwargs.setdefault('force', False) kwargs.setdefault('config', None) kwargs.setdefault('show_fps', False) kwargs.setdefault('style', {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get('force'): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) self._rotation = 0 # event subsystem self.register_event_type('on_flip') self.register_event_type('on_rotate') self.register_event_type('on_draw') self.register_event_type('on_update') self.register_event_type('on_resize') self.register_event_type('on_close') self.register_event_type('on_touch_down') self.register_event_type('on_touch_move') self.register_event_type('on_touch_up') self.register_event_type('on_mouse_down') self.register_event_type('on_mouse_move') self.register_event_type('on_mouse_up') self.register_event_type('on_keyboard') self.register_event_type('on_key_down') self.register_event_type('on_key_up') # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get('style') if len(kwargs.get('style')): self.apply_css(kwargs.get('style')) self.children = SafeList() self.parent = self self.visible = True # add view if 'view' in kwargs: self.add_widget(kwargs.get('view')) # get window params, user options before config option params = {} if 'fullscreen' in kwargs: params['fullscreen'] = kwargs.get('fullscreen') else: params['fullscreen'] = pymt.pymt_config.get( 'graphics', 'fullscreen') if params['fullscreen'] not in ('auto', 'fake'): params['fullscreen'] = params['fullscreen'].lower() in \ ('true', '1', 'yes', 'yup') if 'width' in kwargs: params['width'] = kwargs.get('width') else: params['width'] = pymt.pymt_config.getint('graphics', 'width') if 'height' in kwargs: params['height'] = kwargs.get('height') else: params['height'] = pymt.pymt_config.getint('graphics', 'height') if 'vsync' in kwargs: params['vsync'] = kwargs.get('vsync') else: params['vsync'] = pymt.pymt_config.getint('graphics', 'vsync') if 'fps' in kwargs: params['fps'] = kwargs.get('fps') else: params['fps'] = pymt.pymt_config.getint('graphics', 'fps') if 'rotation' in kwargs: params['rotation'] = kwargs.get('rotation') else: params['rotation'] = pymt.pymt_config.getint( 'graphics', 'rotation') params['position'] = pymt.pymt_config.get('graphics', 'position', 'auto') if 'top' in kwargs: params['position'] = 'custom' params['top'] = kwargs.get('top') else: params['top'] = pymt.pymt_config.getint('graphics', 'top') if 'left' in kwargs: params['position'] = 'custom' params['left'] = kwargs.get('left') else: params['left'] = pymt.pymt_config.getint('graphics', 'left') # show fps if asked self.show_fps = kwargs.get('show_fps') if pymt.pymt_config.getboolean('pymt', 'show_fps'): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True
class Touch(object): '''Abstract class to represent a touch, and support TUIO 1.0 definition. :Parameters: `id` : str uniq ID of the touch `args` : list list of parameters, passed to depack() function ''' __metaclass__ = TouchMetaclass __uniq_id = 0 __attrs__ = \ ('device', 'attr', 'id', 'sx', 'sy', 'sz', 'profile', 'x', 'y', 'z', 'shape', 'dxpos', 'dypos', 'dzpos', 'oxpos', 'oypos', 'ozpos', 'dsxpos', 'dsypos', 'dszpos', 'osxpos', 'osypos', 'oszpos', 'time_start', 'is_double_tap', 'double_tap_time', 'userdata') def __init__(self, device, id, args): if self.__class__ == Touch: raise NotImplementedError, 'class Touch is abstract' # Uniq ID Touch.__uniq_id += 1 self.uid = Touch.__uniq_id self.device = device # For push/pop self.attr = [] self.default_attrs = ( 'x', 'y', 'z', 'dxpos', 'dypos', 'dzpos', 'oxpos', 'oypos', 'ozpos') # For grab self.grab_list = SafeList() self.grab_exclusive_class = None self.grab_state = False self.grab_current = None # TUIO definition self.id = id self.sx = 0.0 self.sy = 0.0 self.sz = 0.0 self.profile = ('pos', ) # new parameters self.x = 0.0 self.y = 0.0 self.z = 0.0 self.shape = None self.dxpos = None self.dypos = None self.dzpos = None self.oxpos = None self.oypos = None self.ozpos = None self.dsxpos = None self.dsypos = None self.dszpos = None self.osxpos = None self.osypos = None self.oszpos = None self.time_start = getClock().get_time() self.is_double_tap = False self.double_tap_time = 0 self.userdata = {} self.depack(args) def depack(self, args): '''Depack `args` into attributes in class''' if self.dsxpos is None: self.dsxpos = self.osxpos = self.sx self.dsypos = self.osypos = self.sy self.dszpos = self.oszpos = self.sz def grab(self, class_instance, exclusive=False): '''Grab a touch. You can grab a touch if you absolutly want to receive on_touch_move() and on_touch_up(), even if the touch is not dispatched by your parent :: def on_touch_down(self, touch): touch.grab(self) def on_touch_move(self, touch): if touch.grab_current == self: # i receive my grabbed touch else: # it's a normal touch def on_touch_up(self, touch): if touch.grab_current == self: # i receive my grabbed touch, i must ungrab it ! touch.ungrab(self) else: # it's a normal touch ''' if self.grab_exclusive_class is not None: raise Exception('Cannot grab the touch, touch are exclusive') class_instance = weakref.ref(class_instance) if exclusive: self.grab_exclusive_class = class_instance self.grab_list.append(class_instance) def ungrab(self, class_instance): '''Ungrab a previous grabbed touch''' class_instance = weakref.ref(class_instance) if self.grab_exclusive_class == class_instance: self.grab_exclusive_class = None if class_instance in self.grab_list: self.grab_list.remove(class_instance) def move(self, args): '''Move the touch to another position.''' self.dxpos = self.x self.dypos = self.y self.dzpos = self.z self.dsxpos = self.sx self.dsypos = self.sy self.dszpos = self.sz self.depack(args) def scale_for_screen(self, w, h, p=None): '''Scale position for the screen''' self.x = self.sx * float(w) self.y = self.sy * float(h) if p: self.z = self.sz * float(p) if self.oxpos is None: self.dxpos = self.oxpos = self.x self.dypos = self.oypos = self.y self.dzpos = self.ozpos = self.z def push(self, attrs=None): '''Push attributes values in `attrs` in the stack''' if attrs is None: attrs = self.default_attrs values = [getattr(self, x) for x in attrs] self.attr.append((attrs, values)) def pop(self): '''Pop attributes values from the stack''' attrs, values = self.attr.pop() for i in xrange(len(attrs)): setattr(self, attrs[i], values[i]) def apply_transform_2d(self, transform): '''Apply a transformation on x, y, dxpos, dypos, oxpos, oypos''' self.x, self.y = transform(self.x, self.y) self.dxpos, self.dypos = transform(self.dxpos, self.dypos) self.oxpos, self.oypos = transform(self.oxpos, self.oypos) def copy_to(self, to): '''Copy some attribute to another touch object.''' for attr in self.__attrs__: to.__setattr__(attr, copy(self.__getattribute__(attr))) def __str__(self): classname = str(self.__class__).split('.')[-1].replace('>', '').replace('\'', '') return '<%s spos=%s pos=%s>' % (classname, str(self.spos), str(self.pos)) def distance(self, other_touch): return Vector(self.pos).distance(other_touch.pos) def __repr__(self): out = [] for x in dir(self): v = getattr(self, x) if x[0] == '_': continue if isroutine(v): continue out.append('%s="%s"' % (x, v)) return '<%s %s>' % ( self.__class__.__name__, ' '.join(out) ) # facility @property def pos(self): '''Return position of the touch in the screen coordinate system (self.x, self.y)''' return self.x, self.y @property def dpos(self): '''Return previous position of the touch in the screen coordinate system (self.dxpos, self.dypos)''' return self.dxpos, self.dypos @property def opos(self): '''Return the initial position of the touch in the screen coordinate system (self.oxpos, self.oypos)''' return self.oxpos, self.oypos @property def spos(self): '''Return the position in the 0-1 coordinate system (self.sx, self.sy)''' return self.sx, self.sy # compatibility bridge xpos = property(lambda self: self.x) ypos = property(lambda self: self.y) blobID = property(lambda self: self.id) xmot = property(lambda self: self.X) ymot = property(lambda self: self.Y) zmot = property(lambda self: self.Z) mot_accel = property(lambda self: self.m) rot_accel = property(lambda self: self.r) angle = property(lambda self: self.a)
def __init__(self, **kwargs): kwargs.setdefault('pos', (0, 0)) kwargs.setdefault('x', None) kwargs.setdefault('y', None) kwargs.setdefault('size', (100, 100)) kwargs.setdefault('size_hint', (None, None)) kwargs.setdefault('width', None) kwargs.setdefault('height', None) kwargs.setdefault('visible', True) kwargs.setdefault('draw_children', True) kwargs.setdefault('cls', '') kwargs.setdefault('style', {}) self._id = None if 'id' in kwargs: self.id = kwargs.get('id') super(MTWidget, self).__init__(**kwargs) # Registers events for ev in MTWidget.visible_events: self.register_event_type(ev) # privates self.__animationcache__ = set() self._parent = None self._visible = None self._size_hint = kwargs.get('size_hint') #: List of children (SafeList) self.children = SafeList() #: If False, childrens are not drawed. (deprecated) self.draw_children = kwargs.get('draw_children') #: Dictionnary that contains the widget style self.style = {} # apply visibility self.visible = kwargs.get('visible') # cache for get_parent_window() self._parent_layout = None self._parent_layout_source = None self._parent_window = None self._parent_window_source = None self._root_window = None self._root_window_source = None # register events register_event_type = self.register_event_type for event in ('on_update', 'on_animation_complete', 'on_resize', 'on_parent_resize', 'on_move', 'on_parent'): register_event_type(event) if kwargs.get('x'): self._pos = (kwargs.get('x'), self.y) if kwargs.get('y'): self._pos = (self.x, kwargs.get('y')) if kwargs.get('width'): self._size = (kwargs.get('width'), self.height) if kwargs.get('height'): self._size = (self.width, kwargs.get('height')) # apply style self._cls = '' self._inline_style = kwargs['style'] # loading is done here automaticly self.cls = kwargs.get('cls')
class MTFlippableWidget(MTWidget): '''This is wrapper widget using which you can make a widget have two sides and you can flip between the sides :: from pymt import * widget = MTFlippableWidget() widget.add_widget(MTLabel(label='Front'), side='front') widget.add_widget(MTLabel(label='Back'), side='back') @widget.event def on_touch_down(touch): widget.flip() runTouchApp(widget) :Parameters: `flipangle` : float, default to 90. Angle to flip back/front ''' def __init__(self, **kwargs): kwargs.setdefault('flipangle', 90.) super(MTFlippableWidget, self).__init__(**kwargs) self.flipangle = kwargs.get('flipangle') # For flipping animations self.zangle = 0 self.side = 'front' # Holds children for both sides self.children_front = SafeList() self.children_back = SafeList() self._anim_current = None self._anim_back = Animation(zangle=180) self._anim_front = Animation(zangle=0) def add_widget(self, w, side='front', front=True): '''Add a widget on a side. :Parameters: `front` : boolean, default is True Indicate if the widget must be top added or bottom added in the list. `side` : string, default is 'front' Specify which side you want to add widget. (can be one of 'front', 'back' or '', defaults to add to both sides) ''' assert(side in ('front', 'back', '')) if side == 'front': if front: self.children_front.append(w) else: self.children_front.insert(0, w) elif side == 'back': if front: self.children_back.append(w) else: self.children_back.insert(0, w) else: self.add_widget(w, side='front', front=front) self.add_widget(w, side='back', front=front) if self.side == side: super(MTFlippableWidget, self).add_widget(w, front) try: w.parent = self except Exception: pass def draw(self): set_color(*self.style.get('bg-color')) drawCSSRectangle(pos=(0, 0), size=self.size, style=self.style) def flip_children(self): # This has to be called exactly half way through the animation # so it looks like there are actually two sides''' if self.side == 'front': self.side = 'back' self.children.clear() for x in self.children_back[:]: super(MTFlippableWidget, self).add_widget(x) else: self.side = 'front' self.children.clear() for x in self.children_front[:]: super(MTFlippableWidget, self).add_widget(x) def flip_to(self, to): '''Flip to the requested side ('front' or 'back')''' assert(to in ('back', 'front')) if to == 'back' and self.side == 'front': self.flip_children() elif to == 'front' and self.side == 'back': self.flip_children() def flip(self): '''Triggers a flipping animation''' if self._anim_current: self._anim_current.stop() if self.side == 'front': self._anim_current = self.do(self._anim_back) else: self._anim_current = self.do(self._anim_front) def on_update(self): if self.zangle < self.flipangle: self.flip_to('front') else: self.flip_to('back') return super(MTFlippableWidget, self).on_update() def on_draw(self): with gx_matrix: glTranslatef(self.x, self.y, 0) glTranslatef(self.width / 2, 0, 0) if self.side == 'front': glRotatef(self.zangle, 0, 1, 0) else: glRotatef(self.zangle + 180, 0, 1, 0) glTranslatef(-self.width / 2, 0, 0) super(MTFlippableWidget, self).on_draw()
class BaseWindow(EventDispatcher): """BaseWindow is a abstract window widget, for any window implementation. .. warning:: The parameters are not working in normal case. Because at import, PyMT create a default OpenGL window, to add the ability to use OpenGL directives, texture creation.. before creating MTWindow. If you don't like this behavior, you can include before the very first import of PyMT :: import os os.environ['PYMT_SHADOW'] = '0' from pymt import * This will forbid PyMT to create the default window ! :Parameters: `fps`: int, default to 0 Maximum FPS allowed. If 0, fps will be not limited `fullscreen`: bool Make window as fullscreen `width`: int Width of window `height`: int Height of window `vsync`: bool Vsync window :Styles: `bg-color`: color Background color of window """ __instance = None __initialized = False _wallpaper = None _wallpaper_position = "norepeat" def __new__(cls, **kwargs): if cls.__instance is None: cls.__instance = EventDispatcher.__new__(cls) return cls.__instance def __init__(self, **kwargs): kwargs.setdefault("force", False) kwargs.setdefault("config", None) kwargs.setdefault("show_fps", False) kwargs.setdefault("style", {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get("force"): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) self._rotation = 0 # event subsystem self.register_event_type("on_flip") self.register_event_type("on_rotate") self.register_event_type("on_draw") self.register_event_type("on_update") self.register_event_type("on_resize") self.register_event_type("on_close") self.register_event_type("on_touch_down") self.register_event_type("on_touch_move") self.register_event_type("on_touch_up") self.register_event_type("on_mouse_down") self.register_event_type("on_mouse_move") self.register_event_type("on_mouse_up") self.register_event_type("on_keyboard") self.register_event_type("on_key_down") self.register_event_type("on_key_up") # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get("style") if len(kwargs.get("style")): self.apply_css(kwargs.get("style")) self.children = SafeList() self.parent = self self.visible = True # add view if "view" in kwargs: self.add_widget(kwargs.get("view")) # get window params, user options before config option params = {} if "fullscreen" in kwargs: params["fullscreen"] = kwargs.get("fullscreen") else: params["fullscreen"] = pymt.pymt_config.get("graphics", "fullscreen") if params["fullscreen"] not in ("auto", "fake"): params["fullscreen"] = params["fullscreen"].lower() in ("true", "1", "yes", "yup") if "width" in kwargs: params["width"] = kwargs.get("width") else: params["width"] = pymt.pymt_config.getint("graphics", "width") if "height" in kwargs: params["height"] = kwargs.get("height") else: params["height"] = pymt.pymt_config.getint("graphics", "height") if "vsync" in kwargs: params["vsync"] = kwargs.get("vsync") else: params["vsync"] = pymt.pymt_config.getint("graphics", "vsync") if "fps" in kwargs: params["fps"] = kwargs.get("fps") else: params["fps"] = pymt.pymt_config.getint("graphics", "fps") if "rotation" in kwargs: params["rotation"] = kwargs.get("rotation") else: params["rotation"] = pymt.pymt_config.getint("graphics", "rotation") params["position"] = pymt.pymt_config.get("graphics", "position", "auto") if "top" in kwargs: params["position"] = "custom" params["top"] = kwargs.get("top") else: params["top"] = pymt.pymt_config.getint("graphics", "top") if "left" in kwargs: params["position"] = "custom" params["left"] = kwargs.get("left") else: params["left"] = pymt.pymt_config.getint("graphics", "left") # show fps if asked self.show_fps = kwargs.get("show_fps") if pymt.pymt_config.getboolean("pymt", "show_fps"): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True def toggle_fullscreen(self): """Toggle fullscreen on window""" pass def close(self): """Close the window""" pass def create_window(self, params): """Will create the main window and configure it""" pass def on_flip(self): """Flip between buffers (event)""" self.flip() def flip(self): """Flip between buffers""" pass def dispatch_events(self): """Dispatch all events from windows""" pass def apply_css(self, styles): """Called at __init__ time to applied css attribute in current class. """ self.style.update(styles) def reload_css(self): """Called when css want to be reloaded from scratch""" self.style = {} style = css_get_style(widget=self) self.apply_css(style) if len(self._inline_style): self.apply_css(self._inline_style) def _get_modifiers(self): return self._modifiers modifiers = property(_get_modifiers) def _get_size(self): r = self._rotation w, h = self._size if r == 0 or r == 180: return w, h return h, w def _set_size(self, size): if super(BaseWindow, self)._set_size(size): pymt_logger.debug("Window: Resize window to %s" % str(self.size)) self.dispatch_event("on_resize", *size) return True return False size = property(_get_size, _set_size, doc="""Rotated size of the window""") # make some property read-only @property def width(self): """Rotated window width""" r = self._rotation if r == 0 or r == 180: return self._size[0] return self._size[1] @property def height(self): """Rotated window height""" r = self._rotation if r == 0 or r == 180: return self._size[1] return self._size[0] @property def center(self): """Rotated window center""" return self.width / 2.0, self.height / 2.0 def _get_wallpaper(self): return self._wallpaper def _set_wallpaper(self, filename): self._wallpaper = pymt.Image(filename) wallpaper = property(_get_wallpaper, _set_wallpaper, doc="Get/set the wallpaper (must be a valid filename)") def _get_wallpaper_position(self): return self._wallpaper_position def _set_wallpaper_position(self, position): self._wallpaper_position = position wallpaper_position = property( _get_wallpaper_position, _set_wallpaper_position, doc="Get/set the wallpaper position (can be one of" + '"norepeat", "center", "repeat", "scale", "strech")', ) def init_gl(self): version = glGetString(GL_VERSION) pymt_logger.info("Window: OpenGL version <%s>" % str(version)) line_smooth = pymt.pymt_config.getint("graphics", "line_smooth") if line_smooth: if line_smooth == 1: hint = GL_FASTEST else: hint = GL_NICEST glHint(GL_LINE_SMOOTH_HINT, hint) glEnable(GL_LINE_SMOOTH) def add_widget(self, w): """Add a widget on window""" self.children.append(w) w.parent = self def remove_widget(self, w): """Remove a widget from window""" if not w in self.children: return self.children.remove(w) w.parent = None def clear(self): """Clear the window with background color""" glClearColor(*self.style.get("bg-color")) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def draw(self): """Draw the window background""" self.clear() if self.wallpaper is not None: self.draw_wallpaper() def draw_wallpaper(self): wallpaper = self.wallpaper if self.wallpaper_position == "center": wallpaper.x = (self.width - wallpaper.width) / 2 wallpaper.y = (self.height - wallpaper.height) / 2 wallpaper.draw() elif self.wallpaper_position == "repeat": r_x = float(self.width) / wallpaper.width r_y = float(self.height) / wallpaper.height if int(r_x) != r_x: r_x = int(r_x) + 1 if int(r_y) != r_y: r_y = int(r_y) + 1 for x in xrange(int(r_x)): for y in xrange(int(r_y)): wallpaper.x = x * wallpaper.width wallpaper.y = y * wallpaper.height wallpaper.draw() elif self.wallpaper_position == "scale": wallpaper.size = self.size wallpaper.draw() elif self.wallpaper_position == "strech": w = self.width h = (self.width * wallpaper.height) / wallpaper.width if h < self.height: h = self.height w = (self.height * wallpaper.width) / wallpaper.height wallpaper.size = w, h wallpaper.pos = -(w - self.width) / 2, -(h - self.height) / 2 wallpaper.draw() else: # no-repeat or any other options wallpaper.draw() def draw_mouse_touch(self): """Compatibility for MouseTouch, drawing a little red circle around under each mouse touches.""" set_color(0.8, 0.2, 0.2, 0.7) for t in [x for x in getCurrentTouches() if x.device == "mouse"]: drawCircle(pos=(t.x, t.y), radius=10) def to_widget(self, x, y, initial=True, relative=False): return (x, y) def to_window(self, x, y, initial=True, relative=False): return (x, y) def get_root_window(self): return self def get_parent_window(self): return self def get_parent_layout(self): return None def on_update(self): """Event called when window are update the widget tree. (Usually before on_draw call.) """ for w in self.children[:]: w.dispatch_event("on_update") def on_draw(self): """Event called when window we are drawing window. This function are cleaning the buffer with bg-color css, and call children drawing + show fps timer on demand""" # draw our window self.draw() # then, draw childrens for w in self.children[:]: w.dispatch_event("on_draw") if self.show_fps: fps = getClock().get_fps() drawLabel(label="FPS: %.2f" % float(fps), center=False, pos=(0, 0), font_size=10, bold=False) self.draw_mouse_touch() def on_touch_down(self, touch): """Event called when a touch is down""" w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event("on_touch_down", touch): return True def on_touch_move(self, touch): """Event called when a touch move""" w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event("on_touch_move", touch): return True def on_touch_up(self, touch): """Event called when a touch up""" w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event("on_touch_up", touch): return True def on_resize(self, width, height): """Event called when the window is resized""" self.update_viewport() def update_viewport(self): width, height = self.system_size w2 = width / 2.0 h2 = height / 2.0 # prepare the viewport glViewport(0, 0, width, height) # set the projection glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum(-w2, w2, -h2, h2, 0.1, 1000) glScalef(5000, 5000, 1) # use the rotated size. width, height = self.size w2 = width / 2.0 h2 = height / 2.0 glTranslatef(-w2, -h2, -500) # set the model view glMatrixMode(GL_MODELVIEW) glLoadIdentity() glTranslatef(w2, h2, 0) glRotatef(self._rotation, 0, 0, 1) glTranslatef(-w2, -h2, 0) # update window size for w in self.children: shw, shh = w.size_hint if shw and shh: w.size = shw * width, shh * height elif shw: w.width = shw * width elif shh: w.height = shh * height def _get_rotation(self): return self._rotation def _set_rotation(self, x): x = int(x % 360) if x == self._rotation: return if x not in (0, 90, 180, 270): raise ValueError("can rotate only 0,90,180,270 degrees") self._rotation = x self.dispatch_event("on_resize", *self.size) self.dispatch_event("on_rotate", x) rotation = property( _get_rotation, _set_rotation, "Get/set the window content rotation. Can be one of " "0, 90, 180, 270 degrees." ) @property def system_size(self): """Real size of the window, without taking care of the rotation """ return self._size def on_rotate(self, rotation): """Event called when the screen have been rotated """ pass def on_close(self, *largs): """Event called when the window is closed""" pymt_modules.unregister_window(self) if self in touch_event_listeners[:]: touch_event_listeners.remove(self) def on_mouse_down(self, x, y, button, modifiers): """Event called when mouse is in action (press/release)""" pass def on_mouse_move(self, x, y, modifiers): """Event called when mouse is moving, with buttons pressed""" pass def on_mouse_up(self, x, y, button, modifiers): """Event called when mouse is moving, with buttons pressed""" pass def on_keyboard(self, key, scancode=None, unicode=None): """Event called when keyboard is in action .. warning:: Some providers can skip `scancode` or `unicode` !! """ pass def on_key_down(self, key, scancode=None, unicode=None): """Event called when a key is down (same arguments as on_keyboard)""" pass def on_key_up(self, key, scancode=None, unicode=None): """Event called when a key is up (same arguments as on_keyboard)""" pass
class MTScreenLayout(MTAbstractLayout): '''Base class to handle a list of screen (widgets). One child widget is shown at a time. :Parameters: `show_tabs`: bool, default to False If True, show tabs (useful for debugging) `duration`: float, default to 1. Duration to switch between screen ''' def __init__(self, **kwargs): kwargs.setdefault('show_tabs', False) kwargs.setdefault('duration', 1.) super(MTScreenLayout, self).__init__(**kwargs) self.screens = SafeList() self.screen = None self.previous_screen = None self._switch_t = 1.1 self.duration = kwargs.get('duration') self.container = MTBoxLayout(orientation='vertical') super(MTScreenLayout, self).add_widget(self.container) self.tabs = self.new_tab_layout() self._show_tabs = False self.show_tabs = kwargs.get('show_tabs', False) def _get_show_tabs(self): return self._show_tabs def _set_show_tabs(self, x): if self._show_tabs and x is False: self.container.remove_widget(self.tabs) if x and self._show_tabs is False: self.container.add_widget(self.tabs) self._show_tabs = x show_tabs = property(_get_show_tabs, _set_show_tabs) def new_tab_layout(self): '''called in init, to create teh layout in which all teh tabs are put. overwrite to create custom tab layout (default is box layout, vertical, height=50, with horizontal stretch.)''' return MTBoxLayout(size_hint=(1.0, None), height=50) def new_tab(self, label): '''fucntion that returns a new tab. return value must be of type MTButton or derive from it (must have on_press handler) if you overwrite the method. A Screenlayuot subclasses can overwrite this to create tabs based with own look and feel or do other custom things when a new tab is created''' return MTButton(label=label, size_hint=(1, 1), height=30) def add_widget(self, widget, tab_name=None): if tab_name: tab_btn = self.new_tab(tab_name) tab_btn.push_handlers(on_press=curry(self.select, widget)) self.tabs.add_widget(tab_btn) if widget.id is None: widget.id = tab_name self.screens.append(widget) def remove_widget(self, widget): for btn in self.tabs.children[:]: if isinstance(widget, basestring): if btn.label == widget: self.tabs.remove_widget(btn) break elif btn.label == widget.id or (hasattr(widget, 'title') and btn.label == widget.title): self.tabs.remove_widget(btn) break if widget in self.screens: self.screens.remove(widget) def select(self, wid, *args): ''' Select which screen is to be the current one. pass either a widget that has been added to this layout, or its id This function return True if the screen is selected, of False if we can't select the screen (non existant) ''' if self.screen is not None: self.container.remove_widget(self.screen) self.previous_screen = self.screen self._switch_t = -1.0 for screen in self.screens: if screen.id == wid or screen == wid: self.screen = screen self.container.add_widget(self.screen, do_layout=True) self.screen.parent = self return True return False def draw_transition(self, t): ''' Function is called each frame while switching screens and responsible for drawing transition state. t will go from -1.0 (previous screen), to 0 (rigth in middle), until 1.0 (last time called before giving new screen full controll) ''' set_color(*self.style['bg-color']) #from 1 to zero drawRectangle(pos=self.container.pos, size=self.container.size) r, g, b = self.style['bg-color'][0:3] if t < 0: if self.previous_screen is not None: self.previous_screen.dispatch_event('on_draw') set_color(r, g, b, 1 + t) #from 1 to zero drawRectangle(pos=self.container.pos, size=self.container.size) else: if self.previous_screen is not None: self.screen.dispatch_event('on_draw') set_color(r, g, b, 1 - t) #from 0 to one drawRectangle(pos=self.container.pos, size=self.container.size) def on_update(self): if not self.screen and len(self.screens): self.select(self.screens[0]) super(MTScreenLayout, self).on_update() def on_draw(self): super(MTScreenLayout, self).on_draw() if self._switch_t < 1.0: if self.duration == 0: self._switch_t = 1. else: self._switch_t += getFrameDt() / self.duration self.draw_transition(self._switch_t)
class LoaderBase(object): '''Common base for Loader and specific implementation. By default, Loader will be the best available loader implementation. The _update() function is called every 1 / 25.s or each frame if we have less than 25 FPS. ''' __metaclass__ = ABCMeta def __init__(self): loading_png_fn = os.path.join(pymt_data_dir, 'loader.png') error_png_fn = os.path.join(pymt_data_dir, 'error.png') self.loading_image = ImageLoader.load(loading_png_fn) self.error_image = ImageLoader.load(error_png_fn) self._q_load = collections.deque() self._q_done = collections.deque() self._client = SafeList() self._running = False self._start_wanted = False getClock().schedule_interval(self._update, 1 / 25.) def __del__(self): try: getClock().unschedule_intervale(self._update) except: pass @abstractmethod def start(self): '''Start the loader thread/process''' self._running = True @abstractmethod def run(self, *largs): '''Main loop for the loader.''' pass @abstractmethod def stop(self): '''Stop the loader thread/process''' self._running = False def _load(self, parameters): '''(internal) Loading function, called by the thread. Will call _load_local() if the file is local, or _load_urllib() if the file is on Internet''' filename, load_callback, post_callback = parameters proto = filename.split(':', 1)[0] if load_callback is not None: data = load_callback(filename) elif proto in ('http', 'https', 'ftp'): data = self._load_urllib(filename) else: data = self._load_local(filename) if post_callback: data = post_callback(data) self._q_done.append((filename, data)) def _load_local(self, filename): '''(internal) Loading a local file''' return ImageLoader.load(filename) def _load_urllib(self, filename): '''(internal) Loading a network file. First download it, save it to a temporary file, and pass it to _load_local()''' import urllib2, tempfile data = None try: suffix = '.%s' % (filename.split('.')[-1]) _out_osfd, _out_filename = tempfile.mkstemp( prefix='pymtloader', suffix=suffix) # read from internet fd = urllib2.urlopen(filename) idata = fd.read() fd.close() # write to local filename os.write(_out_osfd, idata) os.close(_out_osfd) # load data data = self._load_local(_out_filename) except: pymt_logger.exception('Failed to load image <%s>' % filename) return self.error_image finally: os.unlink(_out_filename) return data def _update(self, *largs): '''(internal) Check if a data is loaded, and pass to the client''' # want to start it ? if self._start_wanted: if not self._running: self.start() self._start_wanted = False while True: try: filename, data = self._q_done.pop() except: return # create the image image = data#ProxyImage(data) Cache.append('pymt.loader', filename, image) # update client for c_filename, client in self._client[:]: if filename != c_filename: continue # got one client to update client.image = image client.loaded = True client.dispatch_event('on_load') self._client.remove((c_filename, client)) def image(self, filename, load_callback=None, post_callback=None): '''Load a image using loader. A Proxy image is returned with a loading image :: img = Loader.image(filename) # img will be a ProxyImage. # You'll use it the same as an Image class. # Later, when the image is really loaded, # the loader will change the img.image property # to the new loaded image ''' data = Cache.get('pymt.loader', filename) if data not in (None, False): # found image return ProxyImage(data, loading_image=self.loading_image, loaded=True) client = ProxyImage(self.loading_image, loading_image=self.loading_image) self._client.append((filename, client)) if data is None: # if data is None, this is really the first time self._q_load.append((filename, load_callback, post_callback)) Cache.append('pymt.loader', filename, False) self._start_wanted = True else: # already queued for loading pass return client
class LoaderBase(object): '''Common base for Loader and specific implementation. By default, Loader will be the best available loader implementation. The _update() function is called every 1 / 25.s or each frame if we have less than 25 FPS. ''' __metaclass__ = ABCMeta def __init__(self): self._loading_image = None self._error_image = None self._q_load = collections.deque() self._q_done = collections.deque() self._client = SafeList() self._running = False self._start_wanted = False getClock().schedule_interval(self._update, 1 / 25.) def __del__(self): try: getClock().unschedule(self._update) except Exception: pass @property def loading_image(self): '''Image used for loading (readonly)''' if not self._loading_image: loading_png_fn = os.path.join(pymt_data_dir, 'loader.png') self._loading_image = ImageLoader.load(filename=loading_png_fn) return self._loading_image @property def error_image(self): '''Image used for error (readonly)''' if not self._error_image: error_png_fn = os.path.join(pymt_data_dir, 'error.png') self._error_image = ImageLoader.load(filename=error_png_fn) return self._error_image @abstractmethod def start(self): '''Start the loader thread/process''' self._running = True @abstractmethod def run(self, *largs): '''Main loop for the loader.''' pass @abstractmethod def stop(self): '''Stop the loader thread/process''' self._running = False def _load(self, parameters): '''(internal) Loading function, called by the thread. Will call _load_local() if the file is local, or _load_urllib() if the file is on Internet''' filename, load_callback, post_callback = parameters proto = filename.split(':', 1)[0] if load_callback is not None: data = load_callback(filename) elif proto in ('http', 'https', 'ftp'): data = self._load_urllib(filename) else: data = self._load_local(filename) if post_callback: data = post_callback(data) self._q_done.append((filename, data)) def _load_local(self, filename): '''(internal) Loading a local file''' return ImageLoader.load(filename) def _load_urllib(self, filename): '''(internal) Loading a network file. First download it, save it to a temporary file, and pass it to _load_local()''' import urllib2, tempfile data = None try: suffix = '.%s' % (filename.split('.')[-1]) _out_osfd, _out_filename = tempfile.mkstemp( prefix='pymtloader', suffix=suffix) # read from internet fd = urllib2.urlopen(filename) idata = fd.read() fd.close() # write to local filename os.write(_out_osfd, idata) os.close(_out_osfd) # load data data = self._load_local(_out_filename) except Exception: pymt_logger.exception('Failed to load image <%s>' % filename) return self.error_image finally: os.unlink(_out_filename) return data def _update(self, *largs): '''(internal) Check if a data is loaded, and pass to the client''' # want to start it ? if self._start_wanted: if not self._running: self.start() self._start_wanted = False while True: try: filename, data = self._q_done.pop() except IndexError: return # create the image image = data#ProxyImage(data) Cache.append('pymt.loader', filename, image) # update client for c_filename, client in self._client[:]: if filename != c_filename: continue # got one client to update client.image = image client.loaded = True client.dispatch_event('on_load') self._client.remove((c_filename, client)) def image(self, filename, load_callback=None, post_callback=None): '''Load a image using loader. A Proxy image is returned with a loading image :: img = Loader.image(filename) # img will be a ProxyImage. # You'll use it the same as an Image class. # Later, when the image is really loaded, # the loader will change the img.image property # to the new loaded image ''' data = Cache.get('pymt.loader', filename) if data not in (None, False): # found image return ProxyImage(data, loading_image=self.loading_image, loaded=True) client = ProxyImage(self.loading_image, loading_image=self.loading_image) self._client.append((filename, client)) if data is None: # if data is None, this is really the first time self._q_load.append((filename, load_callback, post_callback)) Cache.append('pymt.loader', filename, False) self._start_wanted = True else: # already queued for loading pass return client
class MTScreenLayout(MTAbstractLayout): '''Base class to handle a list of screen (widgets). One child widget is shown at a time. :Parameters: `show_tabs`: bool, default to False If True, show tabs (useful for debugging) `duration`: float, default to 1. Duration to switch between screen ''' def __init__(self, **kwargs): kwargs.setdefault('show_tabs', False) kwargs.setdefault('duration', 1.) super(MTScreenLayout, self).__init__(**kwargs) self.screens = SafeList() self.screen = None self.previous_screen = None self._switch_t = 1.1 self.duration = kwargs.get('duration') self.container = MTBoxLayout(orientation='vertical') super(MTScreenLayout, self).add_widget(self.container) self.tabs = self.new_tab_layout() self._show_tabs = False self.show_tabs = kwargs.get('show_tabs', False) def _get_show_tabs(self): return self._show_tabs def _set_show_tabs(self, x): if self._show_tabs and x is False: self.container.remove_widget(self.tabs) if x and self._show_tabs is False: self.container.add_widget(self.tabs) self._show_tabs = x show_tabs = property(_get_show_tabs, _set_show_tabs) def new_tab_layout(self): '''called in init, to create teh layout in which all teh tabs are put. overwrite to create custom tab layout (default is box layout, vertical, height=50, with horizontal stretch.)''' return MTBoxLayout(size_hint=(1.0, None), height=50) def new_tab(self, label): '''fucntion that returns a new tab. return value must be of type MTButton or derive from it (must have on_press handler) if you overwrite the method. A Screenlayuot subclasses can overwrite this to create tabs based with own look and feel or do other custom things when a new tab is created''' return MTButton(label=label, size_hint=(1, 1), height=30) def add_widget(self, widget, tab_name=None): if tab_name: tab_btn = self.new_tab(tab_name) tab_btn.push_handlers(on_press=curry(self.select, widget)) self.tabs.add_widget(tab_btn) if widget.id is None: widget.id = tab_name self.screens.append(widget) def remove_widget(self, widget): for btn in self.tabs.children[:]: if isinstance(widget, basestring): if btn.label == widget: self.tabs.remove_widget(btn) break elif btn.label == widget.id or ( hasattr(widget, 'title') and btn.label == widget.title): self.tabs.remove_widget(btn) break if widget in self.screens: self.screens.remove(widget) def select(self, wid, *args): ''' Select which screen is to be the current one. pass either a widget that has been added to this layout, or its id This function return True if the screen is selected, of False if we can't select the screen (non existant) ''' if self.screen is not None: self.container.remove_widget(self.screen) self.previous_screen = self.screen self._switch_t = -1.0 for screen in self.screens: if screen.id == wid or screen == wid: self.screen = screen self.container.add_widget(self.screen, do_layout=True) self.screen.parent = self return True return False def draw_transition(self, t): ''' Function is called each frame while switching screens and responsible for drawing transition state. t will go from -1.0 (previous screen), to 0 (rigth in middle), until 1.0 (last time called before giving new screen full controll) ''' set_color(*self.style['bg-color']) #from 1 to zero drawRectangle(pos=self.container.pos, size=self.container.size) r, g, b = self.style['bg-color'][0:3] if t < 0: if self.previous_screen is not None: self.previous_screen.dispatch_event('on_draw') set_color(r, g, b, 1+t) #from 1 to zero drawRectangle(pos=self.container.pos, size=self.container.size) else: if self.previous_screen is not None: self.screen.dispatch_event('on_draw') set_color(r, g, b, 1-t) #from 0 to one drawRectangle(pos=self.container.pos, size=self.container.size) def on_update(self): if not self.screen and len(self.screens): self.select(self.screens[0]) super(MTScreenLayout, self).on_update() def on_draw(self): super(MTScreenLayout, self).on_draw() if self._switch_t < 1.0: if self.duration == 0: self._switch_t = 1. else: self._switch_t += getFrameDt() / self.duration self.draw_transition(self._switch_t)
class MTWidget(EventDispatcher): """Global base for any multitouch widget. Implement event for mouse, object, touch and animation. Event are dispatched through widget only if it's visible. :Parameters: `pos` : list, default is (0, 0) Position of widget, in (x, y) format `x` : int, default is None X position of widget `y` : int, default is None Y position of widget `size` : list, default is (100, 100) Size of widget, in (width, height) format `width` : int, default is None width position of widget `height` : int, default is None height position of widget `visible` : bool, default is True Visibility of widget `draw_children` : bool, default is True Indicate if children will be draw, or not `style` : dict, default to {} Add inline CSS `cls` : str, default is '' CSS class of this widget :Events: `on_update` () Used to update the widget and his children. `on_draw` () Used to draw the widget and his children. `on_touch_down` (Touch touch) Fired when a blob appear `on_touch_move` (Touch touch) Fired when a blob is moving `on_touch_up` (Touch touch) Fired when a blob disappear `on_resize` (float width, float height) Fired when widget is resized `on_parent_resize` (float width, float height) Fired when parent widget is resized """ __metaclass__ = MTWidgetMetaclass __slots__ = ( "children", "style", "draw_children", "_cls", "_root_window_source", "_root_window", "_parent_window_source", "_parent_window", "_parent_layout_source", "_parent_layout", "_size_hint", "_id", "_parent", "_visible", "_inline_style", "__animationcache__", "__weakref__", ) visible_events = ["on_draw", "on_touch_up", "on_touch_move", "on_touch_down"] def __init__(self, **kwargs): kwargs.setdefault("pos", (0, 0)) kwargs.setdefault("x", None) kwargs.setdefault("y", None) kwargs.setdefault("size", (100, 100)) kwargs.setdefault("size_hint", (None, None)) kwargs.setdefault("width", None) kwargs.setdefault("height", None) kwargs.setdefault("visible", True) kwargs.setdefault("draw_children", True) kwargs.setdefault("cls", "") kwargs.setdefault("style", {}) self._id = None if "id" in kwargs: self.id = kwargs.get("id") super(MTWidget, self).__init__(**kwargs) # Registers events for ev in MTWidget.visible_events: self.register_event_type(ev) self.__animationcache__ = set() self._parent = None self.children = SafeList() self._visible = None self._size_hint = kwargs.get("size_hint") self.visible = kwargs.get("visible") self.draw_children = kwargs.get("draw_children") # cache for get_parent_window() self._parent_layout = None self._parent_layout_source = None self._parent_window = None self._parent_window_source = None self._root_window = None self._root_window_source = None self.register_event_type("on_update") self.register_event_type("on_animation_complete") self.register_event_type("on_resize") self.register_event_type("on_parent_resize") self.register_event_type("on_move") self.register_event_type("on_parent") if kwargs.get("x"): self._pos = (kwargs.get("x"), self.y) if kwargs.get("y"): self._pos = (self.x, kwargs.get("y")) if kwargs.get("width"): self._size = (kwargs.get("width"), self.height) if kwargs.get("height"): self._size = (self.width, kwargs.get("height")) # apply css self.style = {} self._cls = "" self._inline_style = kwargs.get("style") # loading is done here automaticly self.cls = kwargs.get("cls") self.init() def _set_cls(self, cls): self._cls = cls self.reload_css() def _get_cls(self): return self._cls cls = property(_get_cls, _set_cls, doc="Get/Set the class of the widget (used for CSS)") def _set_parent(self, parent): self._parent = parent self.dispatch_event("on_parent") def _get_parent(self): return self._parent parent = property(_get_parent, _set_parent, doc="MTWidget: parent of widget. Fired on_parent event when set") def _set_id(self, id): ref = weakref.ref(self) if ref in _id_2_widget: del _id_2_widget[self._id] self._id = id if self._id: if ref in _id_2_widget: pymt_logger.warning("Widget: ID <%s> is already used ! Replacing with new one." % id) _id_2_widget[self._id] = ref def _get_id(self): return self._id id = property(_get_id, _set_id, doc="str: id of widget") def _set_visible(self, visible): if self._visible == visible: return self._visible = visible # register or unregister event if the widget is visible or not if visible: for ev in MTWidget.visible_events: self.register_event_type(ev) else: for ev in MTWidget.visible_events: self.unregister_event_type(ev) def _get_visible(self): return self._visible visible = property(_get_visible, _set_visible, doc="bool: visibility of widget") def _set_size_hint(self, size_hint): if self._size_hint == size_hint: return False self._size_hint = size_hint def _get_size_hint(self): return self._size_hint size_hint = property( _get_size_hint, _set_size_hint, doc="size_hint is used by layouts to determine size behaviour during layout" ) def apply_css(self, styles): """Called at __init__ time to applied css attribute in current class. """ self.style.update(styles) def reload_css(self): """Called when css want to be reloaded from scratch""" self.style = {} style = css_get_style(widget=self) self.apply_css(style) if len(self._inline_style): self.apply_css(self._inline_style) def to_widget(self, x, y, relative=False): """Return the coordinate from window to local widget""" if self.parent: x, y = self.parent.to_widget(x, y) return self.to_local(x, y, relative=relative) def to_window(self, x, y, initial=True, relative=False): """Transform local coordinate to window coordinate""" if not initial: x, y = self.to_parent(x, y, relative=relative) if self.parent: return self.parent.to_window(x, y, initial=False, relative=relative) return (x, y) def to_parent(self, x, y, relative=False): """Transform local coordinate to parent coordinate :Parameters: `relative`: bool, default to False Change to True is you want to translate relative position from widget to his parent. """ if relative: return (x + self.x, y + self.y) return (x, y) def to_local(self, x, y, relative=False): """Transform parent coordinate to local coordinate :Parameters: `relative`: bool, default to False Change to True is you want to translate a coordinate to a relative coordinate from widget. """ if relative: return (x - self.x, y - self.y) return (x, y) def collide_point(self, x, y): """Test if the (x,y) is in widget bounding box""" if not self.visible: return False if x > self.x and x < self.x + self.width and y > self.y and y < self.y + self.height: return True def init(self): pass def get_root_window(self): """Return the root window of widget""" if not self.parent: return None # cache value if self._root_window_source != self.parent or self._root_window is None: self._root_window = self.parent.get_root_window() if not self._root_window: return None self._root_window_source = self.parent return self._root_window def get_parent_layout(self): """Return the parent layout of widget""" if not self.parent: return None # cache value if self._parent_layout_source != self.parent or self._parent_layout is None: self._parent_layout = self.parent.get_parent_layout() if not self._parent_layout: return None self._parent_layout_source = self.parent return self._parent_layout def get_parent_window(self): """Return the parent window of widget""" if not self.parent: return None # cache value if self._parent_window_source != self.parent or self._parent_window is None: self._parent_window = self.parent.get_parent_window() if not self._parent_window: return None self._parent_window_source = self.parent return self._parent_window def bring_to_front(self): """Remove it from wherever it is and add it back at the top""" if self.parent: parent = self.parent parent.remove_widget(self) parent.add_widget(self) def hide(self): """Hide the widget""" self.visible = False def show(self): """Show the widget""" self.visible = True def on_update(self): for w in self.children[:]: w.dispatch_event("on_update") def on_draw(self): self.draw() if self.draw_children: for w in self.children[:]: w.dispatch_event("on_draw") def draw(self): """Handle the draw of widget. Derivate this method to draw your widget.""" set_color(*self.style.get("bg-color")) drawCSSRectangle(pos=self.pos, size=self.size, style=self.style) def add_widget(self, w, front=True): """Add a widget in the children list.""" if front: self.children.append(w) else: self.children.insert(0, w) try: w.parent = self except Exception: pass def add_widgets(self, *widgets): for w in widgets: self.add_widget(w) def remove_widget(self, w): """Remove a widget from the children list""" if w in self.children: self.children.remove(w) def on_animation_complete(self, *largs): pass def on_parent(self): pass def on_parent_resize(self, w, h): pass def on_resize(self, w, h): for c in self.children[:]: c.dispatch_event("on_parent_resize", w, h) def on_move(self, x, y): for c in self.children[:]: c.dispatch_event("on_move", x, y) def on_touch_down(self, touch): for w in reversed(self.children[:]): if w.dispatch_event("on_touch_down", touch): return True def on_touch_move(self, touch): for w in reversed(self.children[:]): if w.dispatch_event("on_touch_move", touch): return True def on_touch_up(self, touch): for w in reversed(self.children[:]): if w.dispatch_event("on_touch_up", touch): return True def do(self, animation): """Apply/Start animations on the widgets. :Parameters: `animation` : Animation Object Animation object with properties to be animateds "," """ if not animation.set_widget(self): return # XXX bug from Animation framework # we need to store a reference of our animation class # otherwise, if the animation is called with self.do(), # gc can suppress reference, and it's gone ! animobj = animation.start(self) self.__animationcache__.add(animobj) def animobject_on_complete(widget, *l): if widget != self: return if animobj in self.__animationcache__: self.__animationcache__.remove(animobj) animation.connect("on_complete", animobject_on_complete) return animobj # generate event for all baseobject methods def _set_pos(self, x): if super(MTWidget, self)._set_pos(x): self.dispatch_event("on_move", *self._pos) return True pos = property(EventDispatcher._get_pos, _set_pos) def _set_x(self, x): if super(MTWidget, self)._set_x(x): self.dispatch_event("on_move", *self._pos) return True x = property(EventDispatcher._get_x, _set_x) def _set_y(self, x): if super(MTWidget, self)._set_y(x): self.dispatch_event("on_move", *self._pos) return True y = property(EventDispatcher._get_y, _set_y) def _set_size(self, x): if super(MTWidget, self)._set_size(x): self.dispatch_event("on_resize", *self._size) return True size = property(EventDispatcher._get_size, _set_size) def _set_width(self, x): if super(MTWidget, self)._set_width(x): self.dispatch_event("on_resize", *self._size) return True width = property(EventDispatcher._get_width, _set_width) def _set_height(self, x): if super(MTWidget, self)._set_height(x): self.dispatch_event("on_resize", *self._size) return True height = property(EventDispatcher._get_height, _set_height)
def __init__(self, **kwargs): kwargs.setdefault("pos", (0, 0)) kwargs.setdefault("x", None) kwargs.setdefault("y", None) kwargs.setdefault("size", (100, 100)) kwargs.setdefault("size_hint", (None, None)) kwargs.setdefault("width", None) kwargs.setdefault("height", None) kwargs.setdefault("visible", True) kwargs.setdefault("draw_children", True) kwargs.setdefault("cls", "") kwargs.setdefault("style", {}) self._id = None if "id" in kwargs: self.id = kwargs.get("id") super(MTWidget, self).__init__(**kwargs) # Registers events for ev in MTWidget.visible_events: self.register_event_type(ev) self.__animationcache__ = set() self._parent = None self.children = SafeList() self._visible = None self._size_hint = kwargs.get("size_hint") self.visible = kwargs.get("visible") self.draw_children = kwargs.get("draw_children") # cache for get_parent_window() self._parent_layout = None self._parent_layout_source = None self._parent_window = None self._parent_window_source = None self._root_window = None self._root_window_source = None self.register_event_type("on_update") self.register_event_type("on_animation_complete") self.register_event_type("on_resize") self.register_event_type("on_parent_resize") self.register_event_type("on_move") self.register_event_type("on_parent") if kwargs.get("x"): self._pos = (kwargs.get("x"), self.y) if kwargs.get("y"): self._pos = (self.x, kwargs.get("y")) if kwargs.get("width"): self._size = (kwargs.get("width"), self.height) if kwargs.get("height"): self._size = (self.width, kwargs.get("height")) # apply css self.style = {} self._cls = "" self._inline_style = kwargs.get("style") # loading is done here automaticly self.cls = kwargs.get("cls") self.init()
class MTFlippableWidget(MTWidget): '''This is wrapper widget using which you can make a widget have two sides and you can flip between the sides :: from pymt import * widget = MTFlippableWidget() widget.add_widget(MTLabel(label='Front'), side='front') widget.add_widget(MTLabel(label='Back'), side='back') @widget.event def on_touch_down(touch): widget.flip() runTouchApp(widget) :Parameters: `flipangle` : float, default to 90. Angle to flip back/front ''' def __init__(self, **kwargs): kwargs.setdefault('flipangle', 90.) super(MTFlippableWidget, self).__init__(**kwargs) self.flipangle = kwargs.get('flipangle') # For flipping animations self.zangle = 0 self.side = 'front' # Holds children for both sides self.children_front = SafeList() self.children_back = SafeList() self._anim_current = None self._anim_back = Animation(zangle=180) self._anim_front = Animation(zangle=0) def add_widget(self, w, side='front', front=True): '''Add a widget on a side. :Parameters: `front` : boolean, default is True Indicate if the widget must be top added or bottom added in the list. `side` : string, default is 'front' Specify which side you want to add widget. (can be one of 'front', 'back' or '', defaults to add to both sides) ''' assert (side in ('front', 'back', '')) if side == 'front': if front: self.children_front.append(w) else: self.children_front.insert(0, w) elif side == 'back': if front: self.children_back.append(w) else: self.children_back.insert(0, w) else: self.add_widget(w, side='front', front=front) self.add_widget(w, side='back', front=front) if self.side == side: super(MTFlippableWidget, self).add_widget(w, front) try: w.parent = self except Exception: pass def draw(self): set_color(*self.style.get('bg-color')) drawCSSRectangle(pos=(0, 0), size=self.size, style=self.style) def _flip_children(self): # This has to be called exactly half way through the animation # so it looks like there are actually two sides''' if self.side == 'front': self.side = 'back' self.children.clear() for x in self.children_back[:]: super(MTFlippableWidget, self).add_widget(x) else: self.side = 'front' self.children.clear() for x in self.children_front[:]: super(MTFlippableWidget, self).add_widget(x) def _set_side(self, to): assert (to in ('back', 'front')) if to == 'back' and self.side == 'front': self._flip_children() elif to == 'front' and self.side == 'back': self._flip_children() def flip_to(self, to): '''Flip to the requested side ('front' or 'back')''' assert (to in ('back', 'front')) if to == 'back' and self.side == 'front': self.flip() elif to == 'front' and self.side == 'back': self.flip() def flip(self): '''Triggers a flipping animation''' if self._anim_current: self._anim_current.stop() if self.side == 'front': self._anim_current = self.do(self._anim_back) else: self._anim_current = self.do(self._anim_front) def on_update(self): if self.zangle < self.flipangle: self._set_side('front') else: self._set_side('back') return super(MTFlippableWidget, self).on_update() def on_draw(self): with gx_matrix: glTranslatef(self.x, self.y, 0) glTranslatef(self.width / 2, 0, 0) if self.side == 'front': glRotatef(self.zangle, 0, 1, 0) else: glRotatef(self.zangle + 180, 0, 1, 0) glTranslatef(-self.width / 2, 0, 0) super(MTFlippableWidget, self).on_draw()
class BaseWindow(EventDispatcher): '''BaseWindow is a abstract window widget, for any window implementation. .. warning:: The parameters are not working in normal case. Because at import, PyMT create a default OpenGL window, to add the ability to use OpenGL directives, texture creation.. before creating MTWindow. If you don't like this behavior, you can include before the very first import of PyMT :: import os os.environ['PYMT_SHADOW'] = '0' from pymt import * This will forbid PyMT to create the default window ! :Parameters: `fps`: int, default to 0 Maximum FPS allowed. If 0, fps will be not limited `fullscreen`: bool Make window as fullscreen `width`: int Width of window `height`: int Height of window `vsync`: bool Vsync window :Styles: `bg-color`: color Background color of window ''' __instance = None __initialized = False _wallpaper = None _wallpaper_position = 'norepeat' def __new__(cls, **kwargs): if cls.__instance is None: cls.__instance = EventDispatcher.__new__(cls) return cls.__instance def __init__(self, **kwargs): kwargs.setdefault('force', False) kwargs.setdefault('config', None) kwargs.setdefault('show_fps', False) kwargs.setdefault('style', {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get('force'): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) self._rotation = 0 # event subsystem self.register_event_type('on_flip') self.register_event_type('on_rotate') self.register_event_type('on_draw') self.register_event_type('on_update') self.register_event_type('on_resize') self.register_event_type('on_close') self.register_event_type('on_touch_down') self.register_event_type('on_touch_move') self.register_event_type('on_touch_up') self.register_event_type('on_mouse_down') self.register_event_type('on_mouse_move') self.register_event_type('on_mouse_up') self.register_event_type('on_keyboard') self.register_event_type('on_key_down') self.register_event_type('on_key_up') # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get('style') if len(kwargs.get('style')): self.apply_css(kwargs.get('style')) self.children = SafeList() self.parent = self self.visible = True # add view if 'view' in kwargs: self.add_widget(kwargs.get('view')) # get window params, user options before config option params = {} if 'fullscreen' in kwargs: params['fullscreen'] = kwargs.get('fullscreen') else: params['fullscreen'] = pymt.pymt_config.get( 'graphics', 'fullscreen') if params['fullscreen'] not in ('auto', 'fake'): params['fullscreen'] = params['fullscreen'].lower() in \ ('true', '1', 'yes', 'yup') if 'width' in kwargs: params['width'] = kwargs.get('width') else: params['width'] = pymt.pymt_config.getint('graphics', 'width') if 'height' in kwargs: params['height'] = kwargs.get('height') else: params['height'] = pymt.pymt_config.getint('graphics', 'height') if 'vsync' in kwargs: params['vsync'] = kwargs.get('vsync') else: params['vsync'] = pymt.pymt_config.getint('graphics', 'vsync') if 'fps' in kwargs: params['fps'] = kwargs.get('fps') else: params['fps'] = pymt.pymt_config.getint('graphics', 'fps') if 'rotation' in kwargs: params['rotation'] = kwargs.get('rotation') else: params['rotation'] = pymt.pymt_config.getint( 'graphics', 'rotation') params['position'] = pymt.pymt_config.get('graphics', 'position', 'auto') if 'top' in kwargs: params['position'] = 'custom' params['top'] = kwargs.get('top') else: params['top'] = pymt.pymt_config.getint('graphics', 'top') if 'left' in kwargs: params['position'] = 'custom' params['left'] = kwargs.get('left') else: params['left'] = pymt.pymt_config.getint('graphics', 'left') # show fps if asked self.show_fps = kwargs.get('show_fps') if pymt.pymt_config.getboolean('pymt', 'show_fps'): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True def toggle_fullscreen(self): '''Toggle fullscreen on window''' pass def close(self): '''Close the window''' pass def create_window(self, params): '''Will create the main window and configure it''' pass def on_flip(self): '''Flip between buffers (event)''' self.flip() def flip(self): '''Flip between buffers''' pass def dispatch_events(self): '''Dispatch all events from windows''' pass def apply_css(self, styles): '''Called at __init__ time to applied css attribute in current class. ''' self.style.update(styles) def reload_css(self): '''Called when css want to be reloaded from scratch''' self.style = {} style = css_get_style(widget=self) self.apply_css(style) if len(self._inline_style): self.apply_css(self._inline_style) def _get_modifiers(self): return self._modifiers modifiers = property(_get_modifiers) def _get_size(self): r = self._rotation w, h = self._size if r == 0 or r == 180: return w, h return h, w def _set_size(self, size): if super(BaseWindow, self)._set_size(size): pymt_logger.debug('Window: Resize window to %s' % str(self.size)) self.dispatch_event('on_resize', *size) return True return False size = property(_get_size, _set_size, doc='''Rotated size of the window''') # make some property read-only @property def width(self): '''Rotated window width''' r = self._rotation if r == 0 or r == 180: return self._size[0] return self._size[1] @property def height(self): '''Rotated window height''' r = self._rotation if r == 0 or r == 180: return self._size[1] return self._size[0] @property def center(self): '''Rotated window center''' return self.width / 2., self.height / 2. def _get_wallpaper(self): return self._wallpaper def _set_wallpaper(self, filename): self._wallpaper = pymt.Image(filename) wallpaper = property( _get_wallpaper, _set_wallpaper, doc='Get/set the wallpaper (must be a valid filename)') def _get_wallpaper_position(self): return self._wallpaper_position def _set_wallpaper_position(self, position): self._wallpaper_position = position wallpaper_position = property( _get_wallpaper_position, _set_wallpaper_position, doc='Get/set the wallpaper position (can be one of' + '"norepeat", "center", "repeat", "scale", "strech")') def init_gl(self): version = glGetString(GL_VERSION) pymt_logger.info('Window: OpenGL version <%s>' % str(version)) line_smooth = pymt.pymt_config.getint('graphics', 'line_smooth') if line_smooth: if line_smooth == 1: hint = GL_FASTEST else: hint = GL_NICEST glHint(GL_LINE_SMOOTH_HINT, hint) glEnable(GL_LINE_SMOOTH) def add_widget(self, w): '''Add a widget on window''' self.children.append(w) w.parent = self def remove_widget(self, w): '''Remove a widget from window''' if not w in self.children: return self.children.remove(w) w.parent = None def clear(self): '''Clear the window with background color''' glClearColor(*self.style.get('bg-color')) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def draw(self): '''Draw the window background''' self.clear() if self.wallpaper is not None: self.draw_wallpaper() def draw_wallpaper(self): wallpaper = self.wallpaper if self.wallpaper_position == 'center': wallpaper.x = (self.width - wallpaper.width) / 2 wallpaper.y = (self.height - wallpaper.height) / 2 wallpaper.draw() elif self.wallpaper_position == 'repeat': r_x = float(self.width) / wallpaper.width r_y = float(self.height) / wallpaper.height if int(r_x) != r_x: r_x = int(r_x) + 1 if int(r_y) != r_y: r_y = int(r_y) + 1 for x in xrange(int(r_x)): for y in xrange(int(r_y)): wallpaper.x = x * wallpaper.width wallpaper.y = y * wallpaper.height wallpaper.draw() elif self.wallpaper_position == 'scale': wallpaper.size = self.size wallpaper.draw() elif self.wallpaper_position == 'strech': w = self.width h = (self.width * wallpaper.height) / wallpaper.width if h < self.height: h = self.height w = (self.height * wallpaper.width) / wallpaper.height wallpaper.size = w, h wallpaper.pos = -(w - self.width) / 2, -(h - self.height) / 2 wallpaper.draw() else: # no-repeat or any other options wallpaper.draw() def draw_mouse_touch(self): '''Compatibility for MouseTouch, drawing a little red circle around under each mouse touches.''' set_color(0.8, 0.2, 0.2, 0.7) for t in [x for x in getCurrentTouches() if x.device == 'mouse']: drawCircle(pos=(t.x, t.y), radius=10) def to_widget(self, x, y, initial=True, relative=False): return (x, y) def to_window(self, x, y, initial=True, relative=False): return (x, y) def get_root_window(self): return self def get_parent_window(self): return self def get_parent_layout(self): return None def on_update(self): '''Event called when window are update the widget tree. (Usually before on_draw call.) ''' for w in self.children[:]: w.dispatch_event('on_update') def on_draw(self): '''Event called when window we are drawing window. This function are cleaning the buffer with bg-color css, and call children drawing + show fps timer on demand''' # draw our window self.draw() # then, draw childrens for w in self.children[:]: w.dispatch_event('on_draw') if self.show_fps: fps = getClock().get_fps() drawLabel(label='FPS: %.2f' % float(fps), center=False, pos=(0, 0), font_size=10, bold=False) self.draw_mouse_touch() def on_touch_down(self, touch): '''Event called when a touch is down''' w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_down', touch): return True def on_touch_move(self, touch): '''Event called when a touch move''' w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_move', touch): return True def on_touch_up(self, touch): '''Event called when a touch up''' w, h = self.system_size touch.scale_for_screen(w, h, rotation=self._rotation) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_up', touch): return True def on_resize(self, width, height): '''Event called when the window is resized''' self.update_viewport() def update_viewport(self): width, height = self.system_size w2 = width / 2. h2 = height / 2. # prepare the viewport glViewport(0, 0, width, height) # set the projection glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum(-w2, w2, -h2, h2, .1, 1000) glScalef(5000, 5000, 1) # use the rotated size. width, height = self.size w2 = width / 2. h2 = height / 2. glTranslatef(-w2, -h2, -500) # set the model view glMatrixMode(GL_MODELVIEW) glLoadIdentity() glTranslatef(w2, h2, 0) glRotatef(self._rotation, 0, 0, 1) glTranslatef(-w2, -h2, 0) # update window size for w in self.children: shw, shh = w.size_hint if shw and shh: w.size = shw * width, shh * height elif shw: w.width = shw * width elif shh: w.height = shh * height def _get_rotation(self): return self._rotation def _set_rotation(self, x): x = int(x % 360) if x == self._rotation: return if x not in (0, 90, 180, 270): raise ValueError('can rotate only 0,90,180,270 degrees') self._rotation = x self.dispatch_event('on_resize', *self.size) self.dispatch_event('on_rotate', x) rotation = property( _get_rotation, _set_rotation, 'Get/set the window content rotation. Can be one of ' '0, 90, 180, 270 degrees.') @property def system_size(self): '''Real size of the window, without taking care of the rotation ''' return self._size def on_rotate(self, rotation): '''Event called when the screen have been rotated ''' pass def on_close(self, *largs): '''Event called when the window is closed''' pymt_modules.unregister_window(self) if self in touch_event_listeners[:]: touch_event_listeners.remove(self) def on_mouse_down(self, x, y, button, modifiers): '''Event called when mouse is in action (press/release)''' pass def on_mouse_move(self, x, y, modifiers): '''Event called when mouse is moving, with buttons pressed''' pass def on_mouse_up(self, x, y, button, modifiers): '''Event called when mouse is moving, with buttons pressed''' pass def on_keyboard(self, key, scancode=None, unicode=None): '''Event called when keyboard is in action .. warning:: Some providers can skip `scancode` or `unicode` !! ''' pass def on_key_down(self, key, scancode=None, unicode=None): '''Event called when a key is down (same arguments as on_keyboard)''' pass def on_key_up(self, key, scancode=None, unicode=None): '''Event called when a key is up (same arguments as on_keyboard)''' pass
def __init__(self, **kwargs): kwargs.setdefault('force', False) kwargs.setdefault('config', None) kwargs.setdefault('show_fps', False) kwargs.setdefault('style', {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get('force'): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) # event subsystem self.register_event_type('on_flip') self.register_event_type('on_draw') self.register_event_type('on_update') self.register_event_type('on_resize') self.register_event_type('on_close') self.register_event_type('on_touch_down') self.register_event_type('on_touch_move') self.register_event_type('on_touch_up') self.register_event_type('on_mouse_down') self.register_event_type('on_mouse_move') self.register_event_type('on_mouse_up') self.register_event_type('on_keyboard') self.register_event_type('on_key_down') self.register_event_type('on_key_up') # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get('style') if len(kwargs.get('style')): self.apply_css(kwargs.get('style')) self.children = SafeList() self.parent = self self.visible = True # add view if 'view' in kwargs: self.add_widget(kwargs.get('view')) # get window params, user options before config option params = {} if 'fullscreen' in kwargs: params['fullscreen'] = kwargs.get('fullscreen') else: params['fullscreen'] = pymt.pymt_config.get('graphics', 'fullscreen') if params['fullscreen'] not in ('auto', 'fake'): params['fullscreen'] = params['fullscreen'].lower() in \ ('true', '1', 'yes', 'yup') if 'width' in kwargs: params['width'] = kwargs.get('width') else: params['width'] = pymt.pymt_config.getint('graphics', 'width') if 'height' in kwargs: params['height'] = kwargs.get('height') else: params['height'] = pymt.pymt_config.getint('graphics', 'height') if 'vsync' in kwargs: params['vsync'] = kwargs.get('vsync') else: params['vsync'] = pymt.pymt_config.getint('graphics', 'vsync') params['position'] = pymt.pymt_config.get( 'graphics', 'position', 'auto') if 'top' in kwargs: params['position'] = 'custom' params['top'] = kwargs.get('top') else: params['top'] = pymt.pymt_config.getint('graphics', 'top') if 'left' in kwargs: params['position'] = 'custom' params['left'] = kwargs.get('left') else: params['left'] = pymt.pymt_config.getint('graphics', 'left') # show fps if asked self.show_fps = kwargs.get('show_fps') if pymt.pymt_config.getboolean('pymt', 'show_fps'): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True
def __init__(self, **kwargs): kwargs.setdefault("force", False) kwargs.setdefault("config", None) kwargs.setdefault("show_fps", False) kwargs.setdefault("style", {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get("force"): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) self._rotation = 0 # event subsystem self.register_event_type("on_flip") self.register_event_type("on_rotate") self.register_event_type("on_draw") self.register_event_type("on_update") self.register_event_type("on_resize") self.register_event_type("on_close") self.register_event_type("on_touch_down") self.register_event_type("on_touch_move") self.register_event_type("on_touch_up") self.register_event_type("on_mouse_down") self.register_event_type("on_mouse_move") self.register_event_type("on_mouse_up") self.register_event_type("on_keyboard") self.register_event_type("on_key_down") self.register_event_type("on_key_up") # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get("style") if len(kwargs.get("style")): self.apply_css(kwargs.get("style")) self.children = SafeList() self.parent = self self.visible = True # add view if "view" in kwargs: self.add_widget(kwargs.get("view")) # get window params, user options before config option params = {} if "fullscreen" in kwargs: params["fullscreen"] = kwargs.get("fullscreen") else: params["fullscreen"] = pymt.pymt_config.get("graphics", "fullscreen") if params["fullscreen"] not in ("auto", "fake"): params["fullscreen"] = params["fullscreen"].lower() in ("true", "1", "yes", "yup") if "width" in kwargs: params["width"] = kwargs.get("width") else: params["width"] = pymt.pymt_config.getint("graphics", "width") if "height" in kwargs: params["height"] = kwargs.get("height") else: params["height"] = pymt.pymt_config.getint("graphics", "height") if "vsync" in kwargs: params["vsync"] = kwargs.get("vsync") else: params["vsync"] = pymt.pymt_config.getint("graphics", "vsync") if "fps" in kwargs: params["fps"] = kwargs.get("fps") else: params["fps"] = pymt.pymt_config.getint("graphics", "fps") if "rotation" in kwargs: params["rotation"] = kwargs.get("rotation") else: params["rotation"] = pymt.pymt_config.getint("graphics", "rotation") params["position"] = pymt.pymt_config.get("graphics", "position", "auto") if "top" in kwargs: params["position"] = "custom" params["top"] = kwargs.get("top") else: params["top"] = pymt.pymt_config.getint("graphics", "top") if "left" in kwargs: params["position"] = "custom" params["left"] = kwargs.get("left") else: params["left"] = pymt.pymt_config.getint("graphics", "left") # show fps if asked self.show_fps = kwargs.get("show_fps") if pymt.pymt_config.getboolean("pymt", "show_fps"): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True
class MTKineticList(MTStencilContainer): '''This is a kinetic container widget, that allows you to make a kinetic list scrolling in either direction. Some parameters are customizable in global configuration :: [widgets] list_friction = 10 list_trigger_distance = 5 :Parameters: `align` : string, default to 'center' Alignement of widget inside the row (or col). Can be one of 'center', 'left', 'right' `friction` : float, defaults to 10 The Pseudo-friction of the pseudo-kinetic scrolling. Formula for friction is :: acceleration = 1 + friction * frame_delta_time `padding_x` : int, defaults to 4 The spacing between scrolling items on the x axis `padding_y` : int, defaults to 4 The spacing between scrolling items on the y axis `w_limit` : int, defaults to 1 The limit of items that will appear horizontally. When this is set to a non-zero value the width(in terms of items in the kinetic list) will be w_limit, and the height will continually expand. `h_limit` : int, defaults to 0 Exect opposite of w_limit. If I didn't make either this or w_limit clear go bug xelapond `do_x` : bool, defaults to False Enable scrolling on the X axis `do_y` : bool, defaults to True Enable scrolling on the Y axis `title` : string, defaults to <Title Goes Here> Sets the title of the widget, which appears in 20 point font at the top `deletable` : bool, defaults to True When enabled it allows you to delete children by entering delete mode(red button in upper left) `searchable` : bool, defaults to True When enabled it allows you to enter search mode and filter items `trigger_distance` : int, default to 3 Maximum trigger distance to dispatch event on children (this mean if you move too much, trigger will not happen.) :Styles: `bg-color` : color Background color of the widget `scrollbar-size` : int Size of scrollbar in pixel (use 0 to disable it.) `scrollbar-color` : color Color of scrollbar `scrollbar-margin` : int int int int Margin top/right/bottom/left of scrollbar (left are not used.) :Events: `on_delete` (child) Fired when an item gets deleted. ''' def __init__(self, **kwargs): kwargs.setdefault('padding_x', 4) kwargs.setdefault('padding_y', 4) kwargs.setdefault('w_limit', 1) kwargs.setdefault('h_limit', 0) kwargs.setdefault('do_x', False) kwargs.setdefault('do_y', True) kwargs.setdefault('title', 'No title') kwargs.setdefault('deletable', True) kwargs.setdefault('searchable', True) kwargs.setdefault('align', 'center') super(MTKineticList, self).__init__(**kwargs) self.register_event_type('on_delete') self._a_sinput_out = None self._a_sinput_in = None self.title = Label('') self.sb = None self.sinput = None self.do_x = kwargs.get('do_x') self.do_y = kwargs.get('do_y') self.titletext = kwargs.get('title') self.deletable = kwargs.get('deletable') self.searchable = kwargs.get('searchable') self.padding_x = kwargs.get('padding_x') self.padding_y = kwargs.get('padding_y') self.w_limit = kwargs.get('w_limit') self.h_limit = kwargs.get('h_limit') self.align = kwargs.get('align') self.trigger_distance = kwargs.get( 'trigger_distance', pymt_config.getint('widgets', 'list_trigger_distance')) self.friction = kwargs.get( 'friction', pymt_config.getint('widgets', 'list_friction')) if self.w_limit and self.h_limit: raise Exception('You cannot limit both axes') elif not (self.w_limit or self.h_limit): raise Exception('You must limit at least one axis') # How far to offset tself.deletable and he axes(used for scrolling/panning) self.xoffset = 0 self.yoffset = 0 # X and Y translation vectors for the kinetic movement self.vx = 0 self.vy = 0 # List of all children, whatever will be the search self.pchildren = [] # For extra blob stats self.touch = {} # Holds widgets not a part of the scrolling(search button, etc) self.widgets = [] self._last_content_size = 0 self._scrollbar_index = 0 self._scrollbar_size = 0 # create the UI part. self._create_ui() def _create_ui(self): # Title Text if self.titletext is not None: self.title = Label(font_size=18, bold=True, anchor_x='center', anchor_y='center', label=self.titletext) self.title.x = self.width / 2 + self.x self.title.y = self.height - 20 + self.y # Delete Button if self.deletable: self.db = MTToggleButton(label='X', pos=(self.x + self.width - 80, self.y + self.height - 40), size=(80, 40), cls='kineticlist-delete') self.db.push_handlers(on_press=self.toggle_delete) self.widgets.append(self.db) # Search Button and Input Text Area if self.searchable: self.sb = MTToggleButton( label='S', #Button pos=(self.x, self.y + self.width - 40), size=(80, 40), cls='kineticlist-search') self.sb.push_handlers(on_press=self.toggle_search) self.sb.parent = self self.widgets.append(self.sb) self.sinput = pymt.MTTextInput(pos=(self.x, self.y + self.height - 40), size=(80, 40), style={'font-size': 20}) self.sinput.parent = self self.sinput.push_handlers(on_text_change=self.apply_filter) self.widgets.insert(0, self.sinput) # Animations to hide and show the search text input box self._a_sinput_in = Animation(y=self.y + self.height - 40 - self.sinput.size[1], duration=0.5, f='ease_out_cubic') self._a_sinput_out = Animation(y=self.y + self.height - self.sinput.size[1], duration=0.5, f='ease_out_cubic') def on_delete(self, child): pass def clear(self): self.children = SafeList() self.pchildren = SafeList() self.xoffset = self.yoffset = 0 def add_widget(self, widget, **kwargs): super(MTKineticList, self).add_widget(widget, **kwargs) self.pchildren.append(widget) def remove_widget(self, widget): super(MTKineticList, self).remove_widget(widget) if widget in self.pchildren: self.pchildren.remove(widget) self.dispatch_event('on_delete', widget) def toggle_delete(self, touch): '''Toggles the delete buttons on items Attached to the on_press handler of the delete button(self.db) ''' if self.db.state == 'down': for child in self.children[:]: child.show_delete() else: for child in self.children[:]: child.hide_delete() def toggle_search(self, touch): '''Toggles the search area Attached to the on_press handler of self.sb(the green search button) ''' if self.sb.state == 'down': self._a_sinput_in.animate(self.sinput) else: try: self.sinput.hide_keyboard() except: # There isn't a keyboard, so it throws a ValueError pass self.sinput.label = '' self._a_sinput_out.animate(self.sinput) def apply_filter(self, text): '''Applies the filter to the current children set''' self.search(text, 'label') #Move them so you don't have to scroll up to see them self.yoffset = self.padding_y self.xoffset = self.padding_x def filter(self, pattern, attr): '''Given an attribute of the children, and a pattern, return a list of the children with which pattern is in attr ''' return filter(lambda c: pattern in str(getattr(c, attr)), self.pchildren) def search(self, pattern, attr): '''Apply a search pattern to the current set of children''' result = self.filter(pattern, attr) self.children.clear() for item in result: self.children.append(item) def endsearch(self): '''Resets the children set to the full set''' self.children.clear() for item in self.pchildren: self.children.append(item) def _get_total_width(self, items, axis): '''Given a list of items and an axis, return the space they take up(in pixels) ''' total = 0 if axis == 'width': for item in items: total += (item.width + self.padding_x) elif axis == 'height': for item in items: total += (item.height + self.padding_y) return total def goto_head(self): if not self.h_limit: self.yoffset = -self._get_total_width( self.children, 'height') / self.w_limit + self.size[1] - 100 else: self.xoffset = self._get_total_width( self.children, 'width') / self.h_limit + self.size[0] - 100 def do_layout(self): '''Apply layout to all the items''' t = index = 0 # adapt value for direction w2 = self.width / 2. h2 = self.height / 2. inverse = 0 limit = self.w_limit width_attr = 'width' height_attr = 'height' xoffset = self.xoffset sx = self.x y = self.y + self.yoffset padding_x = self.padding_x padding_y = self.padding_y # inverse if not self.w_limit: inverse = 1 limit = self.h_limit width_attr = 'height' height_attr = 'width' xoffset = self.yoffset y = self.x + self.xoffset padding_x = self.padding_y padding_y = self.padding_x w2, h2 = h2, w2 sx = self.y # calculate size of actual content size = 0 for i in xrange(len(self.children)): if i % limit == 0: maxrange = min(i + limit, len(self.children)) childrens = [self.children[z] for z in range(i, maxrange)] h = max((c.__getattribute__(height_attr) for c in childrens)) size += h + padding_y self._last_content_size = size # add little padding for good looking. y += padding_y ny = y # recalculate position for each children for child in self.children[:]: # each row, calculate the height, advance y and reset x if index % limit == 0: # set y axis to the previous calculated position y = ny # get children in the row maxrange = min(t + limit, len(self.children)) childrens = [self.children[z] for z in range(t, maxrange)] # take the largest height in the current row if len(childrens): h = max( (c.__getattribute__(height_attr) for c in childrens)) else: h = 0 # prepare the y axis for next loop ny = y + h + padding_y # reset x for this row. if self.align == 'center': x = sx + w2 + xoffset - \ (self._get_total_width(childrens, width_attr) / 2.) elif self.align == 'left': x = 0 elif self.align == 'right': x = getattr(self, width_attr) - getattr( child, width_attr) - xoffset t += limit # reposition x if not inverse: child.kx = x + padding_x child.ky = y else: child.ky = x + padding_x child.kx = y x += child.__getattribute__(width_attr) + padding_x # Increment index index += 1 def on_touch_down(self, touch): if not self.collide_point(touch.x, touch.y): return # ok, it's a touch for us. grab it ! touch.grab(self) # first, check if own widget take the touch for w in reversed(self.widgets[:]): if w.on_touch_down(touch): return True # initiate kinetic movement. self.vx = self.vy = 0 self.touch[touch.id] = { 'ox': touch.x, 'oy': touch.y, 'xmot': 0, 'ymot': 0, 'travelx': 0, #How far the blob has traveled total in the x axis 'travely': 0, #^ } return True def on_touch_move(self, touch): # accept only grabbed touch by us if touch.grab_current != self: return # ok, if it's not a kinetic movement, # dispatch to children if touch.id not in self.touch: for w in reversed(self.widgets[:]): if w.on_touch_move(touch): return True return # it's a kinetic movement, process it. t = self.touch[touch.id] t['xmot'] = touch.x - t['ox'] t['ymot'] = touch.y - t['oy'] t['ox'] = touch.x t['oy'] = touch.y t['travelx'] += abs(t['xmot']) t['travely'] += abs(t['ymot']) self.xoffset += t['xmot'] * self.do_x self.yoffset += t['ymot'] * self.do_y self.ensure_bounding() def on_touch_up(self, touch): # accept only grabbed touch by us if touch.grab_current != self: return # it's an up, ungrab us ! touch.ungrab(self) if touch.id not in self.touch: for w in reversed(self.widgets[:]): if w.on_touch_up(touch): return True return t = self.touch[touch.id] self.vx = t['xmot'] self.vy = t['ymot'] # check if we can transmit event to children if (self.do_x and t['travelx'] > self.trigger_distance) or \ (self.do_y and t['travely'] > self.trigger_distance): return True # ok, the trigger distance is enough, we can dispatch event. # will not work if children grab the touch in down state :/ for child in reversed(self.children[:]): must_break = child.dispatch_event('on_touch_down', touch) old_grab_current = touch.grab_current touch.grab_current = child child.dispatch_event('on_touch_up', touch) touch.grab_current = old_grab_current if must_break: break return True def ensure_bounding(self): size = float(self._last_content_size) if size <= 0: return self._scrollbar_size = 1 self._scrollbar_index = 0 if self.do_y: if size < self.height: self.yoffset = 0 else: self.yoffset = boundary(self.yoffset, -size + self.height, 0) self._scrollbar_size = self.height / size self._scrollbar_index = -self.yoffset / size elif self.do_x: if size < self.width: self.xoffset = 0 else: self.xoffset = boundary(self.xoffset, -size + self.width, 0) self._scrollbar_size = self.width / size self._scrollbar_index = -self.xoffset / size def process_kinetic(self): '''Apply kinetic movement to all the items''' dt = getFrameDt() self.vx /= 1 + (self.friction * dt) self.vy /= 1 + (self.friction * dt) self.xoffset += self.vx * self.do_x self.yoffset += self.vy * self.do_y self.ensure_bounding() def draw(self): # background set_color(*self.style.get('bg-color')) drawCSSRectangle(pos=self.pos, size=self.size, style=self.style) # draw children self.stencil_push() for w in self.children[:]: # internal update of children w.update() # optimization to draw only viewed children if self.do_y and (w.y + w.height < self.y or w.y > self.y + self.height): continue if self.do_x and (w.x + w.width < self.x or w.x > self.x + self.width): continue w.on_draw() self.stencil_pop() # draw widgets for w in self.widgets: w.dispatch_event('on_draw') # title bar if self.titletext is not None: set_color(*self.style.get('title-color')) w = 0 if self.searchable: x = 80 w += 80 else: x = 0 if self.deletable: w += 80 drawCSSRectangle(pos=(self.x + x, self.height + self.y - 40), size=(self.width - w, 40), prefix='title', style=self.style) self.title.x = self.width / 2 + self.x self.title.y = self.height - 20 + self.y self.title.draw() # scrollbar sb_size = self.style.get('scrollbar-size') if sb_size > 0: mtop, mright, mbottom, mleft = self.style.get('scrollbar-margin') if self.do_y: pos = [ self.x + self.width - mright - sb_size, self.y + mbottom ] size = [sb_size, self.height - mbottom - mtop] pos[1] += size[1] * self._scrollbar_index size[1] = size[1] * self._scrollbar_size elif self.do_x: pos = [self.x + mleft, self.y + self.height - mtop - sb_size] size = [self.width - mleft - mright, sb_size] pos[0] += size[0] * self._scrollbar_index size[0] = size[0] * self._scrollbar_size set_color(*self.style.get('scrollbar-color')) drawRectangle(pos=pos, size=size) def on_draw(self): if not self.visible: return self.do_layout() self.process_kinetic() self.draw()
class MTWidget(EventDispatcher): '''Global base for any multitouch widget. Implement event for mouse, object, touch and animation. Event are dispatched through widget only if it's visible. :Parameters: `pos` : list, default is (0, 0) Position of widget, in (x, y) format `x` : int, default is None X position of widget `y` : int, default is None Y position of widget `size` : list, default is (100, 100) Size of widget, in (width, height) format `width` : int, default is None width position of widget `height` : int, default is None height position of widget `visible` : bool, default is True Visibility of widget `draw_children` : bool, default is True Indicate if children will be draw, or not `style` : dict, default to {} Add inline CSS `cls` : str, default is '' CSS class of this widget :Events: `on_update` () Used to update the widget and his children. `on_draw` () Used to draw the widget and his children. `on_touch_down` (Touch touch) Fired when a blob appear `on_touch_move` (Touch touch) Fired when a blob is moving `on_touch_up` (Touch touch) Fired when a blob disappear `on_resize` (float width, float height) Fired when widget is resized `on_parent_resize` (float width, float height) Fired when parent widget is resized ''' __metaclass__ = MTWidgetMetaclass __slots__ = ('children', 'style', 'draw_children', '_cls', '_root_window_source', '_root_window', '_parent_window_source', '_parent_window', '_parent_layout_source', '_parent_layout', '_size_hint', '_id', '_parent', '_visible', '_inline_style', '__animationcache__', '__weakref__') visible_events = [ 'on_draw', 'on_touch_up', 'on_touch_move', 'on_touch_down' ] def __init__(self, **kwargs): kwargs.setdefault('pos', (0, 0)) kwargs.setdefault('x', None) kwargs.setdefault('y', None) kwargs.setdefault('size', (100, 100)) kwargs.setdefault('size_hint', (None, None)) kwargs.setdefault('width', None) kwargs.setdefault('height', None) kwargs.setdefault('visible', True) kwargs.setdefault('draw_children', True) kwargs.setdefault('cls', '') kwargs.setdefault('style', {}) self._id = None if 'id' in kwargs: self.id = kwargs.get('id') super(MTWidget, self).__init__(**kwargs) # Registers events for ev in MTWidget.visible_events: self.register_event_type(ev) # privates self.__animationcache__ = set() self._parent = None self._visible = None self._size_hint = kwargs.get('size_hint') #: List of children (SafeList) self.children = SafeList() #: If False, childrens are not drawed. (deprecated) self.draw_children = kwargs.get('draw_children') #: Dictionnary that contains the widget style self.style = {} # apply visibility self.visible = kwargs.get('visible') # cache for get_parent_window() self._parent_layout = None self._parent_layout_source = None self._parent_window = None self._parent_window_source = None self._root_window = None self._root_window_source = None # register events register_event_type = self.register_event_type for event in ('on_update', 'on_animation_complete', 'on_resize', 'on_parent_resize', 'on_move', 'on_parent'): register_event_type(event) if kwargs.get('x'): self._pos = (kwargs.get('x'), self.y) if kwargs.get('y'): self._pos = (self.x, kwargs.get('y')) if kwargs.get('width'): self._size = (kwargs.get('width'), self.height) if kwargs.get('height'): self._size = (self.width, kwargs.get('height')) # apply style self._cls = '' self._inline_style = kwargs['style'] # loading is done here automaticly self.cls = kwargs.get('cls') def _set_cls(self, cls): self._cls = cls self.reload_css() def _get_cls(self): return self._cls cls = property( _get_cls, _set_cls, doc='Get/Set the class of the widget (used for CSS, can be a string ' 'or a list of string') def _set_parent(self, parent): self._parent = parent self.dispatch_event('on_parent') def _get_parent(self): return self._parent parent = property( _get_parent, _set_parent, doc='MTWidget: parent of widget. Fired on_parent event when set') def _set_id(self, id): ref = weakref.ref(self) if ref in _id_2_widget: del _id_2_widget[self._id] self._id = id if self._id: if ref in _id_2_widget: pymt_logger.warning( 'Widget: ID <%s> is already used ! Replacing with new one.' % id) _id_2_widget[self._id] = ref def _get_id(self): return self._id id = property(_get_id, _set_id, doc='str: id of widget') def _set_visible(self, visible): if self._visible == visible: return self._visible = visible # register or unregister event if the widget is visible or not if visible: for ev in MTWidget.visible_events: self.register_event_type(ev) else: for ev in MTWidget.visible_events: self.unregister_event_type(ev) def _get_visible(self): return self._visible visible = property( _get_visible, _set_visible, doc='' 'True if the widget is visible. If False, the events on_draw,' 'on_touch_down, on_touch_move, on_touch_up are not dispatched.') def _set_size_hint(self, size_hint): if self._size_hint == size_hint: return False self._size_hint = size_hint def _get_size_hint(self): return self._size_hint size_hint = property( _get_size_hint, _set_size_hint, doc= 'size_hint is used by layouts to determine size behaviour during layout' ) def apply_css(self, styles): '''Called at __init__ time to applied css attribute in current class. ''' self.style.update(styles) def reload_css(self): '''Called when css want to be reloaded from scratch''' self.style = {} style = css_get_style(widget=self) self.apply_css(style) if len(self._inline_style): self.apply_css(self._inline_style) def to_widget(self, x, y, relative=False): '''Return the coordinate from window to local widget''' if self.parent: x, y = self.parent.to_widget(x, y) return self.to_local(x, y, relative=relative) def to_window(self, x, y, initial=True, relative=False): '''Transform local coordinate to window coordinate''' if not initial: x, y = self.to_parent(x, y, relative=relative) if self.parent: return self.parent.to_window(x, y, initial=False, relative=relative) return (x, y) def to_parent(self, x, y, relative=False): '''Transform local coordinate to parent coordinate :Parameters: `relative`: bool, default to False Change to True is you want to translate relative position from widget to his parent. ''' if relative: return (x + self.x, y + self.y) return (x, y) def to_local(self, x, y, relative=False): '''Transform parent coordinate to local coordinate :Parameters: `relative`: bool, default to False Change to True is you want to translate a coordinate to a relative coordinate from widget. ''' if relative: return (x - self.x, y - self.y) return (x, y) def collide_point(self, x, y): '''Test if the (x,y) is in widget bounding box''' if not self.visible: return False if x > self.x and x < self.x + self.width and \ y > self.y and y < self.y + self.height: return True def get_root_window(self): '''Return the root window of widget''' if not self.parent: return None # cache value if self._root_window_source != self.parent or self._root_window is None: self._root_window = self.parent.get_root_window() if not self._root_window: return None self._root_window_source = self.parent return self._root_window def get_parent_layout(self): '''Return the parent layout of widget''' if not self.parent: return None # cache value if self._parent_layout_source != self.parent or self._parent_layout is None: self._parent_layout = self.parent.get_parent_layout() if not self._parent_layout: return None self._parent_layout_source = self.parent return self._parent_layout def get_parent_window(self): '''Return the parent window of widget''' if not self.parent: return None # cache value if self._parent_window_source != self.parent or self._parent_window is None: self._parent_window = self.parent.get_parent_window() if not self._parent_window: return None self._parent_window_source = self.parent return self._parent_window def bring_to_front(self): '''Remove it from wherever it is and add it back at the top''' if self.parent: parent = self.parent parent.remove_widget(self) parent.add_widget(self) def hide(self): '''Hide the widget''' self.visible = False def show(self): '''Show the widget''' self.visible = True def on_update(self): for w in self.children[:]: w.dispatch_event('on_update') def on_draw(self): self.draw() if self.draw_children: for w in self.children[:]: w.dispatch_event('on_draw') def draw(self): '''Handle the draw of widget. Derivate this method to draw your widget.''' set_color(*self.style.get('bg-color')) drawCSSRectangle(pos=self.pos, size=self.size, style=self.style) def add_widget(self, w, front=True): '''Add a widget in the children list.''' if front: self.children.append(w) else: self.children.insert(0, w) try: w.parent = self except Exception: pass def add_widgets(self, *widgets): for w in widgets: self.add_widget(w) def remove_widget(self, w): '''Remove a widget from the children list''' if w in self.children: self.children.remove(w) def on_animation_complete(self, *largs): pass def on_parent(self): pass def on_parent_resize(self, w, h): pass def on_resize(self, w, h): for c in self.children[:]: c.dispatch_event('on_parent_resize', w, h) def on_move(self, x, y): for c in self.children[:]: c.dispatch_event('on_move', x, y) def on_touch_down(self, touch): for w in reversed(self.children[:]): if w.dispatch_event('on_touch_down', touch): return True def on_touch_move(self, touch): for w in reversed(self.children[:]): if w.dispatch_event('on_touch_move', touch): return True def on_touch_up(self, touch): for w in reversed(self.children[:]): if w.dispatch_event('on_touch_up', touch): return True def do(self, animation): '''Apply/Start animations on the widgets. :Parameters: `animation` : Animation Object Animation object with properties to be animateds "," ''' if not animation.set_widget(self): return # XXX bug from Animation framework # we need to store a reference of our animation class # otherwise, if the animation is called with self.do(), # gc can suppress reference, and it's gone ! animobj = animation.start(self) self.__animationcache__.add(animobj) def animobject_on_complete(widget, *l): if widget != self: return if animobj in self.__animationcache__: self.__animationcache__.remove(animobj) animation.connect('on_complete', animobject_on_complete) return animobj # generate event for all baseobject methods def _set_pos(self, x): if super(MTWidget, self)._set_pos(x): self.dispatch_event('on_move', *self._pos) return True pos = property(EventDispatcher._get_pos, _set_pos) def _set_x(self, x): if super(MTWidget, self)._set_x(x): self.dispatch_event('on_move', *self._pos) return True x = property(EventDispatcher._get_x, _set_x) def _set_y(self, x): if super(MTWidget, self)._set_y(x): self.dispatch_event('on_move', *self._pos) return True y = property(EventDispatcher._get_y, _set_y) def _set_size(self, x): if super(MTWidget, self)._set_size(x): self.dispatch_event('on_resize', *self._size) return True size = property(EventDispatcher._get_size, _set_size) def _set_width(self, x): if super(MTWidget, self)._set_width(x): self.dispatch_event('on_resize', *self._size) return True width = property(EventDispatcher._get_width, _set_width) def _set_height(self, x): if super(MTWidget, self)._set_height(x): self.dispatch_event('on_resize', *self._size) return True height = property(EventDispatcher._get_height, _set_height)
class BaseWindow(EventDispatcher): '''BaseWindow is a abstract window widget, for any window implementation. .. warning:: The parameters are not working in normal case. Because at import, PyMT create a default OpenGL window, to add the ability to use OpenGL directives, texture creation.. before creating MTWindow. If you don't like this behavior, you can include before the very first import of PyMT :: import os os.environ['PYMT_SHADOW'] = '0' from pymt import * This will forbid PyMT to create the default window ! :Parameters: `fullscreen`: bool Make window as fullscreen `width`: int Width of window `height`: int Height of window `vsync`: bool Vsync window :Styles: `bg-color`: color Background color of window ''' __instance = None __initialized = False _wallpaper = None _wallpaper_position = 'norepeat' def __new__(cls, **kwargs): if cls.__instance is None: cls.__instance = EventDispatcher.__new__(cls) return cls.__instance def __init__(self, **kwargs): kwargs.setdefault('force', False) kwargs.setdefault('config', None) kwargs.setdefault('show_fps', False) kwargs.setdefault('style', {}) # don't init window 2 times, # except if force is specified if self.__initialized and not kwargs.get('force'): return super(BaseWindow, self).__init__() # init privates self._modifiers = [] self._size = (0, 0) # event subsystem self.register_event_type('on_flip') self.register_event_type('on_draw') self.register_event_type('on_update') self.register_event_type('on_resize') self.register_event_type('on_close') self.register_event_type('on_touch_down') self.register_event_type('on_touch_move') self.register_event_type('on_touch_up') self.register_event_type('on_mouse_down') self.register_event_type('on_mouse_move') self.register_event_type('on_mouse_up') self.register_event_type('on_keyboard') self.register_event_type('on_key_down') self.register_event_type('on_key_up') # set out window as the main pymt window setWindow(self) # apply styles for window self.style = {} style = css_get_style(widget=self) self.apply_css(style) # apply inline css self._inline_style = kwargs.get('style') if len(kwargs.get('style')): self.apply_css(kwargs.get('style')) self.children = SafeList() self.parent = self self.visible = True # add view if 'view' in kwargs: self.add_widget(kwargs.get('view')) # get window params, user options before config option params = {} if 'fullscreen' in kwargs: params['fullscreen'] = kwargs.get('fullscreen') else: params['fullscreen'] = pymt.pymt_config.get('graphics', 'fullscreen') if params['fullscreen'] not in ('auto', 'fake'): params['fullscreen'] = params['fullscreen'].lower() in \ ('true', '1', 'yes', 'yup') if 'width' in kwargs: params['width'] = kwargs.get('width') else: params['width'] = pymt.pymt_config.getint('graphics', 'width') if 'height' in kwargs: params['height'] = kwargs.get('height') else: params['height'] = pymt.pymt_config.getint('graphics', 'height') if 'vsync' in kwargs: params['vsync'] = kwargs.get('vsync') else: params['vsync'] = pymt.pymt_config.getint('graphics', 'vsync') params['position'] = pymt.pymt_config.get( 'graphics', 'position', 'auto') if 'top' in kwargs: params['position'] = 'custom' params['top'] = kwargs.get('top') else: params['top'] = pymt.pymt_config.getint('graphics', 'top') if 'left' in kwargs: params['position'] = 'custom' params['left'] = kwargs.get('left') else: params['left'] = pymt.pymt_config.getint('graphics', 'left') # show fps if asked self.show_fps = kwargs.get('show_fps') if pymt.pymt_config.getboolean('pymt', 'show_fps'): self.show_fps = True # configure the window self.create_window(params) # init some gl self.init_gl() # attach modules + listener event pymt_modules.register_window(self) touch_event_listeners.append(self) # mark as initialized self.__initialized = True def toggle_fullscreen(self): '''Toggle fullscreen on window''' pass def close(self): '''Close the window''' pass def create_window(self, params): '''Will create the main window and configure it''' pass def on_flip(self): '''Flip between buffers (event)''' self.flip() def flip(self): '''Flip between buffers''' pass def dispatch_events(self): '''Dispatch all events from windows''' pass def apply_css(self, styles): '''Called at __init__ time to applied css attribute in current class. ''' self.style.update(styles) def reload_css(self): '''Called when css want to be reloaded from scratch''' self.style = {} style = css_get_style(widget=self) self.apply_css(style) if len(self._inline_style): self.apply_css(self._inline_style) def _get_modifiers(self): return self._modifiers modifiers = property(_get_modifiers) def _set_size(self, size): if super(BaseWindow, self)._set_size(size): pymt_logger.debug('Window: Resize window to %s' % str(self.size)) self.dispatch_event('on_resize', *size) return True return False size = property(EventDispatcher._get_size, _set_size) # make some property read-only width = property(EventDispatcher._get_width) height = property(EventDispatcher._get_height) center = property(EventDispatcher._get_center) def _get_wallpaper(self): return self._wallpaper def _set_wallpaper(self, filename): self._wallpaper = pymt.Image(filename) wallpaper = property(_get_wallpaper, _set_wallpaper, doc='Get/set the wallpaper (must be a valid filename)') def _get_wallpaper_position(self): return self._wallpaper_position def _set_wallpaper_position(self, position): self._wallpaper_position = position wallpaper_position = property( _get_wallpaper_position, _set_wallpaper_position, doc='Get/set the wallpaper position (can be one of' + '"norepeat", "center", "repeat", "scale", "strech")') def init_gl(self): version = glGetString(GL_VERSION) pymt_logger.info('Window: OpenGL version <%s>' % str(version)) line_smooth = pymt.pymt_config.getint('graphics', 'line_smooth') if line_smooth: if line_smooth == 1: hint = GL_FASTEST else: hint = GL_NICEST glHint(GL_LINE_SMOOTH_HINT, hint) glEnable(GL_LINE_SMOOTH) def add_widget(self, w): '''Add a widget on window''' self.children.append(w) w.parent = self def remove_widget(self, w): '''Remove a widget from window''' if not w in self.children: return self.children.remove(w) w.parent = None def clear(self): '''Clear the window with background color''' glClearColor(*self.style.get('bg-color')) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) def draw(self): '''Draw the window background''' self.clear() if self.wallpaper is not None: self.draw_wallpaper() def draw_wallpaper(self): wallpaper = self.wallpaper if self.wallpaper_position == 'center': wallpaper.x = (self.width - wallpaper.width) / 2 wallpaper.y = (self.height - wallpaper.height) / 2 wallpaper.draw() elif self.wallpaper_position == 'repeat': r_x = float(self.width) / wallpaper.width r_y = float(self.height) / wallpaper.height if int(r_x) != r_x: r_x = int(r_x) + 1 if int(r_y) != r_y: r_y = int(r_y) + 1 for x in xrange(int(r_x)): for y in xrange(int(r_y)): wallpaper.x = x * wallpaper.width wallpaper.y = y * wallpaper.height wallpaper.draw() elif self.wallpaper_position == 'scale': wallpaper.size = self.size wallpaper.draw() elif self.wallpaper_position == 'strech': w = self.width h = (self.width * wallpaper.height) / wallpaper.width if h < self.height: h = self.height w = (self.height * wallpaper.width) / wallpaper.height wallpaper.size = w, h wallpaper.pos = -(w - self.width) / 2, -(h - self.height) / 2 wallpaper.draw() else: # no-repeat or any other options wallpaper.draw() def draw_mouse_touch(self): '''Compatibility for MouseTouch, drawing a little red circle around under each mouse touches.''' set_color(0.8, 0.2, 0.2, 0.7) for t in [x for x in getCurrentTouches() if x.device == 'mouse']: drawCircle(pos=(t.x, t.y), radius=10) def to_widget(self, x, y, initial=True, relative=False): return (x, y) def to_window(self, x, y, initial=True, relative=False): return (x, y) def get_root_window(self): return self def get_parent_window(self): return self def get_parent_layout(self): return None def on_update(self): '''Event called when window are update the widget tree. (Usually before on_draw call.) ''' for w in self.children[:]: w.dispatch_event('on_update') def on_draw(self): '''Event called when window we are drawing window. This function are cleaning the buffer with bg-color css, and call children drawing + show fps timer on demand''' # draw our window self.draw() # then, draw childrens for w in self.children[:]: w.dispatch_event('on_draw') if self.show_fps: fps = getClock().get_fps() drawLabel(label='FPS: %.2f' % float(fps), center=False, pos=(0, 0), font_size=10, bold=False) self.draw_mouse_touch() def on_touch_down(self, touch): '''Event called when a touch is down''' touch.scale_for_screen(*self.size) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_down', touch): return True def on_touch_move(self, touch): '''Event called when a touch move''' touch.scale_for_screen(*self.size) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_move', touch): return True def on_touch_up(self, touch): '''Event called when a touch up''' touch.scale_for_screen(*self.size) for w in reversed(self.children[:]): if w.dispatch_event('on_touch_up', touch): return True def on_resize(self, width, height): '''Event called when the window is resized''' glViewport(0, 0, width, height) glMatrixMode(GL_PROJECTION) glLoadIdentity() glFrustum(-width / 2, width / 2, -height / 2, height / 2, .1, 1000) glScalef(5000, 5000, 1) glTranslatef(-width / 2, -height / 2, -500) glMatrixMode(GL_MODELVIEW) for w in self.children: shw, shh = w.size_hint if shw and shh: w.size = shw * width, shh * height elif shw: w.width = shw * width elif shh: w.height = shh * height def on_close(self, *largs): '''Event called when the window is closed''' pymt_modules.unregister_window(self) if self in touch_event_listeners[:]: touch_event_listeners.remove(self) def on_mouse_down(self, x, y, button, modifiers): '''Event called when mouse is in action (press/release)''' pass def on_mouse_move(self, x, y, modifiers): '''Event called when mouse is moving, with buttons pressed''' pass def on_mouse_up(self, x, y, button, modifiers): '''Event called when mouse is moving, with buttons pressed''' pass def on_keyboard(self, key, scancode=None, unicode=None): '''Event called when keyboard is in action .. warning:: Some providers can skip `scancode` or `unicode` !! ''' pass def on_key_down(self, key, scancode=None, unicode=None): '''Event called when a key is down (same arguments as on_keyboard)''' pass def on_key_up(self, key, scancode=None, unicode=None): '''Event called when a key is up (same arguments as on_keyboard)''' pass