OSDN Git Service

layer move: make undoable, distance weighting etc
authorAndrew Chadwick <andrewc-git@piffle.org>
Thu, 24 Nov 2011 20:23:57 +0000 (20:23 +0000)
committerAndrew Chadwick <andrewc-git@piffle.org>
Fri, 9 Dec 2011 18:24:19 +0000 (18:24 +0000)
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.

gui/dragfunc.py
lib/command.py
lib/document.py
lib/layer.py
lib/tiledsurface.py

index da5884e..d93d765 100644 (file)
@@ -114,7 +114,7 @@ class LayerMoveDragFunc (DragFunc):
         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:
@@ -130,15 +130,17 @@ class LayerMoveDragFunc (DragFunc):
                 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:
index fe58bd5..b4d7240 100644 (file)
@@ -28,7 +28,7 @@ class CommandStack:
         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()
@@ -214,7 +214,32 @@ class SelectLayer(Action):
         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
@@ -260,7 +285,7 @@ class DuplicateLayer(Action):
         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[:]
index b6dee65..6822c31 100644 (file)
@@ -159,8 +159,12 @@ class Document():
     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))
index 021d811..679e875 100644 (file)
@@ -46,9 +46,6 @@ class Layer:
 
         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)
@@ -100,12 +97,20 @@ class Layer:
         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)
 
index 0579652..3d6dd4f 100644 (file)
@@ -406,26 +406,23 @@ class Surface(mypaintlib.TiledSurface):
             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)
 
 
@@ -472,54 +469,9 @@ class Surface(mypaintlib.TiledSurface):
         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))