353 lines
12 KiB
C#
353 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
using JD.Application.View.Timeline;
|
|
using JD.Application.View.WindowLess;
|
|
using JD.Audio.Annotations;
|
|
using JD.Collections.Extensions;
|
|
using TeaBox;
|
|
using JD.Serialization;
|
|
using Color = System.Drawing.Color;
|
|
using FontFamily = System.Drawing.FontFamily;
|
|
using Pen = System.Drawing.Pen;
|
|
|
|
namespace JD.Timeline.View.ClipView.Widgets
|
|
{
|
|
public class KaraokeClipWidget : ClipWidget
|
|
{
|
|
#region Variables
|
|
|
|
private static readonly Font _textFont = new Font(FontFamily.GenericSansSerif, 8);
|
|
private static readonly StringFormat _stringFormat = new StringFormat
|
|
{
|
|
Alignment = StringAlignment.Center,
|
|
LineAlignment = StringAlignment.Center
|
|
};
|
|
protected static readonly Color SelectionBorderColor = Color.FromArgb(200, 150, 0);
|
|
protected static readonly Color SelectionFillColor = Color.FromArgb(255, 220, 20);
|
|
protected static readonly Pen SelectionBorderPen = new Pen(SelectionBorderColor, 1);
|
|
protected static readonly Color WarningFillColor = Color.FromArgb(255, 255, 0);
|
|
private static readonly Color _textColor = Color.FromArgb(85, 85, 85);
|
|
private static readonly Bitmap _warningBitmap = Properties.Resources.Icon_16x16_warning;
|
|
|
|
private Rectangle m_toleranceBounds = Rectangle.Empty;
|
|
private Rectangle m_previousToleranceBounds = Rectangle.Empty;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public KaraokeClipWidget(Clip model)
|
|
: base(model)
|
|
{
|
|
model.PropertyChanged += model_PropertyChanged;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public new KaraokeClip Model
|
|
{
|
|
get { return (KaraokeClip)base.Model; }
|
|
}
|
|
|
|
public IEnumerable<KaraokeClipSection> Sections
|
|
{
|
|
get
|
|
{
|
|
return Controls.OfType<KaraokeClipSection>();
|
|
}
|
|
}
|
|
|
|
public override string ToolTipText
|
|
{
|
|
get
|
|
{
|
|
string tooltip = string.Format("Lyrics: \"" + Model.Lyrics + "\"\n");
|
|
if (Model.ContentType == KaraokeClip.ContentType_Enum.KaraokeContent_Singing)
|
|
{
|
|
tooltip += $"Note: {Model.MidiNoteName} ({Model.MidiNote})\n";
|
|
}
|
|
tooltip += base.ToolTipText;
|
|
return tooltip;
|
|
}
|
|
}
|
|
|
|
public Rectangle ToleranceBounds
|
|
{
|
|
get
|
|
{
|
|
return m_toleranceBounds;
|
|
}
|
|
set
|
|
{
|
|
m_toleranceBounds = value;
|
|
}
|
|
}
|
|
|
|
public Rectangle PreviousToleranceBounds
|
|
{
|
|
get
|
|
{
|
|
return m_previousToleranceBounds;
|
|
}
|
|
set
|
|
{
|
|
m_previousToleranceBounds = value;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected override void SetupBounds()
|
|
{
|
|
if (Timeline != null)
|
|
{
|
|
int left = Timeline.PixelFromFrame(Model.StartFrame);
|
|
int right = Timeline.PixelFromFrame(Model.StopFrame);
|
|
int width = right - left;
|
|
Bounds = new Rectangle(left, Track.Bounds.Top, width, Track.Height);
|
|
}
|
|
|
|
PreviousToleranceBounds = ToleranceBounds;
|
|
ToleranceBounds = GetToleranceBounds();
|
|
}
|
|
|
|
private Rectangle GetToleranceBounds()
|
|
{
|
|
if (Timeline != null)
|
|
{
|
|
var toleranceStartFrame = Model.StartTimeTolerance;
|
|
var toleranceEndFrame = Model.EndTimeTolerance;
|
|
int left = Timeline.PixelFromFrame(Model.StartFrame - toleranceStartFrame);
|
|
int right = Timeline.PixelFromFrame(Model.StopFrame + toleranceEndFrame);
|
|
int width = right - left;
|
|
|
|
int height = (int)(Track.Height + Track.Height * Model.SemitoneTolerance * 2);
|
|
int top = Track.Bounds.Top + Track.Height / 2 - height / 2;
|
|
return new Rectangle(left, top, width, height);
|
|
}
|
|
return Bounds;
|
|
}
|
|
|
|
protected override void OnPaint(PaintEventArgs e)
|
|
{
|
|
SetupBounds();
|
|
|
|
var graphics = e.Graphics;
|
|
|
|
var songSection = Timeline.Wave().Annotations.SongSections.Reverse().FirstOrDefault(x => x.Time.Value <= Time);
|
|
|
|
|
|
Color fillColor = songSection != null ? songSection.Type.Color().Add(50) : Color.Orange;
|
|
Color borderColor = fillColor.Add(fillColor.IsBright() ? -40 : 20);
|
|
Color toleranceColor = fillColor.Add((int)(-(fillColor.GetBrightness() * 255.0f * 0.5f))).WithAlpha(50);
|
|
if (Model.IsEndOfLine)
|
|
{
|
|
fillColor = fillColor.Add(40);
|
|
}
|
|
|
|
if (Selected)
|
|
{
|
|
fillColor = SelectionFillColor;
|
|
}
|
|
|
|
if (Selected)
|
|
{
|
|
FillBackground(graphics, ToleranceBounds, toleranceColor);
|
|
}
|
|
|
|
FillBackground(graphics, Bounds, fillColor);
|
|
|
|
DrawRectangle(graphics, Bounds, StyleSheet.Pens[borderColor]);
|
|
|
|
if (Selected)
|
|
{
|
|
DrawRectangle(graphics, Bounds, SelectionBorderPen);
|
|
}
|
|
|
|
Color endOfLineColor = Selected ? SelectionBorderPen.Color : fillColor.Add(fillColor.IsBright() ? -60 : 40);
|
|
var endOfLineBrush = new HatchBrush(HatchStyle.BackwardDiagonal, endOfLineColor, Color.Transparent);
|
|
if (Model.IsEndOfLine)
|
|
{
|
|
graphics.FillRectangle(endOfLineBrush, Bounds);
|
|
}
|
|
|
|
if (Sections.IsEmpty())
|
|
{
|
|
InitSections();
|
|
}
|
|
|
|
if (Bounds.Width > 12)
|
|
{
|
|
if (Selected && !Sections.IsEmpty() &&
|
|
(Bounds.Width/Sections.Count()) > 6 &&
|
|
!Model.CollisionsWithOther &&
|
|
Model.Duration >= Sections.Count() &&
|
|
((ClipViewControl)RootControl).Selection.Count() == 1)
|
|
{
|
|
Sections.ForEach(x => x.IsVisible = true);
|
|
}
|
|
else
|
|
{
|
|
Sections.ForEach(x => x.IsVisible = false);
|
|
graphics.DrawString(Model.Lyrics.Replace("|", " "), _textFont, StyleSheet.Brushes[_textColor], Bounds, _stringFormat);
|
|
}
|
|
|
|
}
|
|
UpdateSections();
|
|
OnPaintChildControls(e);
|
|
|
|
if (!Model.IsActive)
|
|
{
|
|
DrawAsInactive(graphics);
|
|
}
|
|
|
|
if (Model.CollisionsWithOther)
|
|
{
|
|
DrawClipWithWarning(graphics);
|
|
}
|
|
}
|
|
|
|
void DrawClipWithWarning(Graphics graphics)
|
|
{
|
|
var imageBounds = new Rectangle(Bounds.X + 2, Bounds.Top + 2, _warningBitmap.Width, _warningBitmap.Height);
|
|
int right = Bounds.Right;
|
|
if (imageBounds.Right > right)
|
|
{
|
|
imageBounds.Width -= imageBounds.Right - right;
|
|
}
|
|
if (_warningBitmap != null)
|
|
{
|
|
graphics.DrawImageUnscaledAndClipped(_warningBitmap, imageBounds);
|
|
}
|
|
}
|
|
|
|
void InitSections()
|
|
{
|
|
string[] lyricsSections;
|
|
if (Model.Lyrics.TrimEnd(' ').Contains(" "))
|
|
{
|
|
bool endsWithSpace = Model.Lyrics.EndsWith(" ");
|
|
lyricsSections = Model.Lyrics.Split(new []{' '}, StringSplitOptions.RemoveEmptyEntries);
|
|
for (int i = 0; i < (endsWithSpace ? lyricsSections.Length : lyricsSections.Length - 1); i++)
|
|
lyricsSections[i] += ' ';
|
|
}
|
|
else
|
|
{
|
|
lyricsSections = Model.Lyrics.ToCharArray().Select(x => x.ToString()).ToArray();
|
|
}
|
|
|
|
Controls.Clear();
|
|
|
|
for (int i = 0; i < lyricsSections.Length; i++)
|
|
{
|
|
var sectionWidget = new KaraokeClipSection
|
|
{
|
|
Lyrics = lyricsSections[i],
|
|
StartTime = Model.StartFrame + i * Model.Duration / (float)lyricsSections.Length,
|
|
EndTime = Model.StartFrame + (i + 1) * Model.Duration / (float)lyricsSections.Length,
|
|
IsLastSection = (i == lyricsSections.Length - 1),
|
|
IsVisible = false
|
|
};
|
|
Controls.Add(sectionWidget);
|
|
Bounds = Rectangle.Union(Bounds, sectionWidget.Bounds);
|
|
}
|
|
}
|
|
|
|
private void UpdateSections()
|
|
{
|
|
foreach (var section in Sections)
|
|
{
|
|
section.SetupBounds();
|
|
}
|
|
}
|
|
|
|
|
|
public void SplitAtEndOf(KaraokeClipSection section)
|
|
{
|
|
int index = Controls.IndexOf(section);
|
|
string leftText = String.Join("", Sections.Where(x => Sections.IndexOf(x) <= index).Select(x => x.Lyrics));
|
|
string rightText = String.Join("", Sections.Where(x => Sections.IndexOf(x) > index).Select(x => x.Lyrics));
|
|
|
|
int startFrame = Model.StartFrame;
|
|
int splitFrame = (int)section.EndTime;
|
|
int stopFrame = Model.StopFrame;
|
|
|
|
bool isEndOfLine = Model.IsEndOfLine;
|
|
|
|
Model.Lyrics = leftText;
|
|
Model.Duration = splitFrame - startFrame;
|
|
Model.IsEndOfLine = false;
|
|
|
|
var newClip = (KaraokeClip)Model.Clone();
|
|
newClip.StartFrame = splitFrame;
|
|
newClip.Duration = stopFrame - splitFrame;
|
|
newClip.Lyrics = rightText;
|
|
newClip.IsEndOfLine = isEndOfLine;
|
|
Model.Tape.Clips.Add(newClip);
|
|
|
|
var clipViewControl = (ClipViewControl)Timeline.RootControl;
|
|
clipViewControl.AddToSelection(newClip);
|
|
}
|
|
|
|
public override void OnDoubleClick()
|
|
{
|
|
//When we double click on a karaoke clip, it will snaps to the next clip
|
|
var nextClip = Model.Tape.Clips.OfType<KaraokeClip>()
|
|
.Where(x => x.ContentType == Model.ContentType)
|
|
.OrderBy(x => x.StartBeat)
|
|
.FirstOrDefault(x => x.StartBeat >= Model.StopBeat);
|
|
if (nextClip != null)
|
|
{
|
|
Model.StopBeat = nextClip.StartBeat;
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
Model.PropertyChanged -= model_PropertyChanged;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
protected override void OnSelectedChanged(EventArgs e)
|
|
{
|
|
base.OnSelectableChanged(e);
|
|
Invalidate(ToleranceBounds);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
void model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "Lyrics" ||
|
|
e.PropertyName == "StartTime" ||
|
|
e.PropertyName == "Duration" ||
|
|
e.PropertyName == "SemitoneTolerance" ||
|
|
e.PropertyName == "StartTimeTolerance" ||
|
|
e.PropertyName == "EndTimeTolerance")
|
|
{
|
|
Invalidate(PreviousToleranceBounds);
|
|
Invalidate(ToleranceBounds);
|
|
InitSections();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
}
|