public override void Draw(DrawingContext drawingContext, ulong firstVisibleTick, ulong lastVisibleTick) { Rect r = new Rect(0, 0, this.ActualWidth, this.ActualHeight); drawingContext.DrawRectangle(this.IsKeyboardFocused ? this.FocusedBackground : this.Background, null, r); if (this.DataSource == null) { return; } UpdateBitmap(); drawingContext.PushClip(new RectangleGeometry(r)); List<LabeledRange> labeledRanges = new List<LabeledRange>(); FormattedText ftLabel = null; const double textMargin = 3; if (this.LabelFontSize > 0) { // Calculate label rect requirements foreach (var selectedNode in this.selectedNodes) { // We're +1 because there is some kind of sub-pixel misalignment between the event lane itself and the bitmap. var middle = TimeAxis.TimeToScreen(selectedNode.StartTime + (selectedNode.Duration / 2)) + 1; var x1 = TimeAxis.TimeToScreen(selectedNode.StartTime) + 1; var x2 = TimeAxis.TimeToScreen(selectedNode.StartTime + selectedNode.Duration) + 1; if (x1 <= this.PixelWidth && x2 > 0) { if (ftLabel == null) { // NOTE: It is assumed that all selected nodes have the same name... ftLabel = new FormattedText(selectedNode.Name, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, this.labelTypeface, this.LabelFontSize, this.IsKeyboardFocused ? this.FocusedSelectionForeground : this.SelectionForeground); } // Try to center the label under the node. But adjust its position if it // is clipped by the left or right edge of the timeline. var left = middle - ftLabel.Width / 2; if (left < textMargin) { left = textMargin; } else if (left + ftLabel.Width > this.ActualWidth - textMargin) { left = this.ActualWidth - textMargin - ftLabel.Width; } // Create a labeled range for this label var range = new LabeledRange(); range.Start = x1; range.End = x2; range.Rect = new Rect(left, this.palette.BitmapPixelHeight, ftLabel.Width, this.SelectionFanHeight + this.labelTextHeight + (this.LabelMargin * 2)); range.Rect.Inflate(textMargin, 0); range.ContainsPrimaryNode = (selectedNode == this.primarySelectedNode); labeledRanges.Add(range); } } } if (ftLabel != null) { List<LabeledRange> mergedRanges = new List<LabeledRange>(labeledRanges.Count); // Merge the overlapping label rects together while (labeledRanges.Count > 0) { LabeledRange range = labeledRanges[0]; labeledRanges.RemoveAt(0); for (int i = 0; i < labeledRanges.Count; ) { if (range.Rect.IntersectsWith(labeledRanges[i].Rect)) { range.Union(labeledRanges[i]); labeledRanges.RemoveAt(i); } else { i++; } } double centerX = range.Rect.Left + (range.Rect.Width / 2) - (ftLabel.Width / 2); range.Rect = new Rect(centerX, range.Rect.Top, ftLabel.Width, range.Rect.Height); range.Rect.Inflate(textMargin, 0); mergedRanges.Add(range); } double textRectTop = this.DesiredSize.Height - (ftLabel.Height + this.LabelMargin + this.BottomMargin); double textRectBottom = this.DesiredSize.Height - this.BottomMargin; // Draw the remaining labeled ranges foreach (var range in mergedRanges) { double centerX = range.Rect.Left + (range.Rect.Width / 2) - (ftLabel.Width / 2); var geometry = new StreamGeometry() { FillRule = FillRule.EvenOdd }; using (StreamGeometryContext ctx = geometry.Open()) { ctx.BeginFigure(new Point(range.End, range.Rect.Top), isFilled: true, isClosed: true); ctx.LineTo(new Point(range.Rect.Right, textRectTop), true, true); ctx.LineTo(new Point(range.Rect.Right, textRectBottom), true, true); ctx.LineTo(new Point(range.Rect.Left, textRectBottom), true, true); ctx.LineTo(new Point(range.Rect.Left, textRectTop), true, true); if (range.SubRanges == null) { ctx.LineTo(new Point(range.Start, range.Rect.Top), true, true); } else { double inc = range.Rect.Width / range.SubRanges.Count; double nextValley = range.Rect.Left + inc; for (int i = 0; i < range.SubRanges.Count; i++) { var subrange = range.SubRanges[i]; ctx.LineTo(new Point(subrange.Item1, range.Rect.Top), true, true); if (i < range.SubRanges.Count - 1) { ctx.LineTo(new Point(subrange.Item2, range.Rect.Top), false, true); ctx.LineTo(new Point(nextValley, textRectTop), true, true); nextValley += inc; } } } ctx.LineTo(new Point(range.End, range.Rect.Top), false, true); } var brush = this.Background; var foreground = this.Foreground; if (this.IsKeyboardFocused) { if (range.ContainsPrimaryNode) { brush = this.FocusedSelectionBackground; foreground = this.FocusedSelectionForeground; } else { brush = this.FocusedBackground; foreground = this.FocusedForeground; } } drawingContext.DrawGeometry(brush, this.standardPen, geometry); ftLabel.SetForegroundBrush(foreground); drawingContext.DrawText(ftLabel, new Point(centerX, textRectTop)); } } drawingContext.Pop(); }
public void Union(LabeledRange range) { if (this.SubRanges == null) { this.SubRanges = new List<Tuple<double, double>>(); this.SubRanges.Add(new Tuple<double, double>(this.Start, this.End)); } this.Start = Math.Min(this.Start, range.Start); this.End = Math.Max(this.End, range.End); this.Rect.Union(range.Rect); if (range.SubRanges == null) { this.SubRanges.Add(new Tuple<double, double>(range.Start, range.End)); } else { this.SubRanges.AddRange(range.SubRanges); } this.ContainsPrimaryNode |= range.ContainsPrimaryNode; }