protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); e.Graphics.Clear(Color.Black); try { var renderRequest = new RenderRequest { Graphics = e.Graphics, CursorPoint = PointToClient(Cursor.Position), IsHitTest = false }; Render(renderRequest); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
public void Render(RenderRequest request) { var g = request.Graphics; // Draw the image, with stretching. // Might be very ugly, but the thread will improve its resolution step by step. var viewPortStart = Normalize(GetCurrentViewPortStart()); var viewPortEnd = Normalize(GetCurrentViewPortEnd()); if (NoteRequest != null) { var viewPortInterval = new Interval(viewPortStart, viewPortEnd); var args = new AudioPaintEventArgs(viewPortInterval); NoteRequest(this, args); request.Notes = args.Notes; } if (request.IsRendering) { for (var i = 0; i < _work.Count; i++) { var work = _work[i]; if (work.Bitmap == null) { continue; } if (work.To < viewPortStart) { // We've yet to get to a work that is visible. continue; } if (work.From > viewPortEnd) { // We've past the last visible work, so we can abort now. break; } var x = NormalizedByteIndexToClientX(work.From); g.DrawImageUnscaled(work.Bitmap.Bitmap, (int)Math.Round(x), 0); } if (RepeatLength > 0) { var repeatStartX = (float)ByteIndexToClientX(_repeatCurrent); if (float.IsNaN(repeatStartX) == false) { var endByteIndex = _repeatCurrent + Bass.ChannelSeconds2Bytes(_playChannel, _repeatLength); var repeatEndX = (float)ByteIndexToClientX(endByteIndex); var repeatMiddleX = (float)ByteIndexToClientX(endByteIndex - Bass.ChannelSeconds2Bytes(_playChannel, _repeatBackwards)); const int repeatMarkerHeight = 20; var outlinePen = Pens.Black; var y1 = ClientRectangle.Height - repeatMarkerHeight; var y2 = ClientRectangle.Height; { g.DrawLine(outlinePen, repeatStartX - 1, y1, repeatStartX - 1, y2); g.DrawLine(outlinePen, repeatStartX - 1, y1 - 1, repeatStartX + 1, y1 - 1); g.DrawLine(outlinePen, repeatStartX + 1, y1, repeatStartX + 1, y2); g.DrawLine(Pens.LawnGreen, repeatStartX, y1, repeatStartX, y2); } { g.DrawLine(outlinePen, repeatMiddleX - 1, y1, repeatMiddleX - 1, y2); g.DrawLine(outlinePen, repeatMiddleX - 1, y1 - 1, repeatMiddleX + 1, y1 - 1); g.DrawLine(outlinePen, repeatMiddleX + 1, y1, repeatMiddleX + 1, y2); g.DrawLine(Pens.Orange, repeatMiddleX, y1, repeatMiddleX, y2); } { g.DrawLine(outlinePen, repeatEndX - 1, y1, repeatEndX - 1, y2); g.DrawLine(outlinePen, repeatEndX - 1, y1 - 1, repeatEndX + 1, y1 - 1); g.DrawLine(outlinePen, repeatEndX + 1, y1, repeatEndX + 1, y2); g.DrawLine(Pens.Red, repeatEndX, y1, repeatEndX, y2); } if (_repeatPauseRemaining > 0) { var timeString = "" + Math.Round(_repeatPauseRemaining / 1000d, 1); var textSize = g.MeasureString(timeString, SystemFonts.StatusFont); g.DrawString(timeString, SystemFonts.StatusFont, Brushes.Red, Width - textSize.Width - 20, 5); } } } } float paddingBottom = ClientRectangle.Bottom; if (IsPlayable) { var millisecondsStart = (long)Math.Round(Bass.ChannelBytes2Seconds(_playChannel, viewPortStart) * 1000d); var millisecondsEnd = (long)Math.Round(Bass.ChannelBytes2Seconds(_playChannel, viewPortEnd) * 1000d); var millisecondsDistance = millisecondsEnd - millisecondsStart; // Resolution is one large marker every 50 pixels. var preferredResolution = millisecondsDistance / (Width / 50d); const long startingResolution = 100L; var actualResolution = startingResolution; var multipliers = new long[] { 10, // 1 second 20, // 2 seconds 50, // 5 seconds 100, // 10 seconds 150, // 15 seconds 300, // 30 seconds 600, // 1 minute 1200, // 2 minutes 3000, // 5 minutes 6000, // 10 minutes 9000, // 15 minutes 18000, // 30 minutes 36000 // 1 hour }; for (var i = 0; i < multipliers.Length && actualResolution < preferredResolution; i++) { actualResolution = startingResolution * multipliers[i]; } var milliseconds = millisecondsStart - millisecondsStart % actualResolution; while (milliseconds < millisecondsEnd) { var offsetMilliseconds = milliseconds - millisecondsStart; var x = (int)Math.Floor(offsetMilliseconds * (Width / (double)millisecondsDistance)); var stringTime = "" + getTimeString(milliseconds / 1000d / 60) + ":" + getTimeString(milliseconds / 1000d % 60); var timeLineTop = ClientRectangle.Bottom - 10; paddingBottom = Math.Min(paddingBottom, timeLineTop); if (request.IsRendering) { g.DrawLine(Pens.White, x, timeLineTop, x, ClientRectangle.Bottom); using (var f = new Font(FontFamily.GenericSansSerif, 7f)) { var textSize = g.MeasureString(stringTime, f); g.DrawString(stringTime, f, Brushes.White, x + 1, ClientRectangle.Bottom - textSize.Height); } } milliseconds += actualResolution; } } if (request.Notes != null) { if (_mouseDraggingObject is Note) { request.FocusedObject = _mouseDraggingObject; request.HitTestArea = _mouseDraggingArea; } const int hoverPadding = 10; foreach (var note in request.Notes) { var x1 = (float)ByteIndexToClientX(note.Interval.Start); var x2 = (float)ByteIndexToClientX(note.Interval.End); var top = paddingBottom - 45f; var height = 40f; var noteRectangle = new RectangleF(x1, top, x2 - x1, height); if (request.FocusedObject == null) { var ht = request.CursorPoint; if (ht.Y >= noteRectangle.Top && ht.Y <= noteRectangle.Bottom) { if (ht.X >= noteRectangle.Left && ht.X <= noteRectangle.Left + hoverPadding) { request.HitTestArea = HitTestArea.NoteLeft; request.CursorResult = Cursors.SizeWE; request.FocusedObject = note; } else if (ht.X >= noteRectangle.Right - hoverPadding && ht.X <= noteRectangle.Right) { request.HitTestArea = HitTestArea.NoteRight; request.CursorResult = Cursors.SizeWE; request.FocusedObject = note; } else if (noteRectangle.Contains(ht)) { request.HitTestArea = HitTestArea.NoteCenter; request.CursorResult = Cursors.Hand; request.FocusedObject = note; } } } if (request.IsHitTest == false) { if (noteRectangle.Width > 1) { using (var b = new SolidBrush(Color.FromArgb(150, Color.GhostWhite))) { g.FillRectangle(b, noteRectangle); } if (note.Equals(request.FocusedObject)) { using (var hatch = new HatchBrush(HatchStyle.Percent20, Color.LightGray, Color.Transparent)) { g.FillRectangle(hatch, noteRectangle.Left, noteRectangle.Top, hoverPadding, noteRectangle.Height); g.FillRectangle(hatch, noteRectangle.Right - hoverPadding, noteRectangle.Top, hoverPadding, noteRectangle.Height); } } g.DrawRectangle(note.Equals(request.FocusedObject) ? Pens.LightGray : Pens.Gray, noteRectangle.Left, noteRectangle.Top, noteRectangle.Width, noteRectangle.Height); var stringFormat = new StringFormat(); stringFormat.FormatFlags = StringFormatFlags.NoWrap; using (var f = new Font(FontFamily.GenericSansSerif, 8f)) { var measure = g.MeasureString(note.Text, f, noteRectangle.Size, stringFormat); var offsetX = noteRectangle.Width / 2f - measure.Width / 2f; var offsetY = noteRectangle.Height / 2f - measure.Height / 2f; noteRectangle.Offset(offsetX, offsetY); g.DrawString(note.Text, f, Brushes.Black, noteRectangle, stringFormat); } } } } } if (request.IsRendering == false) { return; } if (_mouseDraggingZoom) { var xLow = Math.Min(_mouseDragStartX, _mouseDragEndX); var xHigh = Math.Max(_mouseDragStartX, _mouseDragEndX); using (var b = new SolidBrush(Color.FromArgb(50, Color.LightBlue))) { g.FillRectangle(b, new Rectangle(xLow, 0, xHigh - xLow, ClientRectangle.Height)); } g.DrawLine(Pens.Gray, xLow, 0, xLow, ClientRectangle.Height); g.DrawLine(Pens.Gray, xHigh, 0, xHigh, ClientRectangle.Height); } const int infoPadding = 5; var overviewRectangle = new RectangleF( infoPadding, infoPadding, ClientRectangle.Width * 0.10f, 10); using (var dimBrush = new SolidBrush(Color.FromArgb(150, Color.Gray))) { var timeSinceAnimation = DateTime.Now - _timestampLastAttributeChange; if (timeSinceAnimation <= _attributeChangeAnimationLength) { var volumeWidth = Math.Max(20, Width * 0.20f); var volumeHeight = Math.Min(20, Math.Max(5, Height * 0.10f)); var rectVolume = new RectangleF( Width * 0.5f - volumeWidth / 2f, Height * 0.5f - volumeHeight / 2f, volumeWidth, volumeHeight); var volumeX = rectVolume.X + rectVolume.Width * GetVolume(); var c = new HsvColor((int)(360 * GetVolume()), 75, 75).ToColor(); g.FillRectangle(Brushes.DarkGray, rectVolume); using (var intensityBrush = new SolidBrush(c)) { g.FillRectangle(intensityBrush, rectVolume.Left, rectVolume.Top, volumeX - rectVolume.Left, rectVolume.Height); } g.DrawRectangle(Pens.WhiteSmoke, rectVolume.X, rectVolume.Y, rectVolume.Width, rectVolume.Height); g.DrawLine(Pens.WhiteSmoke, volumeX, rectVolume.Top, volumeX, rectVolume.Bottom); var percentageString = Math.Round(GetVolume() * 100) + "%"; var size = g.MeasureString(percentageString, SystemFonts.StatusFont); g.DrawString(percentageString, SystemFonts.StatusFont, Brushes.WhiteSmoke, rectVolume.Right + 4f, rectVolume.Top + rectVolume.Height / 2 - size.Height / 2f); } long bytePosition; if (IsPlayable) { bytePosition = Normalize(Bass.ChannelGetPosition(_playChannel)); var timePosition = Bass.ChannelBytes2Seconds(_playChannel, bytePosition); var stringTimeCurrent = "" + getTimeString(timePosition / 60) + ":" + getTimeString(timePosition % 60); g.DrawString(stringTimeCurrent, SystemFonts.DefaultFont, Brushes.White, overviewRectangle.Right, overviewRectangle.Top); } else { bytePosition = 0; } if (_bytesTotal > 0) { g.FillRectangle(dimBrush, overviewRectangle.X, overviewRectangle.Y, overviewRectangle.Width, overviewRectangle.Height); var bytesPerOverviewPixel = _bytesTotal / overviewRectangle.Width; var overviewStartX = overviewRectangle.Left + viewPortStart / bytesPerOverviewPixel; using (var overviewFocusBrush = new SolidBrush(Color.FromArgb(150, Color.LightBlue))) { var overviewWidth = (long)Math.Max(1, overviewRectangle.Width * GetZoomRatio()); g.FillRectangle(overviewFocusBrush, overviewStartX, overviewRectangle.Y, overviewWidth, overviewRectangle.Height); } if (bytePosition >= 0) { using (var caretPen = new Pen(Color.FromArgb(150, Color.Red))) { var globalCaretX = (float)ByteIndexToClientX(bytePosition); var overviewCaretX = overviewRectangle.Left + bytePosition / bytesPerOverviewPixel; g.DrawLine(caretPen, globalCaretX, 0, globalCaretX, ClientRectangle.Height); g.DrawLine(caretPen, overviewCaretX, overviewRectangle.Top, overviewCaretX, overviewRectangle.Bottom); } } } } }
protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); var renderRequest = new RenderRequest { IsHitTest = true, CursorPoint = e.Location }; Render(renderRequest); switch (e.Button) { case MouseButtons.XButton1: case MouseButtons.XButton2: // TODO: Redo this so that the paragraph selected in the text is the selected one // TODO: Mouse4 changes start, mouse5 changes end // TODO: The note should not have to be visible; remake the NoteMoved so the note is not required // Middle mouse button should resize the note according to the most closely clicked location, in relevance to the location that the user is in the text var clickedByteIndex = ClientXToByteIndex(e.X); var clickedInterval = new Interval(clickedByteIndex, clickedByteIndex); long[] closestDistances = null; var closestDistance = long.MaxValue; Note closestNote = null; foreach (var note in renderRequest.Notes) { if (note.IsFocused) { closestNote = note; closestDistances = note.Interval.GetDistanceFromOverlap(clickedByteIndex); break; } } if (closestNote == null) { foreach (var note in renderRequest.Notes) { var distance = note.Interval.GetDistanceFromOverlap(clickedByteIndex); if (note.IsFocused || note.Interval.IsOverlapping(clickedInterval)) { closestNote = note; closestDistances = distance; break; } var actualDistance = Math.Min(Math.Abs(distance[0]), Math.Abs(distance[1])); if (actualDistance < closestDistance) { closestNote = note; closestDistances = distance; closestDistance = actualDistance; } } } if (closestNote != null) { var isStart = Math.Abs(closestDistances[0]) < Math.Abs(closestDistances[1]); if (NoteMoved != null) { var args = new NoteMovedEventArgs { Area = isStart ? HitTestArea.NoteLeft : HitTestArea.NoteRight, ByteIndex = clickedByteIndex, Note = closestNote }; NoteMoved(this, args); } } break; case MouseButtons.Middle: break; #region old zoom code /* * if (this._mouseDraggingZoom) * { * // Let's set the time zoom! * var low = Math.Max(0, Math.Min(this._mouseDragStartX, this._mouseDragEndX)); * var high = Math.Min(this.ClientRectangle.Width, Math.Max(this._mouseDragStartX, this._mouseDragEndX)); * * // Set as approximative as we can. * var timeStart = this.ClientXToByteIndex(low); * var timeEnd = this.ClientXToByteIndex(high); * * this._zoomStack.Push((timeEnd - timeStart) / (double)this._bytesTotal); * * // Restart the processing, since we've now zoomed in. * this.ClearWork(); * this.QueueWork(new Work(this.GetCurrentViewPortStart(), this.GetCurrentViewPortEnd())); * } * else * { * // The user clicked Middle Mouse, but had not selected anything. Let's zoom out. * // Zoom out to the previous zoom level. * if (this._zoomStack.Count > 0) * { * this._zoomStack.Pop(); * this.ClearWork(); * this.QueueWork(new Work(this.GetCurrentViewPortStart(), this.GetCurrentViewPortEnd())); * } * } */ #endregion case MouseButtons.Left: if (_mouseDraggingPan == false) { var note = renderRequest.FocusedObject as Note; if (note != null) { if (NoteClicked != null) { NoteClicked(this, new NoteClickedEventArgs { Note = note, Area = renderRequest.HitTestArea }); } } } break; } if (_mouseDraggingPan) { if (_mouseDraggingObject == null && IsCaretInsideViewPort) { var distanceIntoViewPort = GetCurrentBytePosition() - GetCurrentViewPortStart(); _caretOffset = distanceIntoViewPort / (double)GetCurrentViewPortDistance(); } else { // Since we stopped panning outside of the currently playing viewport, we'll set the caret offset to NaN. // This means that the viewport will stay where it is, while the track is playing. switch (Bass.ChannelIsActive(_playChannel)) { case PlaybackState.Playing: _caretOffset = double.NaN; break; } } // Let's get the new area that should be rendered, and queue the work. // We'll add the whole visible viewport, and it will automatically be split and merged properly. var work = new Work(GetCurrentViewPortStart(), GetCurrentViewPortEnd()); QueueWork(work); } Cursor = Cursors.Default; _mouseDraggingZoom = false; _mouseDraggingPan = false; _mouseDown = false; _mouseDraggingObject = null; _mouseDraggingArea = HitTestArea.None; Invalidate(); }
protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); switch (e.Button) { case MouseButtons.Right: // Set the caret position, so that we'll start following with the viewport _viewPortStart = GetCurrentViewPortStart(); // Start playing at the location that was clicked. var clickedByteIndex = ClientXToByteIndex(e.Location.X); switch (Bass.ChannelIsActive(_playChannel)) { case PlaybackState.Playing: StartPlaying(clickedByteIndex); break; default: SetLocation(clickedByteIndex); break; } if (double.IsNaN(_caretOffset) == false) { _caretOffset = e.X / (double)ClientRectangle.Width; } break; case MouseButtons.Middle: _mouseDown = true; _mouseDragStartX = e.Location.X; break; case MouseButtons.Left: var request = new RenderRequest { IsHitTest = true, CursorPoint = e.Location }; Render(request); _mouseDown = true; _mouseDragStartX = e.Location.X; _mouseDraggingArea = HitTestArea.None; var note = request.FocusedObject as Note; if (note != null) { _mouseDraggingArea = request.HitTestArea; _mouseDraggingObject = request.FocusedObject; _viewPortStart = GetCurrentViewPortStart(); switch (Bass.ChannelIsActive(_playChannel)) { case PlaybackState.Playing: _caretOffset = double.NaN; break; } switch (request.HitTestArea) { case HitTestArea.NoteLeft: _mouseDragByteIndexStart = note.Interval.Start; break; case HitTestArea.NoteRight: _mouseDragByteIndexStart = note.Interval.End; break; case HitTestArea.NoteCenter: _mouseDragByteIndexStart = note.Interval.Start; break; } Cursor = request.CursorResult ?? Cursors.Default; } else { _mouseDragByteIndexStart = GetCurrentViewPortStart(); _viewPortStart = _mouseDragByteIndexStart; _caretOffset = double.NaN; } break; } }
protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); var renderRequest = new RenderRequest { IsHitTest = true, CursorPoint = e.Location }; Render(renderRequest); if (_mouseDown) { _mouseDragEndX = e.Location.X; var distance = Math.Max(_mouseDragStartX, _mouseDragEndX) - Math.Min(_mouseDragStartX, _mouseDragEndX); // If we've dragged more than a few pixels, so we'll count it as a drag/zoom action var isDragging = distance > 3; switch (e.Button) { case MouseButtons.Middle: _mouseDraggingZoom = isDragging; break; case MouseButtons.Left: var previous = _mouseDraggingPan; _mouseDraggingPan = isDragging; if (previous != _mouseDraggingPan && _mouseDraggingObject == null) { Cursor = _mouseDraggingPan ? Cursors.Hand : Cursors.Default; } break; } } if (_mouseDraggingPan) { var startByteIndex = ClientXToByteIndex(_mouseDragStartX); var currentByteIndex = ClientXToByteIndex(e.Location.X); var byteDistance = startByteIndex - currentByteIndex; if (_mouseDraggingObject is Note) { var note = (Note)_mouseDraggingObject; var capStart = 0L; var capEnd = _bytesTotal; if (renderRequest.Notes != null) { var foundIndex = -1; for (var i = 0; i < renderRequest.Notes.Count; i++) { var otherNote = renderRequest.Notes[i]; if (otherNote.Equals(note) == false) { continue; } switch (_mouseDraggingArea) { case HitTestArea.NoteLeft: capEnd = otherNote.Interval.End - (long)Math.Round(GetBytesPerPixel() * GetZoomRatio() * 2); break; case HitTestArea.NoteRight: capStart = otherNote.Interval.Start + (long)Math.Round(GetBytesPerPixel() * GetZoomRatio() * 2); break; } foundIndex = i; break; } if (foundIndex != -1) { long previousEnd; long nextStart; if (renderRequest.HitTestArea == HitTestArea.NoteCenter) { previousEnd = foundIndex > 0 ? renderRequest.Notes[foundIndex - 1].Interval.End : capStart; nextStart = foundIndex < renderRequest.Notes.Count - 1 ? renderRequest.Notes[foundIndex + 1].Interval.Start - renderRequest.Notes[foundIndex + 1].Interval.Length : capEnd; } else { previousEnd = foundIndex > 0 ? renderRequest.Notes[foundIndex - 1].Interval.End : capStart; nextStart = foundIndex < renderRequest.Notes.Count - 1 ? renderRequest.Notes[foundIndex + 1].Interval.Start : capEnd; } capStart = Math.Max(capStart, previousEnd); capEnd = Math.Min(capEnd, nextStart); } } var args = new NoteMovedEventArgs { Note = note, Area = _mouseDraggingArea, ByteIndex = Math.Max(capStart, Math.Min(capEnd, _mouseDragByteIndexStart - byteDistance)) }; NoteMoved(this, args); } else { _viewPortStart = Math.Max(0, _mouseDragByteIndexStart + byteDistance); } } else { if (_mouseDown == false) { Cursor = renderRequest.CursorResult ?? Cursors.Default; } } Invalidate(); }