private void owner_MouseMovePicked(object sender, HitEventArgs e) { string toolTipText = null; if (m_active) { e.Handled = true; if (e.HitRecord.Type == HitType.Interval && e.MouseEvent.Button == MouseButtons.None && !m_owner.IsUsingMouse && m_owner.IsEditable(e.HitRecord.HitPath)) { SetCursor(); TimelinePath hitPath = e.HitRecord.HitPath; IInterval hitObject = (IInterval)e.HitRecord.HitTimelineObject; float worldX = GdiUtil.InverseTransform(m_owner.Transform, e.MouseEvent.Location.X); //Make sure the snap-to indicator line is drawn. float delta = m_owner.GetSnapOffset(new[] { worldX }, s_snapOptions); worldX += delta; worldX = m_owner.ConstrainFrameOffset(worldX); Matrix localToWorld = TimelineControl.CalculateLocalToWorld(hitPath); if (worldX <= GdiUtil.Transform(localToWorld, hitObject.Start) || worldX >= GdiUtil.Transform(localToWorld, hitObject.Start + hitObject.Length)) { // Clear the results since a split is impossible. m_owner.GetSnapOffset(new float[] { }, s_snapOptions); } else { toolTipText = worldX.ToString(); } } } if (toolTipText != null) { m_toolTip.Show(toolTipText, m_owner, e.MouseEvent.Location); } else { m_toolTip.Hide(m_owner); } }
// private because this may be moved to TimelineControl private bool Overlaps(TimelinePath path, float beginTime, float endTime) { IEvent e = (IEvent)path.Last; Matrix localToWorld = TimelineControl.CalculateLocalToWorld(path); float start = GdiUtil.Transform(localToWorld, e.Start); float length = GdiUtil.TransformVector(localToWorld, e.Length); // If the length is zero, then count an exact match with beginTime or endTime as // being an overlap. if (length == 0) { return(!( start > endTime || start + length < beginTime)); } // Otherwise, don't count an exact match. return(!( start >= endTime || start + length <= beginTime)); }
private void owner_MouseDownPicked(object sender, HitEventArgs e) { TimelinePath hitPath = e.HitRecord.HitPath; IInterval hitInterval = e.HitRecord.HitTimelineObject as IInterval; if (m_active && e.MouseEvent.Button == MouseButtons.Left && !m_owner.IsUsingMouse && m_owner.IsEditable(hitPath)) { if (hitInterval != null && e.HitRecord.Type == HitType.Interval) { float worldX = Sce.Atf.GdiUtil.InverseTransform(m_owner.Transform, e.MouseEvent.Location.X); worldX += m_owner.GetSnapOffset(new[] { worldX }, s_snapOptions); Matrix localToWorld = TimelineControl.CalculateLocalToWorld(hitPath); float fraction = (worldX - GdiUtil.Transform(localToWorld, hitInterval.Start)) / GdiUtil.TransformVector(localToWorld, hitInterval.Length); if (m_owner.Selection.SelectionContains(hitInterval)) { SplitSelectedIntervals(fraction); } else { SplitUnselectedInterval(hitInterval, fraction); } } Active = false; e.Handled = true; //don't let subsequent listeners get this event } }
/// <summary> /// Gets the offset from one of the world snap points to the closest non-selected object's edge</summary> /// <param name="movingPoints">The x-coordinates to snap "from", in world coordinates</param> /// <param name="options">The options to control the behavior. If null, the defaults are used.</param> /// <returns>The value to be added, to GetDragOffset().X, for example. Is in world coordinates.</returns> private float GetSnapOffset(IEnumerable <float> movingPoints, TimelineControl.SnapOptions options) { //we want to recalculate m_snapInfo and reflect changes to the snap-to lines m_snapInfo.Clear(); m_owner.Invalidate(); if (options == null) { options = new TimelineControl.SnapOptions(); } // Check for user-forced snapping and no-snapping. if (options.CheckModifierKeys) { Keys modKeys = Control.ModifierKeys; if (s_deactivatorKeys != Keys.None && (modKeys & s_deactivatorKeys) == s_deactivatorKeys) { return(0.0f); } if (s_activatorKeys != Keys.None && (modKeys & s_activatorKeys) != s_activatorKeys) { return(0.0f); } } // Prepare helper object on each moving point. foreach (float snapping in movingPoints) { m_snapInfo.Add(new SnapOffsetInfo(snapping)); } if (m_snapInfo.Count == 0) { return(0.0f); } // Find the closest IEvent. float worldSnapTolerance = GdiUtil.InverseTransformVector(m_owner.Transform, s_snapTolerance); List <TimelinePath> events = new List <TimelinePath>( TimelineControl.GetObjects <IEvent>(m_owner.Timeline)); // Allow for snapping to a scrubber manipulator. if (m_scrubber != null && options.IncludeScrubber) { events.Add(new TimelinePath(m_scrubber)); } foreach (TimelinePath path in events) { if (options.IncludeSelected || !m_owner.Selection.SelectionContains(path)) { IEvent snapToEvent = (IEvent)path.Last; if (options.Filter == null || options.Filter(snapToEvent, options)) { Matrix localToWorld = TimelineControl.CalculateLocalToWorld(path); float start, length; GetEventDimensions(snapToEvent, localToWorld, out start, out length); foreach (SnapOffsetInfo info in m_snapInfo) { info.Update(start, snapToEvent, worldSnapTolerance); if (length > 0) { info.Update(start + length, snapToEvent, worldSnapTolerance); } } } } } // Keep only the shortest distance snap-to points. Could be multiple in case of tie. SnapOffsetInfo.RemoveInvalid(m_snapInfo); if (m_snapInfo.Count == 0) { return(0.0f); } SnapOffsetInfo topInfo = m_snapInfo[0]; return(topInfo.Offset); }
// gets move information, for drawing ghosts and for performing actual move operation private GhostInfo[] GetMoveGhostInfo(Matrix worldToView, TimelineLayout layout) { // get start and y offsets in timeline space PointF dragOffset = m_owner.GetDragOffset(); // Get snapping points along the timeline (in world coordinates). List <float> movingPoints = new List <float>(2); TimelinePath snapperPath; if (m_mouseMoveHitRecord != null) { snapperPath = m_mouseMoveHitRecord.HitPath;//use the last clicked event (interval, key or marker) } else { snapperPath = m_owner.Selection.LastSelected as TimelinePath;//moving a group or track, for example } IEvent snapperEvent = snapperPath != null ? snapperPath.Last as IEvent : null; if (snapperEvent != null) { Matrix localToWorld = TimelineControl.CalculateLocalToWorld(snapperPath); float worldStart = GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X); movingPoints.Add(worldStart); if (snapperEvent.Length > 0.0f) { movingPoints.Add(GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X + snapperEvent.Length)); } } // Get the offset from one of the world snap points to the closest non-selected object. float snapOffset; try { s_snapOptions.FilterContext = snapperEvent; s_snapOptions.Filter = new TimelineControl.SnapFilter(MoveSnapFilter); snapOffset = m_owner.GetSnapOffset(movingPoints, s_snapOptions); } finally { s_snapOptions.FilterContext = null; s_snapOptions.Filter = null; } // adjust dragOffset to "snap-to" nearest event dragOffset.X += snapOffset; // get offsets in client space float xOffset = dragOffset.X * worldToView.Elements[0]; float yOffset = dragOffset.Y * worldToView.Elements[3]; TimelinePath[] targets = GetMoveTargets(layout); GhostInfo[] ghosts = new GhostInfo[targets.Length]; int i = -1; foreach (TimelinePath path in m_owner.Selection.Selection) { i++; ITimelineObject timelineObject = path.Last; RectangleF bounds = layout[path]; TimelinePath targetPath = targets[i]; ITimelineObject target = targetPath != null ? targetPath.Last : null; float start = 0; float length = 0; bool valid = true; IInterval interval = timelineObject as IInterval; IKey key = timelineObject as IKey; IMarker marker = timelineObject as IMarker; ITrack track = timelineObject as ITrack; IGroup group = timelineObject as IGroup; ITimelineReference reference = timelineObject as ITimelineReference; if (interval != null) { ITrack targetTrack = target as ITrack; start = interval.Start + dragOffset.X; length = interval.Length; valid = targetTrack != null && m_owner.Constraints.IsStartValid(interval, ref start) && m_owner.Constraints.IsLengthValid(interval, ref length); if (valid) { yOffset = layout[target].Y - layout[interval.Track].Y; TimelinePath testPath = new TimelinePath(targetPath); foreach (IInterval other in targetTrack.Intervals) { // skip selected intervals, since they are moving too testPath.Last = other; if (m_owner.Selection.SelectionContains(testPath)) { continue; } if (!m_owner.Constraints.IsIntervalValid(interval, ref start, ref length, other)) { valid = false; break; } } } } else if (reference != null) { // don't allow for vertical repositioning yet start = reference.Start + dragOffset.X; valid = true; } else if (key != null) { start = key.Start + dragOffset.X; ITrack targetTrack = target as ITrack; valid = targetTrack != null && m_owner.Constraints.IsStartValid(key, ref start); if (valid) { yOffset = layout[targetTrack].Y - layout[key.Track].Y; } } else if (marker != null) { start = marker.Start + dragOffset.X; yOffset = 0; valid = m_owner.Constraints.IsStartValid(marker, ref start); } else if (track != null) { xOffset = 0; if (target == null) { target = (m_owner.DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack(); } } else if (group != null) { xOffset = 0; if (target == null) { IList <IGroup> groups = m_owner.TimelineDocument.Timeline.Groups; target = (m_owner.DragDelta.Y < 0) ? groups[0] : groups[groups.Count - 1]; } } bounds.Offset(xOffset, yOffset); ghosts[i] = new GhostInfo(timelineObject, target, start, length, bounds, valid); } return(ghosts); }