[pitivi] timeline: Fix flickering when dragging a thin layer over a thick layer



commit 144f47d82aa15252c7168ccadd96b038c8145008
Author: Alexandru Băluț <alexandru balut gmail com>
Date:   Mon Oct 5 00:19:58 2015 +0200

    timeline: Fix flickering when dragging a thin layer over a thick layer
    
    Now there is a parameter called past_middle_when_adjacent to
    __getLayerAt. If y is exactly on the layer above or below the preferred
    layer, normally we'd return that layer/position.
    Consider there are thin layers and thick layers.
    If past_middle_when_adjacent is True, y must be far enough so that if the
    layers would be switched, y will be on preferred_layer. Otherwise if the
    layers are switched but y is still on the same layer, flickering takes
    place.
    
    Differential Revision: https://phabricator.freedesktop.org/D327

 pitivi/timeline/timeline.py     |   28 ++++++---
 tests/Makefile.am               |    1 +
 tests/test_timeline_timeline.py |  128 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 147 insertions(+), 10 deletions(-)
---
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index b754d86..8504185 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -616,7 +616,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         elif self.__moving_layer:
             event_widget = self.get_event_widget(event)
             unused_x, y = event_widget.translate_coordinates(self, event.x, event.y)
-            layer, unused_on_sep = self.__getLayerAt(y, prefer_bLayer=self.__moving_layer)
+            layer, unused_on_sep = self.__getLayerAt(
+                y, prefer_bLayer=self.__moving_layer, past_middle_when_adjacent=True)
             if layer != self.__moving_layer:
                 priority = layer.get_priority()
                 self.moveLayer(self.__moving_layer, priority)
@@ -819,10 +820,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         layer = layers.pop(bLayer.get_priority())
         layers.insert(index, layer)
 
-        i = 0
-        for layer in layers:
+        for i, layer in enumerate(layers):
             layer.set_priority(i)
-            i += 1
 
     def _addLayer(self, bLayer):
         control = LayerControls(bLayer, self.app)
@@ -852,9 +851,8 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
     def __resetLayersByPriority(self, reset=False):
         self._layers.sort(key=lambda layer: layer.bLayer.props.priority)
-        i = 0
         self.debug("Reseting layers priorities")
-        for layer in self._layers:
+        for i, layer in enumerate(self._layers):
             if reset:
                 layer.bLayer.props.priority = i
 
@@ -866,8 +864,6 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
                                                            "position",
                                                            layer.bLayer.props.priority)
 
-            i += 1
-
     def _removeLayer(self, bLayer):
         self.info("Removing layer: %s", bLayer.props.priority)
         self.__layers_vbox.remove(bLayer.ui.get_parent())
@@ -918,7 +914,7 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
 
         return [getattr(bLayer.ui, sep_name), getattr(bLayer.control_ui, sep_name)]
 
-    def __getLayerAt(self, y, prefer_bLayer=None):
+    def __getLayerAt(self, y, prefer_bLayer=None, past_middle_when_adjacent=False):
         bLayers = self.bTimeline.get_layers()
         if y < 20:
             # The cursor is at the top, above the first layer.
@@ -932,12 +928,24 @@ class Timeline(Gtk.EventBox, Zoomable, Loggable):
         # as possible when having an option (y is between two layers).
         prefer_after = True
 
+        if past_middle_when_adjacent:
+            index_preferred = prefer_bLayer.get_priority()
+            height_preferred = prefer_bLayer.ui.get_allocation().height
+
         for i, bLayer in enumerate(bLayers):
             layer_rect = bLayer.ui.get_allocation()
             layer_y = layer_rect.y
             layer_height = layer_rect.height
             if layer_y <= y < layer_y + layer_height:
-                # The cursor is on a layer.
+                # The cursor is exactly on bLayer.
+                if past_middle_when_adjacent:
+                    # Check if far enough from prefer_bLayer.
+                    delta = index_preferred - bLayer.get_priority()
+                    if (delta == 1 and y >= layer_y + height_preferred) or \
+                            (delta == -1 and y < layer_y + layer_height - height_preferred):
+                        # bLayer is adjacent to prefer_bLayer, but the cursor
+                        # is not far enough to warrant a change.
+                        return prefer_bLayer, []
                 return bLayer, []
 
             separators = self.__layerGetSeps(bLayer, "after_sep")
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 180ffd9..d1d972e 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -15,6 +15,7 @@ tests =       \
        test_project.py \
        test_projectsettings.py \
        test_system.py \
+       test_timeline_timeline.py \
        test_undo.py \
        test_undo_timeline.py \
        test_utils.py \
diff --git a/tests/test_timeline_timeline.py b/tests/test_timeline_timeline.py
new file mode 100644
index 0000000..b31d8b7
--- /dev/null
+++ b/tests/test_timeline_timeline.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2015, Alex Băluț <alexandru balut gmail com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+import mock
+from unittest import TestCase
+
+from gi.repository import Gdk
+
+from pitivi.project import Project, ProjectManager
+from pitivi.timeline.timeline import Timeline
+from pitivi.utils import ui
+
+
+SEPARATOR_HEIGHT = 4
+THIN = ui.LAYER_HEIGHT / 2
+THICK = ui.LAYER_HEIGHT
+
+
+class TestLayers(TestCase):
+
+    def createTimeline(self, layers_heights):
+        project_manager = ProjectManager(app=None)
+        project_manager.newBlankProject()
+        project = project_manager.current_project
+        timeline = Timeline(container=None, app=None)
+        timeline.get_parent = mock.MagicMock()
+        timeline.setProject(project)
+        y = 0
+        for priority, height in enumerate(layers_heights):
+            bLayer = timeline.createLayer(priority=priority)
+            rect = Gdk.Rectangle()
+            rect.y = y
+            rect.height = height
+            bLayer.ui.set_allocation(rect)
+            y += height + SEPARATOR_HEIGHT
+        return timeline
+
+    def testDraggingLayer(self):
+        self.checkGetLayerAt([THIN, THIN, THIN], 1, True,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THICK, THICK], 1, True,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THIN, THICK, THIN], 1, True,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THIN, THICK], 1, True,
+                             [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2])
+
+    def testDraggingClipFromLayer(self):
+        self.checkGetLayerAt([THIN, THIN, THIN], 1, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THICK, THICK], 1, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THIN, THICK, THIN], 1, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THIN, THICK], 1, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2])
+
+    def testDraggingClipFromOuterSpace(self):
+        self.checkGetLayerAt([THIN, THIN, THIN], None, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THICK, THICK], None, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])
+        self.checkGetLayerAt([THIN, THICK, THIN], None, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])
+        self.checkGetLayerAt([THICK, THIN, THICK], None, False,
+                             [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2])
+
+    def checkGetLayerAt(self, heights, preferred, past_middle_when_adjacent, expectations):
+        timeline = self.createTimeline(heights)
+        bLayers = [layer.bLayer for layer in timeline._layers]
+        if preferred is None:
+            preferred_bLayer = None
+        else:
+            preferred_bLayer = bLayers[preferred]
+        h = [layer.get_allocation().height for layer in timeline._layers]
+        s = SEPARATOR_HEIGHT
+
+        def assertLayerAt(bLayer, y):
+            result = timeline._Timeline__getLayerAt(
+                int(y),
+                prefer_bLayer=preferred_bLayer,
+                past_middle_when_adjacent=past_middle_when_adjacent)
+            self.assertEqual(
+                bLayer,
+                result[0],
+                "Expected %d, got %d at %d" % (bLayers.index(bLayer), bLayers.index(result[0]), y))
+
+        # y on the top layer.
+        assertLayerAt(bLayers[expectations[0]], 0)
+        assertLayerAt(bLayers[expectations[1]], h[0] / 2 - 1)
+        assertLayerAt(bLayers[expectations[2]], h[0] / 2)
+        assertLayerAt(bLayers[expectations[3]], h[0] - 1)
+
+        # y on the separator.
+        assertLayerAt(bLayers[expectations[4]], h[0])
+        assertLayerAt(bLayers[expectations[5]], h[0] + s - 1)
+
+        # y on the middle layer.
+        assertLayerAt(bLayers[expectations[6]], h[0] + s)
+        assertLayerAt(bLayers[expectations[7]], h[0] + s + h[1] / 2 - 1)
+        assertLayerAt(bLayers[expectations[8]], h[0] + s + h[1] / 2)
+        assertLayerAt(bLayers[expectations[9]], h[0] + s + h[1] - 1)
+
+        # y on the separator.
+        assertLayerAt(bLayers[expectations[10]], h[0] + s + h[1])
+        assertLayerAt(bLayers[expectations[11]], h[0] + s + h[1] + s - 1)
+
+        # y on the bottom layer.
+        assertLayerAt(bLayers[expectations[12]], h[0] + s + h[1] + s)
+        assertLayerAt(bLayers[expectations[13]], h[0] + s + h[1] + s + h[2] / 2 - 1)
+        assertLayerAt(bLayers[expectations[14]], h[0] + s + h[1] + s + h[2] / 2)
+        assertLayerAt(bLayers[expectations[15]], h[0] + s + h[1] + s + h[2] - 1)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]