private void picSlider_Paint(object sender, PaintEventArgs e) { e.Graphics.Clear(BackColor); // Any messages in case of any errors string errorMessage = null; if (Animation == null) { errorMessage = "No animation! Create or select animation to start editing."; } else if (Animation.DirectXAnimation.KeyFrames.Count == 0) { errorMessage = "No frames! Add some frames to start editing."; } if (!string.IsNullOrEmpty(errorMessage)) { using (var b = new SolidBrush(Colors.DisabledText)) e.Graphics.DrawString(errorMessage, Font, b, ClientRectangle, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center }); return; } e.Graphics.SmoothingMode = SmoothingMode.HighQuality; // Draw state change ranges foreach (var sch in Animation.WadAnimation.StateChanges) { foreach (var disp in sch.Dispatches) { int realOutFrame = disp.OutFrame >= realFrameCount ? realFrameCount - 1 : disp.OutFrame; e.Graphics.FillRectangle(_stateChangeBrush, new RectangleF(picSlider.Padding.Left + (disp.InFrame * frameStep), picSlider.Padding.Top, (realOutFrame - disp.InFrame) * frameStep, picSlider.ClientSize.Height / _stateChangeMarkerThicknessDivider - picSlider.Padding.Bottom - 2)); } } int halfCursorWidth = (int)Math.Round(_cursorWidth / 2.0f); // Shift the cursor at start/stop positions to prevent clipping int addShift = -halfCursorWidth; if (Value == 0) { addShift += halfCursorWidth; } else if (Value == Maximum) { addShift += -halfCursorWidth; } // Draw selection and highlight in 2 passes for (int passes = 0; passes < 2; passes++) { int x = passes == 0 ? Selection.X : _highlightStart; int y = passes == 0 ? Selection.Y : _highlightEnd; int realX = ValueToX(x); int realY = ValueToX(y); int size = realY - realX; if ((passes == 0 && !SelectionIsEmpty) || (passes == 1 && _highlightTimer.Enabled)) { int width = size == 0 ? _cursorWidth : size + _cursorWidth; Rectangle rect = new Rectangle(realX + picSlider.Padding.Left - halfCursorWidth, picSlider.Padding.Top, width, picSlider.ClientSize.Height - picSlider.Padding.Bottom);; if (size == 0) { if (y != Minimum && y == Maximum) { rect.X -= halfCursorWidth; } else if (x == Minimum) { rect.X += halfCursorWidth; } } else { if (x == Minimum) { rect.X += halfCursorWidth; rect.Width -= halfCursorWidth; } if (y == Maximum) { rect.Width -= halfCursorWidth; } } if (passes == 0) { e.Graphics.FillRectangle(_selectionBrush, rect); e.Graphics.DrawRectangle(_selectionPen, rect); } else { using (SolidBrush currBrush = (SolidBrush)_highlightBrush.Clone()) { currBrush.Color = Color.FromArgb((int)((float)currBrush.Color.A * _highlightCounter), currBrush.Color); e.Graphics.FillRectangle(currBrush, rect); } } } } // Measure maximum label size SizeF maxLabelSize = TextRenderer.MeasureText(realFrameCount.ToString(), Font, new Size(picSlider.Width - picSlider.Padding.Horizontal, picSlider.Height - picSlider.Padding.Vertical), TextFormatFlags.WordBreak); // Precache animcommands so we don't iterate them every drawn frame var acList = new List <KeyValuePair <int, WadAnimCommand> >(); foreach (var ac in Animation.WadAnimation.AnimCommands.Where(ac => ac.FrameBased)) { acList.Add(new KeyValuePair <int, WadAnimCommand>(ac.Parameter1, ac)); } // Precache some variables for speeding up renderer with ultra-long animations (5000+ frames) var step = frameStep; var padding = picSlider.Padding; var drawStepWidth = _keyFrameBorderPen.Width * 4; // Draw frame-specific animcommands, numericals and dividers for (int passes = 0; passes < 2; passes++) { for (int i = 0; i < realFrameCount; ++i) { int currX = (int)MathC.Round(step * i) + padding.Left; bool isKeyFrame = (i % (Animation.WadAnimation.FrameRate == 0 ? 1 : Animation.WadAnimation.FrameRate) == 0); bool first = i == 0; bool last = i >= realFrameCount - 1; if (passes == 0) { int count = 0; // Draw animcommands if (acList.Count > 0) { foreach (var acPair in acList) { var ac = acPair.Value; Rectangle currRect = new Rectangle(currX - _animCommandMarkerRadius / 2, padding.Top - _animCommandMarkerRadius / 2 + (_animCommandMarkerRadius / 3 * count), _animCommandMarkerRadius, _animCommandMarkerRadius); float startAngle = !first ? (!last ? 0 : 90) : 0; float endAngle = !first ? (!last ? 180 : 90) : 90; if (ac.Parameter1 == i) { using (SolidBrush currBrush = (SolidBrush)(ac.Type == WadAnimCommandType.PlaySound ? _animCommandSoundBrush : _animCommandFlipeffectBrush).Clone()) { currBrush.Color = Color.FromArgb((int)((float)currBrush.Color.A / (1.0f + ((float)count / 3.0f))), currBrush.Color); e.Graphics.FillPie(currBrush, currRect, startAngle, endAngle); count++; } } } acList.RemoveAll(ac => ac.Key == i); // Remove already drawn animcommands from list } // Determine if current line should be drawn. bool drawCurrentLine = true; if (step < drawStepWidth) { int period = (int)MathC.Round(drawStepWidth / step); if (i % period != 0 && i != realFrameCount - 1) { drawCurrentLine = false; } } if (drawCurrentLine) { // Draw frame lines e.Graphics.SmoothingMode = SmoothingMode.Default; var lineHeight = picSlider.Height / (isKeyFrame ? 2 : 3); if (isKeyFrame) { e.Graphics.DrawLine(_keyFrameBorderPen, currX, padding.Top, currX, lineHeight); // Draw keyframe } else { e.Graphics.DrawLine(_frameBorderPen, currX, padding.Top, currX, lineHeight); // Draw ordinary frame } e.Graphics.SmoothingMode = SmoothingMode.HighQuality; } } // Draw cursor on 2nd pass's first occurence (only for real animations, not for single-frame ones) if (i == 0 && passes == 1 && realFrameCount > 1) { e.Graphics.FillRectangle(_cursorBrush, new RectangleF(ValueToX(Value) + addShift + padding.Left, padding.Top, _cursorWidth, picSlider.ClientSize.Height - padding.Bottom - 2)); } // Draw labels bool drawCurrentLabel = true; if ((passes == 0 && !isKeyFrame) || (passes != 0 && isKeyFrame)) { // Determine if labels are overlapping and decide on drawing if (step < maxLabelSize.Width * 1.25) { int period = (int)MathC.Round(maxLabelSize.Width * 1.25 / step); if (i % period != 0) { drawCurrentLabel = false; } } if (drawCurrentLabel) { // Align first and last numerical entries so they are not concealed by control border StringAlignment align = StringAlignment.Center; int shift = 0; if (first) { shift -= padding.Left; align = StringAlignment.Near; } else if (last) { shift += padding.Left; align = StringAlignment.Far; } // Finally draw it after all these tests e.Graphics.DrawString(i.ToString(), Font, (isKeyFrame ? _lblKeyframeBrush : _lblFrameBrush), currX + shift, picSlider.Height, new StringFormat { Alignment = align, LineAlignment = StringAlignment.Far }); } } } } // Draw horizontal guide (only for real anims, for single-frame anims we wouldn't wanna show that if (realFrameCount > 1) { e.Graphics.SmoothingMode = SmoothingMode.Default; e.Graphics.DrawLine(_keyFrameBorderPen, padding.Left, padding.Top + 1, picSlider.ClientSize.Width - padding.Left, padding.Top + 1); } }