class LayerList: """List-like layer collection with built-in reordering and callback hooks. Parameters ---------- viewer : Viewer, optional Parent viewer. Attributes ---------- viewer : Viewer Parent viewer. events : vispy.util.event.EmitterGroup Event hooks: * add_item(item): whenever an item is added * remove_item(item): whenever an item is removed * reorder(): whenever the list is reordered """ __slots__ = ('__weakref__', '_list', '_qt', '_viewer', 'total', 'events') def __init__(self, viewer=None): self._list = [] self._qt = QtLayerPanel(self) self._viewer = None self.total = 0 self.events = EmitterGroup(source=self, auto_connect=True, add_item=ItemEvent, remove_item=ItemEvent, reorder=Event) self.events.add_item.connect(self._add) self.events.remove_item.connect(self._remove) self.events.reorder.connect(self._reorder) # property setting - happens last self.viewer = viewer def __str__(self): return str(self._list) def __repr__(self): return repr(self._list) def __iter__(self): return iter(self._list) def __contains__(self, item): return item in self._list def __len__(self): return len(self._list) def __getitem__(self, i): return self._list[i] @property def viewer(self): """Viewer: Parent viewer. """ if self._viewer is None: return self._viewer return self._viewer() @viewer.setter def viewer(self, viewer): prev = self.viewer if viewer == prev: return if prev is not None: self.events.add_item.disconnect(prev._on_layers_change) self.events.remove_item.disconnect(prev._on_layers_change) for layer in self: layer.viewer = viewer if viewer is not None: self.events.add_item.connect(viewer._on_layers_change) self.events.remove_item.connect(viewer._on_layers_change) viewer = weakref.ref(viewer) self._viewer = viewer def _to_index(self, obj): """Ensures that an object is a proper integer index. Parameters ---------- obj : int or Layer Object to be converted. Returns ------- index : int Index of the object if it is not already an int. """ if _check_layer(obj): return self.index(obj) if not isinstance(obj, int): raise TypeError(f'expected {obj} to be int or Layer; ' f'got {type(obj)}') from None return obj def _reordered_list(self, ordering): """Generates the reordered list given an ordering. Parameters ---------- ordering : iterable of int Ordering of the indices to use. Yields ------ item : Layer Next layer in the ordered list. Raises ------ ValueError When the improper indices are used. """ expected = list(range(len(self))) for o in ordering: if not isinstance(o, int): raise TypeError(f'expected {o} to be int; ' f'got {type(o)}') from None try: expected.remove(o) except ValueError: raise ValueError(f'duplicate index: {o}') from None yield self._list[o] if expected: raise ValueError(f'indices {tuple(expected)} not provided') def append(self, item): """Appends a layer to the list. Parameters ---------- item : Layer Layer to append. """ _check_layer(item, error=True) self._list.append(item) self.events.add_item(item=item, index=len(self)-1) self.total = self.total+1 def insert(self, index, item): """Inserts an item before an index. Parameters ---------- index : int Index to insert before. item : Layer Layer to insert. """ _check_layer(item, error=True) self._list.insert(index, item) self.events.add_item(item=item, index=index-1) self.total = self.total+1 def pop(self, index=-1): """Removes and returns an item given an index. Parameters ---------- index : int, optional Index to remove. Returns ------- item : Layer Removed item. """ item = self._list.pop(index) self.events.remove_item(item=item) def remove(self, item): """Removes an item from the list. Parameters ---------- item : Layer Item to remove. """ self._list.remove(item) self.events.remove_item(item=item) def __delitem__(self, index): """Removes an item given its index. Parameters ---------- index : int Index of the item to remove. """ self.pop(index) def swap(self, a, b): """Swaps the ordering of two elements in the list. Parameters ---------- a : Layer or int Layer to swap or its index. b : Layer or int Layer to swap or its index. """ i = self._to_index(a) j = self._to_index(b) self._list[i], self._list[j] = self._list[j], self._list[i] self.events.reorder() def reorder(self, *ordering): """Reorders the list given an iterable of its elements or their indices. Parameters ---------- ordering : iterable of Layer or int Ordering of the items. Can also be used as *args. Notes ----- LayerList.reorder(i, j, k, ...) LayerList.reorder([i, j, k, ...]) """ if not isinstance(ordering[0], (int, Layer)): ordering = ordering[0] if not isinstance(ordering, Sequence): raise TypeError(f'expected {ordering} to be Sequence; ' f'got {type(ordering)}') from None self._list[:] = self._reordered_list(self._to_index(o) for o in ordering) self.events.reorder() def index(self, item, start=None, stop=None): """Finds the index of an item in the list. Parameters ---------- item : object Querying item.. start : int, optional Start of slice index to look. stop : int, optional Stop of slice index to look. Returns ------- index : int Index of the item. Raises ------ ValueError When the item is not in the list. """ args = (item,) if stop is not None and start is None: start = 0 if start is not None: args += (start,) if stop is not None: args += (stop,) return self._list.index(*args) def _add(self, event): """Callback when an item is added to set its order and viewer. """ layer = event.item self._qt.layersList.insert(event.index, len(self), layer) layer._order = -len(self) layer.viewer = self.viewer def _remove(self, event): """Callback when an item is removed to remove its viewer and reset its order. """ layer = event.item self._qt.layersList.remove(layer) layer.viewer = None layer._order = 0 def _reorder(self, event): """Callback when the list is reordered to propagate those changes to the node draw order. """ for i in range(len(self)): self[i]._order = -i self._qt.layersList.reorder() canvas = self.viewer._canvas canvas._draw_order.clear() canvas.update() def remove_selected(self): """Removes selected items from list. """ to_delete = [] for i in range(len(self)): if self[i].selected: to_delete.append(i) to_delete.reverse() for i in to_delete: self.pop(i)