Files
Goatgarien_BAKKA-Editor/BAKKA-Editor/CircleView.cs
T

463 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
namespace BAKKA_Editor
{
internal class CircleView
{
public SizeF PanelSize { get; private set; }
public RectangleF DrawRect { get; private set; }
public PointF TopCorner { get; private set; }
public PointF CenterPoint { get; private set; }
public float Radius { get; private set; }
public float CurrentMeasure { get; set; }
/// <summary>
/// Number of measures in the future that are visible
/// </summary>
public float TotalMeasureShowNotes { get; set; } = 0.5f;
// Pens and Brushes
public Pen BasePen { get; set; }
public Pen BeatPen { get; set; }
public Pen TickMinorPen { get; set; }
public Pen TickMediumPen { get; set; }
public Pen TickMajorPen { get; set; }
public SolidBrush HoldBrush { get; set; } = new SolidBrush(Color.FromArgb(170, Color.Yellow));
public SolidBrush MaskBrush { get; set; } = new SolidBrush(Color.DimGray);
public SolidBrush BackgroundBrush { get; set; }
public Pen HighlightPen { get; set; }
public Pen FlairPen { get; set; }
private int CursorTransparency = 110;
private int SelectTransparency = 110;
private int FlairTransparency = 75;
// Graphics.
BufferedGraphicsContext gfxContext = BufferedGraphicsManager.Current;
BufferedGraphics bufGraphics;
// Mouse information. Public so other GUI elements can be updated with their values.
public int mouseDownPos = -1;
public Point mouseDownPt;
public int lastMousePos = -1;
public bool rolloverPos = false;
public bool rolloverNeg = false;
public CircleView(SizeF size)
{
Update(size);
}
public void Update(SizeF size)
{
PanelSize = size;
float basePenWidth = PanelSize.Width * 4.0f / 600.0f;
TopCorner = new PointF(basePenWidth * 4, basePenWidth * 4);
DrawRect = new RectangleF(
TopCorner.X,
TopCorner.Y,
PanelSize.Width - basePenWidth * 8,
PanelSize.Height - basePenWidth * 8);
Radius = DrawRect.Width / 2.0f;
CenterPoint = new PointF(TopCorner.X + Radius, TopCorner.Y + Radius);
// Pens
BasePen = new Pen(Color.Black, basePenWidth);
BeatPen = new Pen(Color.White, PanelSize.Width * 1.0f / 600.0f);
TickMinorPen = new Pen(Color.Black, PanelSize.Width * 2.0f / 600.0f);
TickMediumPen = new Pen(Color.Black, PanelSize.Width * 4.0f / 600.0f);
TickMajorPen = new Pen(Color.Black, PanelSize.Width * 7.0f / 600.0f);
HighlightPen = new Pen(Color.FromArgb(SelectTransparency, Color.LightPink), PanelSize.Width * 8.0f / 600.0f);
FlairPen = new Pen(Color.FromArgb(FlairTransparency, Color.Yellow), PanelSize.Width * 8.0f / 600.0f);
}
private ArcInfo GetScaledRect(float objectTime)
{
ArcInfo info = new();
float notescaleInit = 1 - ((objectTime - CurrentMeasure) * (1 / TotalMeasureShowNotes)); // Scale from 0-1
info.NoteScale = (float)Math.Pow(10.0f, notescaleInit) / 10.0f;
float scaledRectSize = DrawRect.Width * info.NoteScale;
float scaledRadius = scaledRectSize / 2.0f;
info.Rect = new RectangleF(
new PointF(
CenterPoint.X - scaledRadius,
CenterPoint.Y - scaledRadius),
new SizeF(scaledRectSize, scaledRectSize));
return info;
}
private ArcInfo GetArcInfo(Note note)
{
ArcInfo info = GetScaledRect(note.Measure);
info.StartAngle = -note.Position * 6;
info.ArcLength = -note.Size * 6;
if(info.ArcLength != -360)
{
info.StartAngle -= 2;
info.ArcLength += 4;
}
return info;
}
// Updates the mouse down position within the circle, and returns the new position.
public void UpdateMouseDown(float xCen, float yCen, Point mousePt)
{
float theta = (float)(Math.Atan2(yCen, xCen) * 180.0f / Math.PI);
if (theta < 0)
theta += 360.0f;
// Left click moves the cursor
mouseDownPos = (int)(theta / 6.0f);
mouseDownPt = mousePt;
lastMousePos = -1;
rolloverPos = false;
rolloverNeg = false;
}
public void UpdateMouseUp()
{
if (mouseDownPos <= -1)
{
return;
}
// Reset position and point.
mouseDownPos = -1;
mouseDownPt = new Point();
}
// Updates the mouse position and returns the new position in degrees.
public int UpdateMouseMove(float xCen, float yCen)
{
float thetaCalc = (float)(Math.Atan2(yCen, xCen) * 180.0f / Math.PI);
if (thetaCalc < 0)
thetaCalc += 360.0f;
int theta = (int)(thetaCalc / 6.0f);
int delta = theta - lastMousePos;
// Handle rollover
if (delta == -59)
{
if (rolloverNeg)
rolloverNeg = false;
else
rolloverPos = true;
}
else if (delta == 59)
{
if (rolloverPos)
rolloverPos = false;
else
rolloverNeg = true;
}
lastMousePos = theta;
return theta;
}
public void DrawBackground(int width, int height, Color color)
{
Rectangle panelRect = new Rectangle(0, 0, width, height);
// Set drawing mode
bufGraphics.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
// Remember background color
BackgroundBrush = new SolidBrush(color);
// Draw background
bufGraphics.Graphics.FillRectangle(BackgroundBrush, panelRect);
}
public void DrawMasks(Chart chart)
{
var masks = chart.Notes.Where(
x => x.Measure <= CurrentMeasure
&& x.IsMask).ToList();
foreach (var mask in masks)
{
if (mask.NoteType == NoteType.MaskAdd)
{
// Check if there's a MaskRemove that counters the MaskAdd (unless the MaskAdd is later)
var rem = masks.FirstOrDefault(x => x.NoteType == NoteType.MaskRemove &&
x.Position == mask.Position && x.Size == mask.Size);
if (rem == null || rem.Measure < mask.Measure)
bufGraphics.Graphics.FillPie(MaskBrush, DrawRect.ToInt(), -mask.Position * 6.0f, -mask.Size * 6.0f);
}
else if (mask.NoteType == NoteType.MaskRemove) // Explicitly draw MaskRemove for edge cases
{
bufGraphics.Graphics.FillPie(BackgroundBrush, DrawRect.ToInt(), -mask.Position * 6.0f, -mask.Size * 6.0f);
}
}
}
public void DrawCircle()
{
// Switch drawing modes
bufGraphics.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// Draw measure circle
for (float meas = (float)Math.Ceiling(CurrentMeasure); (meas - CurrentMeasure) < TotalMeasureShowNotes; meas += 1.0f)
{
var info = GetScaledRect(meas);
if (info.Rect.Width >= 1)
{
bufGraphics.Graphics.DrawEllipse(BeatPen, info.Rect);
}
}
// Draw base circle
bufGraphics.Graphics.DrawEllipse(TickMediumPen, DrawRect);
}
public void DrawDegreeLines()
{
for (int i = 0; i < 360; i += 6)
{
PointF startPoint = new PointF(
(float)(Radius * Math.Cos(Utils.DegToRad(i))) + CenterPoint.X,
(float)(Radius * Math.Sin(Utils.DegToRad(i))) + CenterPoint.Y);
float tickLength = PanelSize.Width * 10.0f / 285.0f;
float innerRad = Radius - tickLength;
Pen activePen;
if (i % 90 == 0)
{
innerRad = Radius - (tickLength * 3.5f);
activePen = TickMajorPen;
}
else if (i % 30 == 0)
{
innerRad = Radius - (tickLength * 2.5f);
activePen = TickMediumPen;
}
else
{
activePen = TickMinorPen;
}
PointF endPoint = new PointF(
(float)(innerRad * Math.Cos(Utils.DegToRad(i))) + CenterPoint.X,
(float)(innerRad * Math.Sin(Utils.DegToRad(i))) + CenterPoint.Y);
bufGraphics.Graphics.DrawLine(activePen, startPoint, endPoint);
}
}
public void DrawHolds(Chart chart, bool highlightSelectedNote, int selectedNoteIndex)
{
ArcInfo currentInfo = GetScaledRect(CurrentMeasure);
ArcInfo endInfo = GetScaledRect(CurrentMeasure + TotalMeasureShowNotes);
// First, draw holes that start before the viewpoint and have nodes that end after
List<Note> holdNotes = chart.Notes.Where(
x => x.Measure < CurrentMeasure
&& x.NextNote != null
&& x.NextNote.Measure > (CurrentMeasure + TotalMeasureShowNotes)
&& x.IsHold).ToList();
foreach (var note in holdNotes)
{
ArcInfo info = GetArcInfo(note);
ArcInfo nextInfo = GetArcInfo((Note)note.NextNote);
//GraphicsPath path = new GraphicsPath();
//path.AddArc(endInfo.Rect, info.StartAngle, info.ArcLength);
//path.AddArc(currentInfo.Rect, info.StartAngle + info.ArcLength, -info.ArcLength);
//bufGraphics.Graphics.FillPath(HoldBrush, path);
float ratio = (currentInfo.Rect.Width - nextInfo.Rect.Width) / (info.Rect.Width - nextInfo.Rect.Width);
float startNoteAngle = nextInfo.StartAngle;
float endNoteAngle = info.StartAngle;
if (nextInfo.StartAngle > info.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
startNoteAngle -= 360;
}
else if (info.StartAngle > nextInfo.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
endNoteAngle -= 360;
}
float startAngle = ratio * (endNoteAngle - startNoteAngle) + startNoteAngle;
float endAngle = ratio * ((endNoteAngle - info.ArcLength) - (startNoteAngle - nextInfo.ArcLength)) +
(startNoteAngle - nextInfo.ArcLength);
float arcLength = startAngle - endAngle;
float ratio2 = (endInfo.Rect.Width - nextInfo.Rect.Width) / (info.Rect.Width - nextInfo.Rect.Width);
float startNoteAngle2 = nextInfo.StartAngle;
float endNoteAngle2 = info.StartAngle;
if (nextInfo.StartAngle > info.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
startNoteAngle2 -= 360;
}
else if (info.StartAngle > nextInfo.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
endNoteAngle2 -= 360;
}
float startAngle2 = ratio2 * (endNoteAngle2 - startNoteAngle2) + startNoteAngle2;
float endAngle2 = ratio2 * ((endNoteAngle2 - info.ArcLength) - (startNoteAngle2 - nextInfo.ArcLength)) +
(startNoteAngle2 - nextInfo.ArcLength);
float arcLength2 = startAngle2 - endAngle2;
GraphicsPath path = new GraphicsPath();
path.AddArc(currentInfo.Rect, startAngle, arcLength);
path.AddArc(endInfo.Rect, startAngle2 + arcLength2, -arcLength2);
bufGraphics.Graphics.FillPath(HoldBrush, path);
}
// Second, draw all the notes on-screen
holdNotes = chart.Notes.Where(
x => x.Measure >= CurrentMeasure
&& x.Measure <= (CurrentMeasure + TotalMeasureShowNotes)
&& x.IsHold).ToList();
foreach (var note in holdNotes)
{
ArcInfo info = GetArcInfo(note);
// If the previous note is off-screen, this case handles that
if (note.PrevNote != null && note.PrevNote.Measure < CurrentMeasure)
{
ArcInfo prevInfo = GetArcInfo((Note)note.PrevNote);
float ratio = (currentInfo.Rect.Width - info.Rect.Width) / (prevInfo.Rect.Width - info.Rect.Width);
float startNoteAngle = info.StartAngle;
float endNoteAngle = prevInfo.StartAngle;
if (info.StartAngle > prevInfo.StartAngle && (Math.Abs(info.StartAngle - prevInfo.StartAngle) > 180))
{
startNoteAngle -= 360;
}
else if (prevInfo.StartAngle > info.StartAngle && (Math.Abs(info.StartAngle - prevInfo.StartAngle) > 180))
{
endNoteAngle -= 360;
}
float startAngle = ratio * (endNoteAngle - startNoteAngle) + startNoteAngle;
float endAngle = ratio * ((endNoteAngle - prevInfo.ArcLength) - (startNoteAngle - info.ArcLength)) +
(startNoteAngle - info.ArcLength);
float arcLength = startAngle - endAngle;
GraphicsPath path = new GraphicsPath();
path.AddArc(info.Rect, info.StartAngle, info.ArcLength);
path.AddArc(currentInfo.Rect, startAngle + arcLength, -arcLength);
bufGraphics.Graphics.FillPath(HoldBrush, path);
}
// If the next note is on-screen, this case handles that
if (note.NextNote != null && note.NextNote.Measure <= (CurrentMeasure + TotalMeasureShowNotes))
{
ArcInfo nextInfo = GetArcInfo((Note)note.NextNote);
GraphicsPath path = new GraphicsPath();
path.AddArc(info.Rect, info.StartAngle, info.ArcLength);
path.AddArc(nextInfo.Rect, nextInfo.StartAngle + nextInfo.ArcLength, -nextInfo.ArcLength);
bufGraphics.Graphics.FillPath(HoldBrush, path);
}
// If the next note is off-screen, this case handles that
if (note.NextNote != null && note.NextNote.Measure > (CurrentMeasure + TotalMeasureShowNotes))
{
ArcInfo nextInfo = GetArcInfo((Note)note.NextNote);
float ratio = (endInfo.Rect.Width - nextInfo.Rect.Width) / (info.Rect.Width - nextInfo.Rect.Width);
float startNoteAngle = nextInfo.StartAngle;
float endNoteAngle = info.StartAngle;
if (nextInfo.StartAngle > info.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
startNoteAngle -= 360;
}
else if (info.StartAngle > nextInfo.StartAngle && (Math.Abs(nextInfo.StartAngle - info.StartAngle) > 180))
{
endNoteAngle -= 360;
}
float startAngle = ratio * (endNoteAngle - startNoteAngle) + startNoteAngle;
float endAngle = ratio * ((endNoteAngle - info.ArcLength) - (startNoteAngle - nextInfo.ArcLength)) +
(startNoteAngle - nextInfo.ArcLength);
float arcLength = startAngle - endAngle;
GraphicsPath path = new GraphicsPath();
path.AddArc(endInfo.Rect, startAngle, arcLength);
path.AddArc(info.Rect, info.StartAngle + info.ArcLength, -info.ArcLength);
bufGraphics.Graphics.FillPath(HoldBrush, path);
}
// Draw note
if (info.Rect.Width >= 1)
{
bufGraphics.Graphics.DrawArc(GetPen(note), info.Rect, info.StartAngle, info.ArcLength);
// Draw bonus
if (note.IsFlair)
bufGraphics.Graphics.DrawArc(HighlightPen, info.Rect, info.StartAngle + 2, info.ArcLength - 4);
// Plot highlighted
if (highlightSelectedNote)
{
if (selectedNoteIndex != -1 && note == chart.Notes[selectedNoteIndex])
{
bufGraphics.Graphics.DrawArc(HighlightPen, info.Rect, info.StartAngle + 2, info.ArcLength - 4);
}
}
}
}
}
public void DrawNotes(Chart chart, bool highlightSelectedNote, int selectedNoteIndex)
{
List<Note> drawNotes = chart.Notes.Where(
x => x.Measure >= CurrentMeasure
&& x.Measure <= (CurrentMeasure + TotalMeasureShowNotes)
&& !x.IsHold && !x.IsMask).ToList();
foreach (var note in drawNotes)
{
ArcInfo info = GetArcInfo(note);
if (info.Rect.Width >= 1)
{
bufGraphics.Graphics.DrawArc(GetPen(note, info.NoteScale), info.Rect, info.StartAngle, info.ArcLength);
if (note.IsFlair)
{
bufGraphics.Graphics.DrawArc(FlairPen, info.Rect, info.StartAngle + 2, info.ArcLength - 4);
}
// Plot highlighted
if (highlightSelectedNote)
{
if (selectedNoteIndex != -1 && note == chart.Notes[selectedNoteIndex])
{
bufGraphics.Graphics.DrawArc(HighlightPen, info.Rect, info.StartAngle + 2, info.ArcLength - 4);
}
}
}
}
}
public void DrawCursor(NoteType noteType, float startAngle, float sweepAngle)
{
bufGraphics.Graphics.DrawArc(
GetPen(noteType),
DrawRect,
-(float)startAngle * 6.0f,
-(float)sweepAngle * 6.0f);
}
public void Render(Graphics graphics)
{
bufGraphics.Render(graphics);
}
public void SetBufferedGraphicsContext(int width, int height, Graphics graphics)
{
gfxContext.MaximumBuffer = new Size(width + 1, height + 1);
bufGraphics = gfxContext.Allocate(graphics, new Rectangle(0, 0, width, height));
}
public Pen GetPen(Note note, float noteScale = 1.0f)
{
return new Pen(note.Color, PanelSize.Width * 8.0f * noteScale / 600.0f);
}
public Pen GetPen(NoteType noteType)
{
return new Pen(Color.FromArgb(CursorTransparency, Utils.NoteTypeToColor(noteType)), PanelSize.Width * 24.0f / 600.0f);
}
}
internal struct ArcInfo
{
public float StartAngle;
public float ArcLength;
public RectangleF Rect;
public float NoteScale;
}
}