/// <summary> /// Constructor for a HitRecord representing a hit on an ITimelineObject. If this /// ITimelineObject is in the main document, then the path will have just one element /// in it. Otherwise, the elements of the path should be ITimelineReference objects /// plus some other ITimelineObject (like IInterval, for example) as the last element.</summary> /// <param name="type">Hit type</param> /// <param name="path">Full path of the hit timeline object</param> public HitRecord(HitType type, TimelinePath path) { Type = type; HitPath = path; HitTimelineObject = path != null ? path.Last : null; HitObject = HitTimelineObject; }
private void owner_MouseMovePicked(object sender, HitEventArgs e) { if (e.MouseEvent.Button == MouseButtons.None) { HitRecord hitRecord = e.HitRecord; TimelinePath hitPath = hitRecord.HitPath; switch (hitRecord.Type) { case HitType.IntervalResizeLeft: case HitType.IntervalResizeRight: if (Owner.IsEditable(hitPath)) { Owner.Cursor = Cursors.SizeWE; } break; //one of the scale manipulator's handles? case HitType.Custom: if (hitRecord.HitObject == m_leftHitObject || hitRecord.HitObject == m_rightHitObject) { Owner.Cursor = Cursors.SizeWE; } break; default: break; } } }
// private because this may be moved to TimelineControl private bool Overlaps(TimelinePath path, float beginTime, float endTime) { IEvent e = (IEvent)path.Last; float start, length; using (Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path)) { start = GdiUtil.Transform(localToWorld, e.Start); 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) { if (e.MouseEvent.Button != MouseButtons.Left) { return; } TimelinePath hitPath = e.HitRecord.HitPath; bool isResizing = false; switch (e.HitRecord.Type) { case HitType.IntervalResizeLeft: if (Owner.IsEditable(hitPath) && ( Owner.Selection.SelectionContains(hitPath) || Owner.Select <IEvent>(hitPath))) { IInterval hitInterval = e.HitRecord.HitTimelineObject as IInterval; m_resizer = new Resizer(Side.Left, ScaleMode.InPlace, hitInterval.Start, Owner); isResizing = true; } break; case HitType.IntervalResizeRight: if (Owner.IsEditable(hitPath) && ( Owner.Selection.SelectionContains(hitPath) || Owner.Select <IEvent>(hitPath))) { IInterval hitInterval = e.HitRecord.HitTimelineObject as IInterval; m_resizer = new Resizer(Side.Right, ScaleMode.InPlace, hitInterval.Start + hitInterval.Length, Owner); isResizing = true; } break; case HitType.Custom: { HitRecordObject hitManipulator = e.HitRecord.HitObject as HitRecordObject; if (hitManipulator != null) { if (hitManipulator.Side == Side.Left) { m_resizer = new Resizer(Side.Left, ScaleMode.TimePeriod, m_worldMin, Owner); } else { m_resizer = new Resizer(Side.Right, ScaleMode.TimePeriod, m_worldMax, Owner); } isResizing = true; } } break; default: m_resizer = null; break; } Owner.IsResizingSelection = isResizing; //legacy. obsolete. }
/// <summary> /// Gets the last group as determined by the associated ITimeline</summary> /// <returns>Last group as TimelinePath</returns> protected TimelinePath GetLastGroup() { TimelinePath last = null; foreach (TimelinePath group in Owner.AllGroups) { last = group; } return(last); }
/// <summary> /// Gets the last track as determined by the associated ITimeline</summary> /// <returns>Last track as TimelinePath</returns> protected TimelinePath GetLastTrack() { TimelinePath last = null; foreach (TimelinePath track in Owner.AllTracks) { last = track; } return(last); }
private bool IsEditable(ITimelineObject item) { var path = new TimelinePath(item); TimelineDocument document = (TimelineDocument)TimelineEditor.TimelineDocumentRegistry.ActiveDocument; if (document != null) { return(document.TimelineControl.IsEditable(path)); } return(false); }
/// <summary> /// Checks whether the given timeline object's attribute is editable for the current /// context and document</summary> /// <param name="item">Timeline object that changed</param> /// <param name="attribute">Attribute on the timeline object that changed</param> /// <returns>True iff this timeline object attribute is editable for the current /// ActiveControl, ActiveContext, and ActiveDocument properties</returns> public virtual bool IsEditable(ITimelineObject item, AttributeInfo attribute) { if (attribute == Schema.groupType.expandedAttribute) { return(true); } TimelinePath path = new TimelinePath(item); return(ActiveControl.IsEditable(path)); }
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); using (Matrix localToWorld = D2dTimelineControl.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); } }
/// <summary> /// Adds range of tracks to selection, from anchor to given target. /// Handles selecting a track, taking into account selecting ranges of tracks.</summary> /// <param name="target">Track as TimelinePath</param> protected virtual void SelectTracks(TimelinePath target) { // add range of tracks, from anchor to target? if ((Control.ModifierKeys & Keys.Shift) != Keys.None) { if (Anchor != null && Anchor.Last is ITrack && SelectionContext != null) { SelectionContext.SetRange(GetRangeOfTracks(Anchor, target)); return; } } // simply add this target track, using the current modifier keys to determine how Owner.Select <ITrack>(target); }
/// <summary> /// Adds range of groups to selection, from anchor to given target. /// Handles selecting a group, taking into account selecting ranges of groups. /// If no anchor, just selects target.</summary> /// <param name="target">Timeline path</param> protected virtual void SelectGroups(TimelinePath target) { // Add range of groups, from anchor to target? Holding down Ctrl or Alt simultaneously // still does a range selection in Visual Studio's Solution Explorer. if ((Control.ModifierKeys & Keys.Shift) != Keys.None) { if (Anchor != null && Anchor.Last is IGroup && SelectionContext != null) { SelectionContext.SetRange(GetRangeOfGroups(Anchor, target)); return; } } // simply add this target group, using the current modifier keys to determine how Owner.Select <IGroup>(target); }
/// <summary> /// Event handler for the timeline control.MouseDownPicked event</summary> /// <param name="sender">Timeline control that we are attached to</param> /// <param name="e">Event args</param> protected virtual void Owner_MouseDownPicked(object sender, HitEventArgs e) { HitRecord hitRecord = e.HitRecord; TimelinePath hitObject = hitRecord.HitPath; if (hitObject != null) { Keys modifiers = Control.ModifierKeys; if (e.MouseEvent.Button == MouseButtons.Left) { if (modifiers == Keys.None && SelectionContext != null && SelectionContext.SelectionContains(hitObject)) { // The hit object is already selected. Wait until the mouse up to see if there was // a drag or not. If no drag, then set the selection set to be this one object. m_mouseDownHitRecord = hitRecord; m_mouseDownPos = e.MouseEvent.Location; } else if (modifiers == Keys.Control) { // Either this object is not already selected or Shift key is being held down. // to be consistent with the Windows interface. m_mouseDownHitRecord = hitRecord; m_mouseDownPos = e.MouseEvent.Location; } else if ((modifiers & Keys.Alt) == 0) { // The 'Alt' key might mean something different. If no Alt key, we can update the // selection immediately. UpdateSelection(hitRecord, modifiers); } } else if (e.MouseEvent.Button == MouseButtons.Right) { if (modifiers == Keys.None && SelectionContext != null && !SelectionContext.SelectionContains(hitObject)) { // The hit object is not already selected and a right-click landed on it. Select it. UpdateSelection(hitRecord, modifiers); } } } }
/// <summary> /// Updates the selection set, given the hit object and the modifier keys</summary> /// <param name="hitRecord">HitRecord</param> /// <param name="modifiers">Modifier keys</param> private void UpdateSelection(HitRecord hitRecord, Keys modifiers) { TimelinePath hitObject = hitRecord.HitPath; bool hitIsValidAnchor = true; switch (hitRecord.Type) { case HitType.GroupMove: SelectGroups(hitObject); break; case HitType.TrackMove: SelectTracks(hitObject); break; case HitType.Interval: case HitType.Key: case HitType.Marker: SelectEvents(hitObject); Owner.Constrain = (modifiers & Owner.ConstrainModifierKeys) != 0; break; default: Anchor = null; hitIsValidAnchor = false; break; } if (hitIsValidAnchor) { // If the Shift key is not held down or the current Anchor is null, or the user // has switched between track and group, or the user has picked an event, then // update the Anchor. IEvents are always additive with the shift key. if ((modifiers & Keys.Shift) == 0 || Anchor == null || (Anchor.Last is IGroup && hitObject.Last is ITrack) || (Anchor.Last is ITrack && hitObject.Last is IGroup) || (Anchor.Last is IEvent && hitObject.Last is IEvent)) { Anchor = hitObject; } } }
// get track that contains y value (in client window coordinates, aka, screen space) private TimelinePath GetTargetTrack( float y, TimelineLayout layout, IList <ITrack> tracks) { TimelinePath testPath = new TimelinePath((ITimelineObject)null); foreach (ITrack track in tracks) { testPath.Last = track; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= y && targetBounds.Bottom >= y) { return(testPath); } } return(null); }
// private because this may be moved to TimelineControl private TimelinePath GetOwningTrack(TimelinePath begin) { if (begin.Last is IKey) { TimelinePath trackPath = new TimelinePath(begin); trackPath.Last = ((IKey)begin.Last).Track; return(trackPath); } if (begin.Last is IInterval) { TimelinePath trackPath = new TimelinePath(begin); trackPath.Last = ((IInterval)begin.Last).Track; return(trackPath); } if (begin.Last is ITrack) { return(begin); } return(null); }
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) { PointF mouseLocation = e.MouseEvent.Location; float worldX = Sce.Atf.GdiUtil.InverseTransform(m_owner.Transform, mouseLocation.X); worldX += m_owner.GetSnapOffset(new[] { worldX }, s_snapOptions); float fraction; using (Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(hitPath)) { 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 range of groups from 'begin' to 'end', inclusive, as determined by the order /// that they are presented by the ITimelineDocument. The result always has 'begin' /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same, /// then the result has only the one ITimelineObject.</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths in the given range. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable <TimelinePath> GetRangeOfGroups(TimelinePath begin, TimelinePath end) { TimelinePath lastGroupToFind = null; List <TimelinePath> range = new List <TimelinePath>(); foreach (TimelinePath group in Owner.AllGroups) { if (lastGroupToFind == null) { if (group == begin) { lastGroupToFind = end; } else if (group == end) { lastGroupToFind = begin; } } if (lastGroupToFind != null) { range.Add(group); } if (group == lastGroupToFind) { break; } } if (lastGroupToFind == begin) { range.Reverse(); } return(range); }
/// <summary> /// Gets the range of tracks from 'begin' to 'end', inclusive, as determined by the order /// that they are presented by the ITimelineDocument. The result always has 'begin' /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same, /// then the result has only the one ITimelineObject.</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths in the given range. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable <TimelinePath> GetRangeOfTracks(TimelinePath begin, TimelinePath end) { TimelinePath lastTrackToFind = null; List <TimelinePath> range = new List <TimelinePath>(); foreach (TimelinePath track in Owner.AllTracks) { if (lastTrackToFind == null) { if (track == begin) { lastTrackToFind = end; } else if (track == end) { lastTrackToFind = begin; } } if (lastTrackToFind != null) { range.Add(track); } if (track == lastTrackToFind) { break; } } if (lastTrackToFind == begin) { range.Reverse(); } return(range); }
// get track that contains y value (in client window coordinates, aka, screen space) private TimelinePath GetTargetTrack( float y, TimelineLayout layout, IList<ITrack> tracks) { TimelinePath testPath = new TimelinePath((ITimelineObject)null); foreach (ITrack track in tracks) { testPath.Last = track; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= y && targetBounds.Bottom >= y) return testPath; } return null; }
/// <summary> /// Checks whether the given timeline object's attribute is editable for the current /// context and document</summary> /// <param name="item">Timeline object that changed</param> /// <param name="attribute">Attribute on the timeline object that changed</param> /// <returns>True iff this timeline object attribute is editable for the current /// ActiveControl, ActiveContext, and ActiveDocument properties</returns> public virtual bool IsEditable(ITimelineObject item, AttributeInfo attribute) { if (attribute == Schema.groupType.expandedAttribute) return true; TimelinePath path = new TimelinePath(item); return ActiveControl.IsEditable(path); }
/// <summary> /// Creates and caches resize information, for drawing ghosts and for performing /// actual resize operations</summary> /// <param name="layout">TimelineLayout</param> /// <param name="worldDrag">Drag offset</param> /// <param name="worldToView">World to view transformation matrix</param> /// <returns>Array of GhostInfo</returns> internal GhostInfo[] CreateGhostInfo(TimelineLayout layout, float worldDrag, Matrix worldToView) { // Get snap-from point in world coordinates. float[] movingPoints = new[] { worldDrag + m_originalBoundary }; float snapOffset = m_owner.GetSnapOffset(movingPoints, null); // adjust dragOffset to snap-to nearest event worldDrag += snapOffset; DragOffsetWithSnap = worldDrag; GhostInfo[] ghosts = new GhostInfo[m_selection.Count]; IEnumerator <TimelinePath> events = m_selection.GetEnumerator(); m_worldGhostMin = float.MaxValue; m_worldGhostMax = float.MinValue; for (int i = 0; i < ghosts.Length; i++) { events.MoveNext(); IEvent curr = (IEvent)events.Current.Last; RectangleF bounds = layout[events.Current]; float viewStart = bounds.Left; float viewEnd = viewStart + bounds.Width; float worldStart = curr.Start; float worldEnd = worldStart + curr.Length; Resize( worldDrag, worldToView, ref viewStart, ref viewEnd, ref worldStart, ref worldEnd); float worldLength = worldEnd - worldStart; bounds = new RectangleF(viewStart, bounds.Y, viewEnd - viewStart, bounds.Height); if (m_worldGhostMin > worldStart) { m_worldGhostMin = worldStart; } if (m_worldGhostMax < worldEnd) { m_worldGhostMax = worldEnd; } bool valid = true; IInterval interval = curr as IInterval; if (interval != null) { TimelinePath testPath = new TimelinePath(events.Current); foreach (IInterval other in interval.Track.Intervals) { // Skip this interval if it's part of the selection because we have to assume // that the track began in a valid state and that all of the selected objects // will still be valid relative to each other. We only need to check that the // scaled objects are valid relative to the stationary objects. testPath.Last = other; if (Mode == ScaleMode.TimePeriod && m_selection.Contains(testPath)) { continue; } else if (other == interval) { continue; } if (!m_owner.Constraints.IsIntervalValid(interval, ref worldStart, ref worldLength, other)) { valid = false; break; } } } ghosts[i] = new GhostInfo(curr, null, worldStart, worldLength, bounds, valid); } m_ghosts = ghosts; return(ghosts); }
// gets objects of appropriate type that are underneath the moving selected objects private TimelinePath[] GetMoveTargets(TimelineLayout layout) { // get groups and visible tracks List <IGroup> groups = new List <IGroup>(); List <ITrack> visibleTracks = new List <ITrack>(); foreach (IGroup group in m_owner.TimelineDocument.Timeline.Groups) { groups.Add(group); IList <ITrack> tracks = group.Tracks; bool expanded = group.Expanded; bool collapsible = tracks.Count > 1; bool collapsed = collapsible && !expanded; if (!collapsed) { foreach (ITrack track in tracks) { visibleTracks.Add(track); } } } TimelinePath[] targets = new TimelinePath[m_owner.Selection.SelectionCount]; RectangleF bounds; TimelinePath testPath = new TimelinePath((ITimelineObject)null); int i = -1; foreach (TimelinePath path in m_owner.Selection.Selection) { i++; IGroup group = path.Last as IGroup; if (group != null) { foreach (IGroup targetGroup in groups) { Point p = DragPoint; testPath.Last = targetGroup; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } continue; } ITrack track = path.Last as ITrack; if (track != null) { foreach (ITrack targetTrack in visibleTracks) { Point p = m_owner.DragPoint; testPath.Last = targetTrack; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } if (targets[i] == null) { foreach (IGroup targetGroup in groups) { Point p = DragPoint; testPath.Last = targetGroup; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } } continue; } IInterval interval = path.Last as IInterval; if (interval != null) { track = interval.Track; if (track != null) { testPath.Last = track; bounds = layout[testPath]; float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); continue; } else { float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); } } IKey key = path.Last as IKey; if (key != null) { track = key.Track; if (track != null) { testPath.Last = track; bounds = layout[testPath]; float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); continue; } else { //float y = m_owner.ClientToCanvas(m_owner.DragPoint).Y; float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); } } // ignore Markers, as they don't hit targets } return(targets); }
/// <summary> /// Gets the range of groups from 'begin' to 'end', inclusive, as determined by the order /// that they are presented by the ITimelineDocument. The result always has 'begin' /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same, /// then the result has only the one ITimelineObject.</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths in the given range. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable<TimelinePath> GetRangeOfGroups(TimelinePath begin, TimelinePath end) { TimelinePath lastGroupToFind = null; List<TimelinePath> range = new List<TimelinePath>(); foreach (TimelinePath group in Owner.AllGroups) { if (lastGroupToFind == null) { if (group == begin) lastGroupToFind = end; else if (group == end) lastGroupToFind = begin; } if (lastGroupToFind != null) range.Add(group); if (group == lastGroupToFind) break; } if (lastGroupToFind == begin) range.Reverse(); return range; }
/// <summary> /// Adds range of tracks to selection, from anchor to given target. /// Handles selecting a track, taking into account selecting ranges of tracks.</summary> /// <param name="target">Track as TimelinePath</param> protected virtual void SelectTracks(TimelinePath target) { // add range of tracks, from anchor to target? if ((Control.ModifierKeys & Keys.Shift) != Keys.None) { if (Anchor != null && Anchor.Last is ITrack && SelectionContext != null) { SelectionContext.SetRange(GetRangeOfTracks(Anchor, target)); return; } } // simply add this target track, using the current modifier keys to determine how Owner.Select<ITrack>(target); }
/// <summary> /// Draws the timeline to the display</summary> /// <param name="timeline">Timeline</param> /// <param name="selection">Selected timeline objects</param> /// <param name="activeGroup">Currently active group, or null</param> /// <param name="activeTrack">Currently active track, or null</param> /// <param name="transform">Transform taking timeline objects to display coordinates</param> /// <param name="clientRectangle">Bounds of displayed area of timeline, in screen space</param> /// <param name="marginBounds">Page coordinate bounding rectangle for printing</param> public void Print( ITimeline timeline, ISelectionContext selection, TimelinePath activeGroup, TimelinePath activeTrack, Matrix transform, RectangleF clientRectangle, Rectangle marginBounds) { m_marginBounds = marginBounds; try { m_printing = true; Draw(timeline, selection, activeGroup, activeTrack, transform, clientRectangle); } finally { m_printing = false; } }
// private because this may be moved to TimelineControl private bool Overlaps(TimelinePath path, float beginTime, float endTime) { IEvent e = (IEvent)path.Last; Matrix localToWorld = D2dTimelineControl.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); }
// documentTop is in timeline coordinates private void LayoutSubTimeline( TimelinePath path, ref float documentTop, Context c, TimelineLayout result) { SizeF pixelSize = c.PixelSize; float margin = pixelSize.Height * m_margin; // Limit Markers, etc., to being drawn within the owning timeline. RectangleF originalBounds = c.Bounds; c.Bounds.Y = documentTop; // Add a large bounding box for the whole TimelineReference row. float docRowHeight = Math.Max(margin * 2, MinimumTrackSize); RectangleF refBounds = new RectangleF(c.Bounds.X, documentTop, c.Bounds.Width, docRowHeight); documentTop += docRowHeight; // The whole timeline, with all groups and tracks laid out, and offset by the reference.Start. ITimelineReference reference = (ITimelineReference)path.Last; TimelineReferenceOptions options = reference.Options; IHierarchicalTimeline resolved = reference.Target; if (resolved != null) { Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path); c.PushTransform(localToWorld, MatrixOrder.Prepend); LayoutSubTimeline(path, resolved, ref documentTop, options.Expanded, c, result); c.PopTransform(); } // Now that we know the height of the tallest group, we can update the collapsed rectangle. if (!options.Expanded) { docRowHeight = Math.Max(docRowHeight, documentTop - refBounds.Y); refBounds.Height = docRowHeight; } refBounds = GdiUtil.Transform(c.Transform, refBounds); result.Add(path, refBounds); // Restore the bounds so that the horizontal scale (tick marks & numbers) is correct. c.Bounds = originalBounds; }
// private because this may be moved to TimelineControl private TimelinePath GetOwningTrack(TimelinePath begin) { if (begin.Last is IKey) { TimelinePath trackPath = new TimelinePath(begin); trackPath.Last = ((IKey)begin.Last).Track; return trackPath; } if (begin.Last is IInterval) { TimelinePath trackPath = new TimelinePath(begin); trackPath.Last = ((IInterval)begin.Last).Track; return trackPath; } if (begin.Last is ITrack) return begin; return null; }
/// <summary> /// Draws the timeline to the display</summary> /// <param name="timeline">Timeline</param> /// <param name="selection">Selected timeline objects</param> /// <param name="activeGroup">Currently active group, or null</param> /// <param name="activeTrack">Currently active track, or null</param> /// <param name="transform">Transform taking timeline objects to display coordinates</param> /// <param name="clientRectangle">Display coordinate bounding rectangle</param> /// <param name="g">Graphics object</param> /// <returns>Bounding rectangles for all timeline objects, organized in a dictionary of TimelinePath/RectangleF pairs</returns> public virtual TimelineLayout Draw( ITimeline timeline, ISelectionContext selection, TimelinePath activeGroup, TimelinePath activeTrack, Matrix transform, RectangleF clientRectangle, Graphics g) { if (m_printing) { transform.Translate(m_marginBounds.Left, m_marginBounds.Top, MatrixOrder.Append); g.SetClip(m_marginBounds); } Context c = new Context(this, transform, clientRectangle, g); TimelineLayout layout = Layout(timeline, c); c.ClearRecursionData(); // Clear the header column. g.FillRectangle(SystemBrushes.Control, 0, 0, m_headerWidth, c.ClientRectangle.Height); // Draw the main timeline and then any sub-timelines. DrawSubTimeline(null, timeline, false, true, selection, activeGroup, activeTrack, layout, c); foreach (TimelinePath path in TimelineControl.GetHierarchy(timeline)) DrawSubTimeline(path, selection, activeGroup, activeTrack, layout, c); // Draw the dark vertical line on the header that separates the groups and tracks. g.DrawLine(SystemPens.ControlDark, TrackIndent, m_timeScaleHeight, TrackIndent, c.ClientRectangle.Height); // Draw the dark vertical line on the right-side of the header, separating it from the canvas. g.DrawLine(SystemPens.ControlDark, m_headerWidth, m_timeScaleHeight, m_headerWidth, c.ClientRectangle.Height); // draw scales, etc. if (m_printing) c.Graphics.TranslateTransform(0, m_marginBounds.Top); DrawEventOverlay(c); // Draw the dark horizontal line underneath the scale. g.DrawLine(SystemPens.ControlDark, 0, m_timeScaleHeight, m_headerWidth, m_timeScaleHeight); // Give the Markers in the main timeline precedence over the scale and canvas RectangleF clipBounds = g.ClipBounds; clipBounds.X = m_headerWidth; DrawMarkers(null, timeline, selection, c, layout, clipBounds); return layout; }
// gets objects of appropriate type that are underneath the moving selected objects private TimelinePath[] GetMoveTargets(TimelineLayout layout) { // get groups and visible tracks List<IGroup> groups = new List<IGroup>(); List<ITrack> visibleTracks = new List<ITrack>(); foreach (IGroup group in m_owner.TimelineDocument.Timeline.Groups) { groups.Add(group); IList<ITrack> tracks = group.Tracks; bool expanded = group.Expanded; bool collapsible = tracks.Count > 1; bool collapsed = collapsible && !expanded; if (!collapsed) foreach (ITrack track in tracks) visibleTracks.Add(track); } TimelinePath[] targets = new TimelinePath[m_owner.Selection.SelectionCount]; RectangleF bounds; TimelinePath testPath = new TimelinePath((ITimelineObject)null); int i = -1; foreach(TimelinePath path in m_owner.Selection.Selection) { i++; IGroup group = path.Last as IGroup; if (group != null) { foreach (IGroup targetGroup in groups) { Point p = DragPoint; testPath.Last = targetGroup; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } continue; } ITrack track = path.Last as ITrack; if (track != null) { foreach (ITrack targetTrack in visibleTracks) { Point p = m_owner.DragPoint; testPath.Last = targetTrack; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } if (targets[i] == null) { foreach (IGroup targetGroup in groups) { Point p = DragPoint; testPath.Last = targetGroup; RectangleF targetBounds = layout[testPath]; if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y) { targets[i] = new TimelinePath(testPath); break; } } } continue; } IInterval interval = path.Last as IInterval; if (interval != null) { track = interval.Track; if (track != null) { testPath.Last = track; bounds = layout[testPath]; float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); continue; } else { float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); } } IKey key = path.Last as IKey; if (key != null) { track = key.Track; if (track != null) { testPath.Last = track; bounds = layout[testPath]; float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); continue; } else { //float y = m_owner.ClientToCanvas(m_owner.DragPoint).Y; float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y; targets[i] = GetTargetTrack(y, layout, visibleTracks); } } // ignore Markers, as they don't hit targets } return targets; }
/// <summary> /// Gets the range of tracks from 'begin' to 'end', inclusive, as determined by the order /// that they are presented by the ITimelineDocument. The result always has 'begin' /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same, /// then the result has only the one ITimelineObject.</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths in the given range. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable<TimelinePath> GetRangeOfTracks(TimelinePath begin, TimelinePath end) { TimelinePath lastTrackToFind = null; List<TimelinePath> range = new List<TimelinePath>(); foreach (TimelinePath track in Owner.AllTracks) { if (lastTrackToFind == null) { if (track == begin) lastTrackToFind = end; else if (track == end) lastTrackToFind = begin; } if (lastTrackToFind != null) range.Add(track); if (track == lastTrackToFind) break; } if (lastTrackToFind == begin) range.Reverse(); return range; }
/// <summary> /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths that intersect the rectangle. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable <TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end) { // If the two paths are the same, then the rectangle is just a point. // This check fixes a bug where the 'rectangle' becomes the whole interval // and then falsely selects an adjacent zero-length object due to Overlaps(). if (begin.Equals(end)) { return new TimelinePath[] { begin } } ; // Get the range of tracks between these two events. bool searchMarkers = false; System.Collections.IEnumerable rangeOfTracks; if (begin.Last is IMarker || end.Last is IMarker) { rangeOfTracks = Owner.AllTracks; searchMarkers = true; } else { TimelinePath beginTrack = GetOwningTrack(begin); TimelinePath endTrack = GetOwningTrack(end); rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack); } // Get the range of times to look for. float beginStart, beginEnd; D2dTimelineControl.CalculateRange(begin, out beginStart, out beginEnd); float endStart, endEnd; D2dTimelineControl.CalculateRange(end, out endStart, out endEnd); float beginTime = Math.Min(beginStart, endStart); float endTime = Math.Max(beginEnd, endEnd); // Look through all the IEvents of these tracks. List <TimelinePath> range = new List <TimelinePath>(); foreach (TimelinePath testPath in rangeOfTracks) { ITrack track = (ITrack)testPath.Last; foreach (IKey key in track.Keys) { testPath.Last = key; if (Overlaps(testPath, beginTime, endTime)) { range.Add(new TimelinePath(testPath)); } } foreach (IInterval interval in track.Intervals) { testPath.Last = interval; if (Overlaps(testPath, beginTime, endTime)) { range.Add(new TimelinePath(testPath)); } } } // Look for markers? if (searchMarkers) { foreach (TimelinePath testPath in Owner.AllMarkers) { if (Overlaps(testPath, beginTime, endTime)) { range.Add(testPath); } } } return(range); }
/// <summary> /// Draws the timeline to the display</summary> /// <param name="timeline">Timeline</param> /// <param name="selection">Selected timeline objects</param> /// <param name="activeGroup">Currently active group, or null</param> /// <param name="activeTrack">Currently active track, or null</param> /// <param name="transform">Transform taking timeline objects to display coordinates</param> /// <param name="clientRectangle">Bounds of displayed area of timeline, in screen space</param> /// <returns>Bounding rectangles for all timeline objects, organized in a dictionary of TimelinePath/RectangleF pairs</returns> public virtual TimelineLayout Draw( ITimeline timeline, ISelectionContext selection, TimelinePath activeGroup, TimelinePath activeTrack, Matrix transform, RectangleF clientRectangle) { Context c = new Context(this, transform, clientRectangle, m_graphics); TimelineLayout layout = Layout(timeline, c); c.ClearRecursionData(); try { if (m_printing) { transform.Translate(m_marginBounds.Left, m_marginBounds.Top, MatrixOrder.Append); m_graphics.PushAxisAlignedClip(m_marginBounds); } // Clear the header column. m_graphics.FillRectangle(new RectangleF(0, 0, HeaderWidth, c.ClientRectangle.Height), m_headerBrush); // Draw the main timeline and then any sub-timelines. DrawSubTimeline(null, timeline, false, true, selection, activeGroup, activeTrack, layout, c); foreach (TimelinePath path in D2dTimelineControl.GetHierarchy(timeline)) DrawSubTimeline(path, selection, activeGroup, activeTrack, layout, c); // Draw the dark vertical line on the header that separates the groups and tracks. m_graphics.DrawLine(new PointF(TrackIndent, m_timeScaleHeight), new PointF(TrackIndent, c.ClientRectangle.Height), m_headerLineBrush); // Draw the dark vertical line on the right-side of the header, separating it from the canvas. m_graphics.DrawLine(new PointF(HeaderWidth, m_timeScaleHeight), new PointF(HeaderWidth, c.ClientRectangle.Height), m_headerLineBrush); // draw scales, etc. if (m_printing) c.Graphics.TranslateTransform(0, m_marginBounds.Top); DrawEventOverlay(c); // Draw the dark horizontal line underneath the scale. m_graphics.DrawLine(new PointF(0, m_timeScaleHeight), new PointF(HeaderWidth, m_timeScaleHeight), m_scaleLineBrush); } finally { if (m_printing) m_graphics.PopAxisAlignedClip(); } // Give the Markers in the main timeline precedence over the scale and canvas RectangleF clipBounds = m_graphics.ClipBounds; clipBounds.X = HeaderWidth; //var clipBounds = new RectangleF(HeaderWidth, 0, m_graphics.Size.Width, m_graphics.Size.Height); DrawMarkers(null, timeline, selection, c, layout, clipBounds); return layout; }
private void DrawSubTimeline( TimelinePath path, ISelectionContext selection, TimelinePath activeGroup, TimelinePath activeTrack, TimelineLayout layout, Context c) { // Include the reference's offset into the Transform and InverseTransform properties. Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path); c.PushTransform(localToWorld, MatrixOrder.Prepend); // draw the row that has this timeline reference's name ITimelineReference reference = (ITimelineReference)path.Last; RectangleF clipBounds = m_graphics.ClipBounds; RectangleF bounds = layout.GetBounds(path); IHierarchicalTimeline timeline = reference.Target; if (bounds.IntersectsWith(clipBounds)) { DrawMode drawMode = DrawMode.Normal; if (selection.SelectionContains(path)) drawMode |= DrawMode.Selected; DrawTimelineReference(reference, bounds, drawMode, c); } // draw the timeline document as if it were the main document if (timeline != null) DrawSubTimeline(path, timeline, true, reference.Options.Expanded, selection, activeGroup, activeTrack, layout, c); c.PopTransform(); }
private void DrawSubTimeline( TimelinePath path, ITimeline timeline, bool subTimeline, bool expandedTimeline, ISelectionContext selection, TimelinePath activeGroup, TimelinePath activeTrack, TimelineLayout layout, Context c) { //if (c.TestRecursion(timeline)) // return; if (!subTimeline) m_offsetX = c.Transform.OffsetX; RectangleF clipBounds = m_graphics.ClipBounds; DrawGroupsAndTracks(path, timeline, expandedTimeline, selection, c, layout, clipBounds); // draw markers over keys, intervals, tracks, and group if (subTimeline) { // Give the Markers in the main timeline precedence; draw on top of everything. clipBounds.X = HeaderWidth; clipBounds.Width -= HeaderWidth; DrawMarkers(path, timeline, selection, c, layout, clipBounds); } // Draw the group and track handles only if the owning timeline is expanded. if (expandedTimeline) { if (m_printing) c.Graphics.TranslateTransform(m_marginBounds.Left, 0); RectangleF bounds; foreach (IGroup group in timeline.Groups) { IList<ITrack> tracks = group.Tracks; TimelinePath groupPath = path + group; bounds = layout.GetBounds(groupPath); bounds = GetGroupHandleRect(bounds, !group.Expanded); RectangleF groupLabelBounds = new RectangleF(bounds.X, bounds.Y, HeaderWidth, bounds.Height); // Draw group's move handle. DrawMoveHandle(bounds, selection.SelectionContains(groupPath), groupPath == activeGroup); // Draw expander? if (tracks.Count > 1) { RectangleF expanderRect = GetExpanderRect(bounds); m_graphics.DrawExpander( expanderRect.X, expanderRect.Y, expanderRect.Width, m_expanderBrush, group.Expanded); groupLabelBounds.X += TrackIndent; groupLabelBounds.Width -= TrackIndent; } // Draw tracks' move handles? if (group.Expanded || tracks.Count == 1) { foreach (ITrack track in tracks) { TimelinePath trackPath = path + track; bounds = layout.GetBounds(trackPath); bounds = GetTrackHandleRect(bounds); DrawMoveHandle(bounds, selection.SelectionContains(trackPath), trackPath == activeTrack); if (bounds.Width > 0f) m_graphics.DrawText(track.Name, m_trackTextFormat, bounds, m_nameBrush); } } // Draw group name. if (groupLabelBounds.Width > 0) m_graphics.DrawText(group.Name, c.TextFormat, groupLabelBounds, m_nameBrush); } if (m_printing) c.Graphics.TranslateTransform(-m_marginBounds.Left, 0); } return; }
/// <summary> /// Adds range of groups to selection, from anchor to given target. /// Handles selecting a group, taking into account selecting ranges of groups. /// If no anchor, just selects target.</summary> /// <param name="target">Timeline path</param> protected virtual void SelectGroups(TimelinePath target) { // Add range of groups, from anchor to target? Holding down Ctrl or Alt simultaneously // still does a range selection in Visual Studio's Solution Explorer. if ((Control.ModifierKeys & Keys.Shift) != Keys.None) { if (Anchor != null && Anchor.Last is IGroup && SelectionContext != null) { SelectionContext.SetRange(GetRangeOfGroups(Anchor, target)); return; } } // simply add this target group, using the current modifier keys to determine how Owner.Select<IGroup>(target); }
private void DrawMarkers(TimelinePath path, ITimeline timeline, ISelectionContext selection, Context c, TimelineLayout layout, RectangleF clipBounds) { RectangleF bounds; foreach (IMarker marker in timeline.Markers) { TimelinePath markerPath = path + marker; if (!layout.TryGetBounds(markerPath, out bounds)) continue; if (bounds.IntersectsWith(clipBounds)) { DrawMode drawMode = DrawMode.Normal; if (selection.SelectionContains(markerPath)) drawMode |= DrawMode.Selected; Draw(marker, bounds, drawMode, c); } } }
/// <summary> /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths that intersect the rectangle. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable<TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end) { // If the two paths are the same, then the rectangle is just a point. // This check fixes a bug where the 'rectangle' becomes the whole interval // and then falsely selects an adjacent zero-length object due to Overlaps(). if (begin.Equals(end)) return new TimelinePath[] { begin }; // Get the range of tracks between these two events. bool searchMarkers = false; System.Collections.IEnumerable rangeOfTracks; if (begin.Last is IMarker || end.Last is IMarker) { rangeOfTracks = Owner.AllTracks; searchMarkers = true; } else { TimelinePath beginTrack = GetOwningTrack(begin); TimelinePath endTrack = GetOwningTrack(end); rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack); } // Get the range of times to look for. float beginStart, beginEnd; D2dTimelineControl.CalculateRange(begin, out beginStart, out beginEnd); float endStart, endEnd; D2dTimelineControl.CalculateRange(end, out endStart, out endEnd); float beginTime = Math.Min(beginStart, endStart); float endTime = Math.Max(beginEnd, endEnd); // Look through all the IEvents of these tracks. List<TimelinePath> range = new List<TimelinePath>(); foreach (TimelinePath testPath in rangeOfTracks) { ITrack track = (ITrack)testPath.Last; foreach (IKey key in track.Keys) { testPath.Last = key; if (Overlaps(testPath, beginTime, endTime)) range.Add(new TimelinePath(testPath)); } foreach (IInterval interval in track.Intervals) { testPath.Last = interval; if (Overlaps(testPath, beginTime, endTime)) range.Add(new TimelinePath(testPath)); } } // Look for markers? if (searchMarkers) { foreach (TimelinePath testPath in Owner.AllMarkers) { if (Overlaps(testPath, beginTime, endTime)) range.Add(testPath); } } return range; }
private void DrawGroupsAndTracks(TimelinePath path, ITimeline timeline, bool expandedTimeline, ISelectionContext selection, Context c, TimelineLayout layout, RectangleF clipBounds) { RectangleF canvasBounds = clipBounds; //clipBounds minus the left-side header canvasBounds.X = HeaderWidth; canvasBounds.Width -= HeaderWidth; RectangleF bounds; foreach (IGroup group in timeline.Groups) { TimelinePath groupPath = path + group; if (!layout.TryGetBounds(groupPath, out bounds)) continue; if (bounds.IntersectsWith(clipBounds)) { DrawMode drawMode = DrawMode.Normal; if (selection.SelectionContains(groupPath)) drawMode |= DrawMode.Selected; if (expandedTimeline) Draw(group, bounds, drawMode, c); IList<ITrack> tracks = group.Tracks; bool collapsed = !expandedTimeline || (!group.Expanded && tracks.Count > 1); foreach (ITrack track in tracks) { TimelinePath trackPath = path + track; bounds = layout.GetBounds(trackPath); if (bounds.IntersectsWith(clipBounds)) { drawMode = DrawMode.Normal; if (selection.SelectionContains(trackPath)) drawMode |= DrawMode.Selected; if (collapsed) drawMode = DrawMode.Collapsed; Draw(track, bounds, drawMode, c); foreach (IInterval interval in track.Intervals) { TimelinePath intervalPath = path + interval; bounds = layout.GetBounds(intervalPath); if (bounds.IntersectsWith(canvasBounds)) { drawMode = DrawMode.Normal; if (selection.SelectionContains(intervalPath)) drawMode |= DrawMode.Selected; if (collapsed) drawMode = DrawMode.Collapsed; Draw(interval, bounds, drawMode, c); } } foreach (IKey key in track.Keys) { TimelinePath keyPath = path + key; bounds = layout.GetBounds(keyPath); if (bounds.IntersectsWith(canvasBounds)) { drawMode = DrawMode.Normal; if (selection.SelectionContains(keyPath)) drawMode |= DrawMode.Selected; if (collapsed) drawMode = DrawMode.Collapsed; Draw(key, bounds, drawMode, c); } } } } } } }
// 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 = 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 = D2dTimelineControl.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 D2dTimelineControl.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); // Pretend that drag-drop objects are in the TimelineLayout object if (m_owner.DragDropObjects != null) { foreach (ITimelineObject dragDrop in m_owner.DragDropObjects) { layout[dragDrop] = GetDragDropBounds(dragDrop); } } 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; valid &= m_owner.Constraints.IsStartValid(interval, ref start); valid &= m_owner.Constraints.IsLengthValid(interval, ref length); if (valid) { ITrack intervalTrack = interval.Track; if (intervalTrack != null) { yOffset = layout[target].Y - layout[interval.Track].Y; } else { yOffset = layout[target].Y - bounds.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; valid &= m_owner.Constraints.IsStartValid(key, ref start); if (valid) { ITrack keyTrack = key.Track; if (keyTrack != null) { yOffset = layout[targetTrack].Y - layout[key.Track].Y; } else { yOffset = layout[targetTrack].Y - bounds.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 = (DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack(); } } else if (group != null) { xOffset = 0; if (target == null) { IList <IGroup> groups = m_owner.TimelineDocument.Timeline.Groups; target = (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); }
private void PickSubTimeline( TimelinePath path, RectangleF pickRect, Context c, TimelineLayout layout, List<HitRecord> result) { RectangleF clipBounds = c.Graphics.ClipBounds; ITimelineReference reference = (ITimelineReference)path.Last; RectangleF bounds; if (!layout.TryGetBounds(path, out bounds)) return; IHierarchicalTimeline timeline = reference.Target; if (timeline != null && reference.Options.Expanded) PickSubTimeline(path, timeline, pickRect, c, layout, result); if (timeline != null && bounds.IntersectsWith(clipBounds)) { IList<IGroup> groups = timeline.Groups; bool collapsible = groups.Count > 0; bool expanded = reference.Options.Expanded; bool collapsed = collapsible && !expanded; RectangleF handleRect = GetGroupHandleRect(bounds, collapsed); RectangleF expanderRect = GetExpanderRect(handleRect); if (collapsible && expanderRect.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.GroupExpand, path)); } else if (bounds.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.Key, path)); } } else if (bounds.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.Key, path)); } }
/// <summary> /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary> /// <param name="begin">Beginning timeline path</param> /// <param name="end">Ending timeline path</param> /// <returns>Enumeration of timeline paths that intersect the rectangle. /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns> protected virtual IEnumerable<TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end) { // Get the range of tracks between these two events. bool searchMarkers = false; System.Collections.IEnumerable rangeOfTracks; if (begin.Last is IMarker || end.Last is IMarker) { rangeOfTracks = Owner.AllTracks; searchMarkers = true; } else { TimelinePath beginTrack = GetOwningTrack(begin); TimelinePath endTrack = GetOwningTrack(end); rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack); } // Get the range of times to look for. float beginStart, beginEnd; TimelineControl.CalculateRange(begin, out beginStart, out beginEnd); float endStart, endEnd; TimelineControl.CalculateRange(end, out endStart, out endEnd); float beginTime = Math.Min(beginStart, endStart); float endTime = Math.Max(beginEnd, endEnd); // Look through all the IEvents of these tracks. List<TimelinePath> range = new List<TimelinePath>(); foreach (TimelinePath testPath in rangeOfTracks) { ITrack track = (ITrack)testPath.Last; foreach (IKey key in track.Keys) { testPath.Last = key; if (Overlaps(testPath, beginTime, endTime)) range.Add(new TimelinePath(testPath)); } foreach (IInterval interval in track.Intervals) { testPath.Last = interval; if (Overlaps(testPath, beginTime, endTime)) range.Add(new TimelinePath(testPath)); } } // Look for markers? if (searchMarkers) { foreach (TimelinePath testPath in Owner.AllMarkers) { if (Overlaps(testPath, beginTime, endTime)) range.Add(testPath); } } return range; }
private void PickSubTimeline( TimelinePath root, ITimeline timeline, RectangleF pickRect, Context c, TimelineLayout layout, List<HitRecord> result) { if (timeline == null) return; RectangleF clipBounds = c.Graphics.ClipBounds; RectangleF clientRectangle = c.ClientRectangle; RectangleF bounds; HitType hitType; foreach (IMarker marker in timeline.Markers) { if (!layout.TryGetBounds(root + marker, out bounds)) continue; if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth) { hitType = Pick(marker, bounds, pickRect, c); if (hitType != HitType.None) result.Add(new HitRecord(hitType, root + marker)); } } // If the pick is on the timescale, then let's stop here. if (pickRect.Left > HeaderWidth && pickRect.Right < clientRectangle.Width && pickRect.Bottom > 0 && pickRect.Bottom < TimeScaleHeight) { if ((result.Count == 0) && (pickRect.Height <= 2 * PickTolerance) && (pickRect.Width <= 2 * PickTolerance)) result.Add(new HitRecord(HitType.TimeScale, null)); return; } IList<IGroup> groups = timeline.Groups; for (int i = groups.Count - 1; i >= 0; i--) { IGroup group = groups[i]; if (!layout.ContainsPath(root + group)) continue; IList<ITrack> tracks = group.Tracks; bool expanded = group.Expanded; bool collapsible = tracks.Count > 1; bool collapsed = collapsible && !expanded; if (!collapsed) { for (int j = tracks.Count - 1; j >= 0; j--) { ITrack track = tracks[j]; IList<IKey> keys = track.Keys; for (int k = keys.Count - 1; k >= 0; k--) { IKey key = keys[k]; if (!layout.TryGetBounds(root + key, out bounds)) continue; if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth) { hitType = Pick(key, bounds, pickRect, c); if (hitType != HitType.None) result.Add(new HitRecord(hitType, root + key)); } } IList<IInterval> intervals = track.Intervals; for (int k = intervals.Count - 1; k >= 0; k--) { IInterval interval = intervals[k]; if (!layout.TryGetBounds(root + interval, out bounds)) continue; if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth) { hitType = Pick(interval, bounds, pickRect, c); if (hitType != HitType.None) result.Add(new HitRecord(hitType, root + interval)); } } if (!layout.TryGetBounds(root + track, out bounds)) continue; if (bounds.IntersectsWith(clipBounds)) { RectangleF handleRect = GetTrackHandleRect(bounds); if (handleRect.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.TrackMove, root + track)); } else if (bounds.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.Track, root + track)); } } } } if (!layout.TryGetBounds(root + group, out bounds)) continue; if (bounds.IntersectsWith(clipBounds)) { RectangleF handleRect = GetGroupHandleRect(bounds, collapsed); RectangleF expanderRect = GetExpanderRect(handleRect); if (collapsible && expanderRect.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.GroupExpand, root + group)); } else if (handleRect.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.GroupMove, root + group)); } else if (bounds.IntersectsWith(pickRect)) { result.Add(new HitRecord(HitType.Group, root + group)); } } } if (pickRect.Left < HeaderWidth && HeaderWidth < pickRect.Right) { result.Add(new HitRecord(HitType.HeaderResize, null)); } }
// documentTop is in timeline coordinates private void LayoutSubTimeline( TimelinePath path, ITimeline timeline, ref float documentTop, bool expandedTimeline, Context c, TimelineLayout result) { //if (c.TestRecursion(timeline)) // return; RectangleF bounds; SizeF pixelSize = c.PixelSize; float margin = pixelSize.Height * m_margin; float groupTop = documentTop; float documentBottom = groupTop; foreach (IGroup group in timeline.Groups) { bool expanded = expandedTimeline && group.Expanded; float groupBottom = groupTop; float trackTop = groupTop; foreach (ITrack track in group.Tracks) { float eventTop = trackTop + margin; float trackBottom = eventTop; foreach (IInterval interval in track.Intervals) { bounds = GetBounds(interval, eventTop, c); trackBottom = Math.Max(trackBottom, bounds.Bottom); bounds = GdiUtil.Transform(c.Transform, bounds); // add it, even if 'expandedTimeline' is false, to get the shadow effect result.Add(path + interval, bounds); } foreach (IKey key in track.Keys) { bounds = GetBounds(key, eventTop, c); trackBottom = Math.Max(trackBottom, bounds.Bottom); bounds = GdiUtil.Transform(c.Transform, bounds); // add it, even if 'expandedTimeline' is false, to get the shadow effect result.Add(path + key, bounds); } trackBottom += margin; trackBottom = Math.Max(trackBottom, trackTop + MinimumTrackSize); // need height for track, even if it's empty bounds = new RectangleF(c.Bounds.X, trackTop, c.Bounds.Width, trackBottom - trackTop); bounds = GdiUtil.Transform(c.Transform, bounds); bounds.X = c.ClientRectangle.X; // add it, even if 'expandedTimeline' is false, to get the shadow effect result.Add(path + track, bounds); if (expanded) trackTop = trackBottom; groupBottom = Math.Max(groupBottom, trackBottom); } // need height for group, even if it's empty groupBottom = Math.Max(groupBottom, groupTop + Math.Max(margin*2, MinimumTrackSize)); float groupHeight = groupBottom - groupTop; bounds = new RectangleF(0, groupTop, c.Bounds.Width, groupHeight); bounds = GdiUtil.Transform(c.Transform, bounds); bounds.X = c.ClientRectangle.X; // add it, even if 'expandedTimeline' is false, to get the shadow effect result.Add(path + group, bounds); if (expandedTimeline) groupTop = groupBottom; documentBottom = Math.Max(documentBottom, groupBottom); } if (expandedTimeline) { // Draw Markers, but limit them to be within the owning timeline. RectangleF originalBounds = c.Bounds; c.Bounds.Height = documentBottom - c.Bounds.Y; foreach (IMarker marker in timeline.Markers) { bounds = GetBounds(marker, c); bounds = GdiUtil.Transform(c.Transform, bounds); result.Add(path + marker, bounds); } c.Bounds = originalBounds; } documentTop = documentBottom; }
// 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 = 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 = D2dTimelineControl.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 D2dTimelineControl.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); // Pretend that drag-drop objects are in the TimelineLayout object if (m_owner.DragDropObjects != null) { foreach (ITimelineObject dragDrop in m_owner.DragDropObjects) layout[dragDrop] = GetDragDropBounds(dragDrop); } 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; valid &= m_owner.Constraints.IsStartValid(interval, ref start); valid &= m_owner.Constraints.IsLengthValid(interval, ref length); if (valid) { ITrack intervalTrack = interval.Track; if (intervalTrack != null) yOffset = layout[target].Y - layout[interval.Track].Y; else yOffset = layout[target].Y - bounds.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; valid &= m_owner.Constraints.IsStartValid(key, ref start); if (valid) { ITrack keyTrack = key.Track; if (keyTrack != null) yOffset = layout[targetTrack].Y - layout[key.Track].Y; else yOffset = layout[targetTrack].Y - bounds.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 = (DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack(); } } else if (group != null) { xOffset = 0; if (target == null) { IList<IGroup> groups = m_owner.TimelineDocument.Timeline.Groups; target = (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; }