JD2022-TU1/main/tools/framework/JD.Timeline/Karaoke/KaraokeClipWidget.cs

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
}
}