Source code for ognon.cursor

"""
This module defines the Cursor class.
The cursor is the most importants object of the ognon's structure.
It is a required argument for every control and view functions.
"""
from . import model
from . import projects
from . import tags

[docs]class UndefinedProjectError(AttributeError): """ This exception will be raised if a cursor action require a poject. And no one is selected.""" pass
[docs]class Cursor(): """ A Cursor is a point of view on an ognon project, a *tape head*. It store informations about his position and his state and a reference to the project. It provides a bunch of getters and setters to move the cursor, know where it is and access to the ellements under it. Attributes: - `proj` : a model.Project instance. - `_pos` : a dict that store the cursor position. - `_pos['anim']` : The name of an animation in the project anims dict. - `_pos['layer']` : The index of a layer in the animation layers list. - `_pos['frm']` : A specific instant of the animation. - `playing` : The cursor state (playing or not playing) """ def __init__(self, proj=None): """ Init a cursor with an optional ognon project. """ self._proj = proj or projects.new('new_project') self._pos = { 'anim':'master', 'layer':0, 'frm':0, } self.playing = False self.clipboard = None self.state_id = 0 @property def proj(self): if not self._proj: raise UndefinedProjectError return self._proj @proj.setter def proj(self, value): self._proj = value
[docs] def get_pos(self, key=None): """ Get the cursor position. If a key is passed, return the specified position ('anim', 'layer' or 'frm'). This method first call set_pos without arguments to ensure that the position is valid (e.g. not pointing an inexisting layer) """ self.set_pos() if key: return self._pos[key] return self._pos
[docs] def set_pos(self, anim=None, layer=None, frm=None): """ Set the cursor position. (anim, layer and frm) Positions are constrained to valid values : frm is constained using the constrain_frm method, anim is set to 'masster' if current value refer to an unexisting animation and layer is set to 0 if the current value is an out of range index. If the anim argument is passed but layer or frm are not, set them to 0. If no arguments are passed, just constain the three values. If no project, raise a UndefinedProjectError """ # Set position. if anim is not None: self._pos['anim'] = anim self._pos['layer'] = 0 self._pos['frm'] = 0 if layer is not None: self._pos['layer'] = layer if frm is not None: self._pos['frm'] = frm if self._pos['anim'] not in self.proj.anims: self._pos['anim'] = 'master' # Constrain layer. try: self._pos['layer'] %= len(self.proj.anims[self._pos['anim']].layers) except ZeroDivisionError: self._pos['layer'] = 0 # Constrain frm. self._pos['frm'] = self.constrain_frm(self._pos['frm'])
[docs] def constrain_frm(self, frm): """ Return a frm position constrained in the current anim length. Result will be different depending on whether the loop mode is on or off. """ if self.anim_len() == 0: return 0 if self.proj.config['play']['loop']: return frm % self.anim_len() if frm <= 0: return 0 if frm >= self.anim_len(): return self.anim_len() - 1 return frm
[docs] def get_anim(self, anim=None): """ Return the current anim or the specified anim """ anim = anim or self.get_pos('anim') return self.proj.anims[anim]
[docs] def iter_elements(self, anim=None): """ Generator function for iterating over all animation's elements. """ for layer in self.get_anim(anim).layers: for element in layer.elements: yield element
[docs] def get_layer(self, anim=None, layer=None): """ Return the current layer or the specified layer in the specified anim Return None if no layer """ layer = layer if layer is not None else self.get_pos('layer') try: return self.get_anim(anim).layers[layer] except IndexError: return None
# True if the given element is an AnimRef is_animref = lambda self, element: isinstance(element, model.AnimRef) # True if the given AnimRef refers to an anim containing ref to itself. is_self_ref = lambda self, elmt: elmt.name in [ e.name for e in self.iter_elements(elmt.name) if self.is_animref(e)] # True if the given AnimRef refers to an unexisting anim. is_unexisting_ref = lambda self, elmt: elmt.name not in self.proj.anims
[docs] def get_element_pos(self, anim=None, layer=None, frm=None): """ Return a tuple with the index of the element in the layer, the element object and the position of the cursor inside the element Return None if no element. """ broken = model.BrokenElement if self.anim_len(anim): layer = self.get_layer(anim, layer) frm = frm if frm is not None else self.get_pos('frm') frm_ = 0 for i, e in enumerate(layer.elements): if self.is_animref(e): if self.is_unexisting_ref(e): e = broken( '/!\\ "{}" does not exists...'.format(e.name)) elif self.is_self_ref(e): e = broken('/!\\ self-reference...') length = self.element_len(e) frm_ += length if frm_ > frm: at = frm-frm_+length for tag in e.tags: at = tags.calculate_inside_pos( at, self.element_len(e, True), tag) return i, e, at return 0, broken('/!\\ no element'), 0
[docs] def get_element(self, anim=None, layer=None, frm=None): """ Return the current element or the element on the specified frm in the specified layer in the specified anim """ return self.get_element_pos(anim, layer, frm)[1]
[docs] def anim_len(self, anim=None): """ Return the length of the current anim or specified anim """ anim = anim if anim else self._pos['anim'] layer_len = lambda layer : sum(map(self.element_len, layer.elements)) layers = self.get_anim(anim).layers if layers: return layer_len(max(self.get_anim(anim).layers, key=layer_len)) else: # len is 0 if no layers return 0
[docs] def element_len(self, elmt, ignonre_tags=False): """ Return the length of the given element """ if isinstance(elmt, model.Cell): length = 1 elif isinstance(elmt, model.AnimRef): if self.is_unexisting_ref(elmt): length = 1 elif self.is_self_ref(elmt): length = 1 else : length = self.anim_len(elmt.name) if isinstance(elmt, model.BrokenElement): length = 1 if ignonre_tags: return length else : for tag in elmt.tags: length = tags.calculate_len(length, tag) return length