Allow the layer-move action to be undone.
Distance-weight and dither the tiles being dragged around. Use modern
key-based sorting too.
General code tidy and path reduction.
model_dx = model_x - self.model_x0
model_dy = model_y - self.model_y0
self.final_model_dx = model_dx
- self.final_final_dy = model_dy
+ self.final_model_dy = model_dy
self.offsets = self.layer.update_interactive_move(model_dx, model_dy)
self.chunks_i = 0
if self.idle_srcid is None:
layer = self.layer
self.tdw.set_sensitive(False)
self.tdw.set_override_cursor(gdk.Cursor(gdk.WATCH))
- def process_remaining():
- layer.process_interactive_move_queue(\
- self.snapshot, chunks, self.offsets)
- self.tdw.set_sensitive(True)
- self.tdw.set_override_cursor(None)
- self.offsets = None
- return False
- gobject.idle_add(process_remaining)
- # TODO: make this undoable
+ while gtk.events_pending():
+ gtk.main_iteration() # HACK to set the cursor
+ # Finish up
+ layer.process_interactive_move_queue(\
+ self.snapshot, chunks, self.offsets)
+ self.tdw.set_sensitive(True)
+ self.tdw.set_override_cursor(None)
+ self.offsets = None
+ dx = self.final_model_dx
+ dy = self.final_model_dy
+ self.model.record_layer_move(self.layer, dx, dy)
def idle_cb(self, k=200):
if self.idle_srcid is None:
self.undo_stack.append(command)
self.reduce_undo_history()
self.notify_stack_observers()
-
+
def undo(self):
if not self.undo_stack: return
for f in self.call_before_action: f()
self._notify_document_observers()
class MoveLayer(Action):
- display_name = _("Move Layer")
+ display_name = _("Move Layer on Canvas")
+ # NOT "Move Layer" for now - old translatable string with different sense
+ def __init__(self, doc, layer_idx, dx, dy, ignore_first_redo=True):
+ self.doc = doc
+ self.layer_idx = layer_idx
+ self.dx = dx
+ self.dy = dy
+ self.ignore_first_redo = ignore_first_redo
+ def redo(self):
+ layer = self.doc.layers[self.layer_idx]
+ if self.ignore_first_redo:
+ # these are typically created interactively, after
+ # the entire layer has been moved
+ self.ignore_first_redo = False
+ else:
+ layer.translate(self.dx, self.dy)
+ self._notify_canvas_observers([layer])
+ self._notify_document_observers()
+ def undo(self):
+ layer = self.doc.layers[self.layer_idx]
+ layer.translate(-self.dx, -self.dy)
+ self._notify_canvas_observers([layer])
+ self._notify_document_observers()
+
+class ReorderSingleLayer(Action):
+ display_name = _("Reorder Layer in Stack")
def __init__(self, doc, was_idx, new_idx, select_new=False):
self.doc = doc
self.was_idx = was_idx
self._notify_document_observers()
class ReorderLayers(Action):
- display_name = _("Reorder Layer")
+ display_name = _("Reorder Layer Stack")
def __init__(self, doc, new_order):
self.doc = doc
self.old_order = doc.layers[:]
def select_layer(self, idx):
self.do(command.SelectLayer(self, idx))
+ def record_layer_move(self, layer, dx, dy):
+ layer_idx = self.layers.index(layer)
+ self.do(command.MoveLayer(self, layer_idx, dx, dy, True))
+
def move_layer(self, was_idx, new_idx, select_new=False):
- self.do(command.MoveLayer(self, was_idx, new_idx, select_new))
+ self.do(command.ReorderSingleLayer(self, was_idx, new_idx, select_new))
def duplicate_layer(self, insert_idx=None, name=''):
self.do(command.DuplicateLayer(self, insert_idx, name))
self.clear()
- def translate(self, dx, dy):
- self._surface.translate(dx, dy)
-
def _notify_content_observers(self, *args):
for f in self.content_observers:
f(*args)
self._surface.load_snapshot(data)
+ def translate(self, dx, dy):
+ """Translate a layer non-interactively.
+ """
+ snapshot, chunks = self.begin_interactive_move(0, 0)
+ offsets = self.update_interactive_move(dx, dy)
+ self.process_interactive_move_queue(snapshot, chunks, offsets)
+
+
def begin_interactive_move(self, x, y):
"""Start an interactive move.
- Returns ``(snapshot, chunks)``, and blanks the current layer. In the
- current implementation, ``chunks`` is a list of tile indices which
- is sorted by proximity to the initial position.
+ Returns ``(snapshot, chunks)``. In the current implementation,
+ ``chunks`` is a list of tile indices which is sorted by proximity to
+ the initial position.
"""
return self._surface.begin_interactive_move(x, y)
if not data.rgba.any():
self.tiledict.pop(pos)
+
def begin_interactive_move(self, x, y):
snapshot = self.save_snapshot()
chunks = snapshot.tiledict.keys()
- tx = x // 64
- ty = y // 64
- def _cmp_chebyshev(txy1, txy2):
- dtx1 = abs(tx - txy1[0])
- dtx2 = abs(tx - txy2[0])
- dty1 = abs(ty - txy1[1])
- dty2 = abs(ty - txy2[1])
- return cmp(max(dty1,dtx1), max(dty2,dtx2))
-
- # Blank everything
- self.tiledict = {}
- for pos in chunks:
- self._mark_mipmap_dirty(*pos)
- bbox = get_tiles_bbox(chunks)
- self.notify_observers(*bbox)
-
- chunks.sort(cmp=_cmp_chebyshev)
+ tx = x // N
+ ty = y // N
+ #chebyshev = lambda p: max(abs(tx - p[0]), abs(ty - p[1]))
+ #euclidean = lambda p: math.sqrt((tx - p[0])**2 + (ty - p[1])**2)
+ def mandala(p):
+ # A sort of distance weighted, dithered effect favouring
+ # the area around the mouse pointer.
+ c = max(abs(tx - p[0]), abs(ty - p[1]))
+ for i in (17,13,11,7,5,3,2):
+ if (p[0] % i == 0) and (p[1] % i == 0):
+ return c // i
+ return c
+ chunks.sort(key=mandala)
return (snapshot, chunks)
self.notify_observers(*bbox)
- def translate(self, dx, dy, prune=True):
- """Translates by (dx,dy) pixels.
- """
-
- dx = int(dx)
- dy = int(dy)
-
- src_tiles = self.tiledict
- dirty_tiles = set(src_tiles.keys())
-
- if (dx, dy) == (0, 0):
- return
-
- self.tiledict = {}
-
- slices_x = calc_translation_slices(dx)
- slices_y = calc_translation_slices(dy)
- is_integral = len(slices_x) == 1 and len(slices_y) == 1
-
- for (src_tx, src_ty), src_tile in src_tiles.iteritems():
- for (src_x0, src_x1), (targ_tdx, targ_x0, targ_x1) in slices_x:
- for (src_y0, src_y1), (targ_tdy, targ_y0, targ_y1) in slices_y:
- targ_tx = src_tx + targ_tdx
- targ_ty = src_ty + targ_tdy
- if is_integral:
- targ_tile = src_tile
- self.tiledict[(targ_tx, targ_ty)] = targ_tile
- else:
- targ_tile = self.tiledict.get((targ_tx, targ_ty), None)
- if targ_tile is None:
- targ_tile = Tile()
- self.tiledict[(targ_tx, targ_ty)] = targ_tile
- targ_tile.rgba[targ_y0:targ_y1, targ_x0:targ_x1] \
- = src_tile.rgba[src_y0:src_y1, src_x0:src_x1]
-
- dirty_tiles.update(self.tiledict.keys())
- for loc in dirty_tiles:
- self._mark_mipmap_dirty(*loc)
- if prune and not is_integral:
- self.remove_empty_tiles()
-
- bbox = get_tiles_bbox(dirty_tiles)
- self.notify_observers(*bbox)
-
-
def calc_translation_slices(dc):
"""Returns a list of offsets and slice extents for a translation of `dc`.
-
+
The returned slice list's members are of the form
((src_c0, src_c1), (targ_tdc, targ_c0, targ_c1))