timeline: reimplement snap_to_position a bit more appropriately.

It could yet be made be simpler, but it would require
touching the rest of the timeline editing code.

Fixes https://phabricator.freedesktop.org/T7587

Reviewed-by: Thibault Saunier <thibault.saunier@collabora.com>
Differential Revision: https://phabricator.freedesktop.org/D657
This commit is contained in:
Mathieu Duponchelle 2016-01-12 14:51:55 +00:00 committed by Thibault Saunier
parent ff6dc38900
commit 73cf36fa25
4 changed files with 59 additions and 81 deletions

View file

@ -1271,85 +1271,46 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackElement * obj1,
} }
} }
static guint64 * static GstClockTime *
ges_timeline_snap_position (GESTimeline * timeline, ges_timeline_snap_position (GESTimeline * timeline,
GESTrackElement * trackelement, guint64 * current, guint64 timecode, GESTrackElement * trackelement, GstClockTime * current,
gboolean emit) GstClockTime timecode, gboolean emit)
{ {
GESTimelinePrivate *priv = timeline->priv; GESTimelinePrivate *priv = timeline->priv;
GSequenceIter *iter, *prev_iter, *nxt_iter; GSequenceIter *iter, *end_iter;
GESTrackElement *tmp_trackelement; GESContainer *container = get_toplevel_container (trackelement);
GESContainer *tmp_container, *container; GstClockTime *ret = NULL;
GstClockTime smallest_offset = G_MAXUINT64;
GstClockTime tmp_pos;
GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts; tmp_pos = timecode - priv->snapping_distance;
guint64 snap_distance = timeline->priv->snapping_distance; /* Rippling, not snapping with previous elements */
guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 = if (priv->movecontext.moving_trackelements)
G_MAXUINT64; tmp_pos = timecode;
iter = g_sequence_search (priv->starts_ends, &tmp_pos,
/* Avoid useless calculations */
if (snap_distance == 0)
return NULL;
/* If we can just resnap as last snap... do it */
if (last_snap_ts) {
off = timecode > *last_snap_ts ?
timecode - *last_snap_ts : *last_snap_ts - timecode;
if (off <= snap_distance) {
ret = last_snap_ts;
goto done;
}
}
container = get_toplevel_container (trackelement);
iter = g_sequence_search (priv->starts_ends, &timecode,
(GCompareDataFunc) compare_uint64, NULL); (GCompareDataFunc) compare_uint64, NULL);
/* Getting the next/previous values, and use the closest one if any "respects" tmp_pos = timecode + priv->snapping_distance;
* the snap_distance value */ end_iter = g_sequence_search (priv->starts_ends, &tmp_pos,
nxt_iter = iter; (GCompareDataFunc) compare_uint64, NULL);
while (!g_sequence_iter_is_end (nxt_iter)) {
next_tc = g_sequence_get (iter);
tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, next_tc);
tmp_container = get_toplevel_container (tmp_trackelement);
off = timecode > *next_tc ? timecode - *next_tc : *next_tc - timecode; for (; iter != end_iter && !g_sequence_iter_is_end (iter);
if (next_tc != current && off <= snap_distance iter = g_sequence_iter_next (iter)) {
&& container != tmp_container) { GstClockTime *iter_tc = g_sequence_get (iter);
GESTrackElement *tmp_trackelement =
g_hash_table_lookup (priv->by_object, iter_tc);
GESContainer *tmp_container = get_toplevel_container (tmp_trackelement);
ret = next_tc; if (tmp_container == container)
continue;
if (ABS (timecode - *iter_tc) > smallest_offset)
break; break;
}
nxt_iter = g_sequence_iter_next (nxt_iter); smallest_offset = ABS (timecode - *iter_tc);
ret = iter_tc;
} }
if (ret == NULL)
off = G_MAXUINT64;
if (priv->movecontext.moving_trackelements) {
GST_INFO_OBJECT (timeline, "Rippling, no way we snap end");
goto done;
}
prev_iter = g_sequence_iter_prev (iter);
while (!g_sequence_iter_is_begin (prev_iter)) {
prev_tc = g_sequence_get (prev_iter);
tmp_trackelement = g_hash_table_lookup (timeline->priv->by_object, prev_tc);
tmp_container = get_toplevel_container (tmp_trackelement);
off1 = timecode > *prev_tc ? timecode - *prev_tc : *prev_tc - timecode;
if (prev_tc != current && off1 < off && off1 <= snap_distance &&
container != tmp_container) {
ret = prev_tc;
break;
}
prev_iter = g_sequence_iter_prev (prev_iter);
}
done:
/* We emit the snapping signal only if we snapped with a different value /* We emit the snapping signal only if we snapped with a different value
* than the current one */ * than the current one */
if (emit) { if (emit) {
@ -1413,7 +1374,6 @@ ges_move_context_set_objects (GESTimeline * timeline, GESTrackElement * obj,
GSequenceIter *iter, *trackelement_iter; GSequenceIter *iter, *trackelement_iter;
MoveContext *mv_ctx = &timeline->priv->movecontext; MoveContext *mv_ctx = &timeline->priv->movecontext;
iters = g_hash_table_lookup (timeline->priv->obj_iters, obj); iters = g_hash_table_lookup (timeline->priv->obj_iters, obj);
trackelement_iter = iters->iter_obj; trackelement_iter = iters->iter_obj;
switch (edge) { switch (edge) {

View file

@ -393,9 +393,8 @@ GST_START_TEST (test_snapping)
* time 26-------- 62 --------122 * time 26-------- 62 --------122
*/ */
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip1), 26); ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip1), 26);
ges_timeline_element_set_duration (GES_TIMELINE_ELEMENT (clip1), 37);
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
CHECK_OBJECT_PROPS (trackelement1, 26, 5, 36); CHECK_OBJECT_PROPS (trackelement1, 26, 5, 37);
CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60); CHECK_OBJECT_PROPS (trackelement2, 62, 0, 60);
/** /**
@ -409,7 +408,7 @@ GST_START_TEST (test_snapping)
fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1), fail_unless (ges_timeline_element_ripple (GES_TIMELINE_ELEMENT (clip1),
58) == TRUE); 58) == TRUE);
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36); CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
CHECK_OBJECT_PROPS (trackelement2, 98, 0, 60); CHECK_OBJECT_PROPS (trackelement2, 98, 0, 60);
/** /**
@ -419,7 +418,7 @@ GST_START_TEST (test_snapping)
*/ */
ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip2), 110); ges_timeline_element_set_start (GES_TIMELINE_ELEMENT (clip2), 110);
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36); CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
/** /**
@ -430,7 +429,7 @@ GST_START_TEST (test_snapping)
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
GES_EDGE_NONE, 72) == TRUE); GES_EDGE_NONE, 72) == TRUE);
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
CHECK_OBJECT_PROPS (trackelement1, 73, 5, 36); CHECK_OBJECT_PROPS (trackelement1, 73, 5, 37);
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);
/** /**
@ -441,7 +440,7 @@ GST_START_TEST (test_snapping)
fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL, fail_unless (ges_container_edit (clip1, NULL, -1, GES_EDIT_MODE_NORMAL,
GES_EDGE_NONE, 58) == TRUE); GES_EDGE_NONE, 58) == TRUE);
CHECK_OBJECT_PROPS (trackelement, 25, 0, 37); CHECK_OBJECT_PROPS (trackelement, 25, 0, 37);
CHECK_OBJECT_PROPS (trackelement1, 62, 5, 36); CHECK_OBJECT_PROPS (trackelement1, 62, 5, 37);
CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60); CHECK_OBJECT_PROPS (trackelement2, 110, 0, 60);

View file

@ -25,8 +25,6 @@ gi.require_version("GES", "1.0")
from gi.repository import Gst # noqa from gi.repository import Gst # noqa
from gi.repository import GES # noqa from gi.repository import GES # noqa
from .common import GESSimpleTimelineTest # noqa
import unittest # noqa import unittest # noqa

View file

@ -27,7 +27,9 @@ from gi.repository import GES # noqa
import unittest # noqa import unittest # noqa
from unittest import mock from unittest import mock
from . import common from .common import create_main_loop
from .common import create_project
from .common import GESSimpleTimelineTest # noqa
Gst.init(None) Gst.init(None)
GES.init() GES.init()
@ -36,8 +38,8 @@ GES.init()
class TestTimeline(unittest.TestCase): class TestTimeline(unittest.TestCase):
def test_signals_not_emitted_when_loading(self): def test_signals_not_emitted_when_loading(self):
mainloop = common.create_main_loop() mainloop = create_main_loop()
timeline = common.create_project(with_group=True, saved=True) timeline = create_project(with_group=True, saved=True)
# Reload the project, check the group. # Reload the project, check the group.
project = GES.Project.new(uri=timeline.get_asset().props.uri) project = GES.Project.new(uri=timeline.get_asset().props.uri)
@ -52,7 +54,6 @@ class TestTimeline(unittest.TestCase):
timeline = project.extract() timeline = project.extract()
signals = ["layer-added", "group-added", "track-added"] signals = ["layer-added", "group-added", "track-added"]
called = []
handle = mock.Mock() handle = mock.Mock()
for signal in signals: for signal in signals:
timeline.connect(signal, handle) timeline.connect(signal, handle)
@ -61,7 +62,7 @@ class TestTimeline(unittest.TestCase):
self.assertTrue(loaded_called) self.assertTrue(loaded_called)
handle.assert_not_called() handle.assert_not_called()
class TestEditing(common.GESSimpleTimelineTest): class TestEditing(GESSimpleTimelineTest):
def test_transition_disappears_when_moving_to_another_layer(self): def test_transition_disappears_when_moving_to_another_layer(self):
self.timeline.props.auto_transition = True self.timeline.props.auto_transition = True
@ -111,3 +112,23 @@ class TestEditing(common.GESSimpleTimelineTest):
self.assertEquals(len(self.layer.get_clips()), 4) self.assertEquals(len(self.layer.get_clips()), 4)
clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND) clip1.edit([], self.layer.get_priority(), GES.EditMode.EDIT_RIPPLE, GES.Edge.EDGE_NONE, 35 * Gst.SECOND)
self.assertEquals(len(self.layer.get_clips()), 4) self.assertEquals(len(self.layer.get_clips()), 4)
class TestSnapping(GESSimpleTimelineTest):
def test_snapping(self):
self.timeline.props.auto_transition = True
self.timeline.set_snapping_distance(1)
clip1 = self.add_clip(0, 0, 100)
# Split clip1.
split_position = 50
clip2 = clip1.split(split_position)
self.assertEquals(len(self.layer.get_clips()), 2)
self.assertEqual(clip1.props.duration, split_position)
self.assertEqual(clip2.props.start, split_position)
# Make sure snapping prevents clip2 to be moved to the left.
clip2.edit([], self.layer.get_priority(), GES.EditMode.EDIT_NORMAL, GES.Edge.EDGE_NONE,
clip2.props.start - 1)
self.assertEqual(clip2.props.start, split_position)