* Redesign song selection screen * Fix SonarCloud issues * Add TileCaption to SConfig test file --------- Co-authored-by: Florian Ostertag <flokuep@users.noreply.github.com>
650 lines
18 KiB
C#
650 lines
18 KiB
C#
#region license
|
|
// This file is part of Vocaluxe.
|
|
//
|
|
// Vocaluxe is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Vocaluxe is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Vocaluxe. If not, see <http://www.gnu.org/licenses/>.
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Xml.Serialization;
|
|
using VocaluxeLib.Draw;
|
|
using VocaluxeLib.PartyModes;
|
|
|
|
namespace VocaluxeLib.Menu.SongMenu
|
|
{
|
|
[XmlType("SongMenu")]
|
|
public struct SThemeSongMenu
|
|
{
|
|
[XmlAttribute(AttributeName = "Name")]
|
|
public string Name;
|
|
|
|
public string CoverBackground;
|
|
public string CoverBigBackground;
|
|
public string DuetIcon;
|
|
public string RapIcon;
|
|
public string VideoIcon;
|
|
public string MedleyCalcIcon;
|
|
public string MedleyTagIcon;
|
|
public string InstrumentalIcon;
|
|
public string VocalsIcon;
|
|
public SThemeColor Color;
|
|
|
|
//public SThemeSongMenuBook songMenuBook;
|
|
//public SThemeSongMenuDreidel songMenuDreidel;
|
|
public SThemeSongMenuList SongMenuList;
|
|
public SThemeSongMenuTileBoard SongMenuTileBoard;
|
|
}
|
|
|
|
public struct SThemeSongMenuList
|
|
{
|
|
/// <summary>
|
|
/// Number of visible songs in list
|
|
/// </summary>
|
|
public int ListLength;
|
|
|
|
/// <summary>
|
|
/// Space between tiles horizontal
|
|
/// </summary>
|
|
public float SpaceW;
|
|
|
|
/// <summary>
|
|
/// Space between tiles vertical
|
|
/// </summary>
|
|
public float SpaceH;
|
|
|
|
public SRectF TileRect;
|
|
public SRectF TileRectSmall;
|
|
|
|
public SThemeText TextArtist;
|
|
public SThemeText TextTitle;
|
|
public SThemeText TextSongLength;
|
|
public SThemeText TextSongYear;
|
|
|
|
public SThemeStatic StaticCoverBig;
|
|
public SThemeStatic StaticTextBG;
|
|
public SThemeStatic StaticDuetIcon;
|
|
public SThemeStatic StaticRapIcon;
|
|
public SThemeStatic StaticInstrumentalIcon;
|
|
public SThemeStatic StaticVocalsIcon;
|
|
public SThemeStatic StaticVideoIcon;
|
|
public SThemeStatic StaticMedleyCalcIcon;
|
|
public SThemeStatic StaticMedleyTagIcon;
|
|
|
|
public SThemeStatic? StaticScrollBar;
|
|
public SThemeStatic? StaticScrollBarThumb;
|
|
}
|
|
|
|
public struct SThemeSongMenuTileBoard
|
|
{
|
|
/// <summary>
|
|
/// Number of tiles horizontal
|
|
/// </summary>
|
|
public int NumW;
|
|
|
|
/// <summary>
|
|
/// Number of tiles vertical
|
|
/// </summary>
|
|
public int NumH;
|
|
|
|
/// <summary>
|
|
/// Number of tiles horizontal in small-modus
|
|
/// </summary>
|
|
public int NumWsmall;
|
|
|
|
/// <summary>
|
|
/// Number of tiles vertical in small-modus
|
|
/// </summary>
|
|
public int NumHsmall;
|
|
|
|
/// <summary>
|
|
/// Space between tiles horizontal
|
|
/// </summary>
|
|
public float SpaceW;
|
|
|
|
/// <summary>
|
|
/// Space between tiles vertical
|
|
/// </summary>
|
|
public float SpaceH;
|
|
|
|
public SRectF TileRect;
|
|
public SRectF TileRectSmall;
|
|
|
|
public SThemeText TextArtist;
|
|
public SThemeText TextTitle;
|
|
public SThemeText TextSongLength;
|
|
public SThemeText TextSongYear;
|
|
|
|
public SThemeStatic StaticCoverBig;
|
|
public SThemeStatic StaticTextBG;
|
|
public SThemeStatic StaticDuetIcon;
|
|
public SThemeStatic StaticRapIcon;
|
|
public SThemeStatic StaticInstrumentalIcon;
|
|
public SThemeStatic StaticVocalsIcon;
|
|
public SThemeStatic StaticVideoIcon;
|
|
public SThemeStatic StaticMedleyCalcIcon;
|
|
public SThemeStatic StaticMedleyTagIcon;
|
|
|
|
public SThemeStatic? StaticScrollBar;
|
|
public SThemeStatic? StaticScrollBarThumb;
|
|
public SThemeStatic? StaticTileCaptionBG;
|
|
public SThemeText? TextTileArtist;
|
|
public SThemeText? TextTileTitle;
|
|
}
|
|
|
|
public abstract class CSongMenuFramework : CMenuElementBase, ISongMenu
|
|
{
|
|
protected readonly int _PartyModeId;
|
|
protected SThemeSongMenu _Theme;
|
|
|
|
public bool ThemeLoaded { get; private set; }
|
|
|
|
public bool Selectable
|
|
{
|
|
get { return Visible; }
|
|
}
|
|
|
|
protected bool _Initialized;
|
|
|
|
private int _PreviewNrInternal = -1;
|
|
protected virtual int _PreviewNr
|
|
{
|
|
get { return _PreviewNrInternal; }
|
|
set
|
|
{
|
|
if (_PreviewNrInternal == value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CBase.Songs.IsInCategory())
|
|
{
|
|
if (value >= CBase.Songs.GetNumSongsVisible())
|
|
{
|
|
value = -1;
|
|
}
|
|
|
|
_PlaySong(value);
|
|
}
|
|
else if (value >= CBase.Songs.GetNumCategories())
|
|
{
|
|
value = -1;
|
|
CBase.BackgroundMusic.SetPlayingPreview(false);
|
|
}
|
|
|
|
_PreviewNrInternal = value;
|
|
}
|
|
}
|
|
|
|
protected int _AutoplayDelayinMs = CBase.Config.GetAutoplayPreviewDelay();
|
|
protected System.Timers.Timer _AutoplayTimer = new System.Timers.Timer();
|
|
protected volatile bool _AutoplayPending = false;
|
|
|
|
private const float _ScrollDuration = 150f;
|
|
private readonly Stopwatch _ScrollTimer = new Stopwatch();
|
|
private float _ScrollStart;
|
|
private int _ScrollTarget;
|
|
|
|
private SColorF _ColorInternal;
|
|
protected SColorF _Color
|
|
{
|
|
get { return _ColorInternal; }
|
|
}
|
|
|
|
public int GetPreviewSong()
|
|
{
|
|
return _PreviewNr;
|
|
}
|
|
|
|
public virtual bool SmallView { get; set; }
|
|
public bool PreviewVisible { get; set; } = true;
|
|
public abstract float SelectedTileZoomFactor { get; }
|
|
// This is the nr of the current selection (song or category)
|
|
protected virtual int _SelectionNr { get; set; }
|
|
|
|
protected CSongMenuFramework(int partyModeId)
|
|
{
|
|
Visible = true;
|
|
_PartyModeId = partyModeId;
|
|
}
|
|
|
|
protected CSongMenuFramework(SThemeSongMenu theme, int partyModeId)
|
|
{
|
|
Visible = true;
|
|
_PartyModeId = partyModeId;
|
|
_Theme = theme;
|
|
|
|
ThemeLoaded = true;
|
|
}
|
|
|
|
#region Theme
|
|
public string GetThemeName()
|
|
{
|
|
return _Theme.Name;
|
|
}
|
|
|
|
public virtual object GetTheme()
|
|
{
|
|
return _Theme;
|
|
}
|
|
#endregion Theme
|
|
|
|
public virtual void Init()
|
|
{
|
|
_ResetPreview(false);
|
|
_InitializeAutoplayTimer();
|
|
_Initialized = true;
|
|
}
|
|
|
|
public virtual void Update(SScreenSongOptions songOptions)
|
|
{
|
|
if (_AutoplayPending)
|
|
{
|
|
_AutoplayPending = false;
|
|
_PreviewSelectedSong();
|
|
}
|
|
}
|
|
|
|
public abstract void OnShow();
|
|
|
|
public virtual void OnHide()
|
|
{
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayPending = false;
|
|
|
|
var check = CBase.Graphics.GetNextScreenType();
|
|
if (CBase.Graphics.GetNextScreenType() == EScreen.Sing)
|
|
{
|
|
_ResetPreview(false);
|
|
}
|
|
else if ((CBase.Graphics.GetNextScreenType() != EScreen.Names && CBase.Graphics.GetNextScreenType() != EScreen.Highscore) ||
|
|
CBase.Config.GetBackgroundMusicStatus() == EBackgroundMusicOffOn.TR_CONFIG_OFF)
|
|
{
|
|
_ResetPreview();
|
|
}
|
|
}
|
|
|
|
public abstract bool HandleInput(ref SKeyEvent keyEvent, SScreenSongOptions options);
|
|
|
|
public abstract bool HandleMouse(ref SMouseEvent mouseEvent, SScreenSongOptions songOptions);
|
|
|
|
public virtual void Draw()
|
|
{
|
|
if (!_Initialized || !Visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CBase.BackgroundMusic.IsPlaying())
|
|
{
|
|
CBase.Drawing.DrawTexture(CBase.BackgroundMusic.GetVideoTexture(), new SRectF(0, 0, 1920, 1080, 0));
|
|
}
|
|
}
|
|
|
|
public virtual bool IsMouseOverSelectedSong(SMouseEvent mEvent)
|
|
{
|
|
var selCov = GetSelectedSongCover();
|
|
return selCov != null && CHelper.IsInBounds(selCov.Rect.Scale(SelectedTileZoomFactor), mEvent);
|
|
}
|
|
|
|
public int GetPreviewSongNr()
|
|
{
|
|
if (CBase.Songs.IsInCategory())
|
|
{
|
|
return _PreviewNr;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public int GetSelectedSongNr()
|
|
{
|
|
if (CBase.Songs.IsInCategory())
|
|
{
|
|
return _SelectionNr;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public abstract CStatic GetSelectedSongCover();
|
|
|
|
public int GetSelectedCategory()
|
|
{
|
|
if (!CBase.Songs.IsInCategory())
|
|
{
|
|
return _SelectionNr;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
public void SetSelectedSong(int visibleSongNr)
|
|
{
|
|
Debug.Assert(CBase.Songs.IsInCategory());
|
|
if (visibleSongNr >= 0 && visibleSongNr < CBase.Songs.GetNumSongsVisible())
|
|
{
|
|
_SelectionNr = visibleSongNr;
|
|
}
|
|
else
|
|
{
|
|
_SelectionNr = -1;
|
|
}
|
|
|
|
_PreviewNr = _SelectionNr;
|
|
}
|
|
|
|
public void SetSelectedCategory(int categoryNr)
|
|
{
|
|
Debug.Assert(!CBase.Songs.IsInCategory());
|
|
if (categoryNr >= 0 && categoryNr < CBase.Songs.GetNumCategories())
|
|
{
|
|
_SelectionNr = categoryNr;
|
|
}
|
|
else
|
|
{
|
|
_SelectionNr = -1;
|
|
}
|
|
|
|
_PreviewNr = _SelectionNr;
|
|
}
|
|
|
|
public void LeaveSelectedCategory()
|
|
{
|
|
if (!CBase.Songs.IsInCategory())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var oldCategory = CBase.Songs.GetCurrentCategoryIndex();
|
|
_LeaveCategory();
|
|
|
|
if (oldCategory >= 0 && oldCategory < CBase.Songs.GetNumCategories())
|
|
{
|
|
SetSelectedCategory(oldCategory);
|
|
}
|
|
else if (CBase.Songs.GetNumCategories() > 0)
|
|
{
|
|
SetSelectedCategory(0);
|
|
}
|
|
}
|
|
|
|
public virtual void UnloadSkin() { }
|
|
|
|
public virtual void LoadSkin()
|
|
{
|
|
Init();
|
|
|
|
_Theme.Color.Get(_PartyModeId, out _ColorInternal);
|
|
}
|
|
|
|
public void ReloadSkin()
|
|
{
|
|
UnloadSkin();
|
|
LoadSkin();
|
|
}
|
|
|
|
public bool EnterSelectedCategory()
|
|
{
|
|
if (!_Initialized)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (CBase.Songs.IsInCategory() || GetSelectedCategory() < 0 || GetSelectedCategory() >= CBase.Songs.GetNumCategories())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_EnterCategory(GetSelectedCategory());
|
|
return true;
|
|
}
|
|
|
|
protected virtual void _EnterCategory(int categoryNr)
|
|
{
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayPending = false;
|
|
|
|
if (!_Initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (categoryNr >= CBase.Songs.GetNumCategories())
|
|
{
|
|
return;
|
|
}
|
|
|
|
_ResetPreview(false);
|
|
CBase.Songs.SetCategory(categoryNr);
|
|
}
|
|
|
|
protected virtual void _LeaveCategory()
|
|
{
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayPending = false;
|
|
|
|
if (!_Initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!CBase.Songs.IsInCategory())
|
|
{
|
|
return;
|
|
}
|
|
|
|
_ResetPreview();
|
|
CBase.Songs.SetCategory(-1);
|
|
}
|
|
|
|
private void _PlaySong(int nr)
|
|
{
|
|
_PreviewNrInternal = -1;
|
|
|
|
var song = CBase.Songs.GetVisibleSong(nr);
|
|
if (song == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBase.BackgroundMusic.LoadPreview(song, song.Preview.StartTime);
|
|
}
|
|
|
|
protected void _ResetPreview(bool playBGagain = true)
|
|
{
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayPending = false;
|
|
|
|
if (_PreviewNrInternal == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CBase.BackgroundMusic.StopPreview();
|
|
CBase.Sound.SetGlobalVolume(CBase.Config.GetMusicVolume(EMusicType.Background));
|
|
if (playBGagain)
|
|
{
|
|
CBase.BackgroundMusic.Play();
|
|
}
|
|
|
|
//Make sure we don't have a preview here otherwise a change won't be recognized
|
|
//(e.g. leave a category with one song and set preview to 0 --> previewOld=previewNew=0 --> No change --> Old data shown
|
|
//Use internal nr because this function gets called from withing setPreviewNr
|
|
_PreviewNrInternal = -1;
|
|
}
|
|
|
|
protected void _PreviewSelectedSong()
|
|
{
|
|
_PreviewNr = _SelectionNr;
|
|
}
|
|
|
|
protected void _InitializeAutoplayTimer()
|
|
{
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayPending = false;
|
|
_AutoplayTimer.Interval = _AutoplayDelayinMs;
|
|
_AutoplayTimer.AutoReset = false;
|
|
_AutoplayTimer.Elapsed -= _OnAutoplayTimerElapsed;
|
|
_AutoplayTimer.Elapsed += _OnAutoplayTimerElapsed;
|
|
}
|
|
|
|
private void _OnAutoplayTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
|
|
{
|
|
_AutoplayPending = true;
|
|
}
|
|
|
|
protected void _AutoplayPreviewIfEnabled()
|
|
{
|
|
if (CBase.Config.GetAutoplayPreviews() == EOffOn.TR_CONFIG_ON)
|
|
{
|
|
_PlayPreviewAfterDelay();
|
|
}
|
|
}
|
|
|
|
protected void _PlayPreviewAfterDelay()
|
|
{
|
|
_AutoplayPending = false;
|
|
_AutoplayTimer.Stop();
|
|
_AutoplayTimer.Start();
|
|
}
|
|
|
|
protected void _SetScrollTarget(int target, bool animate, int maxDistance)
|
|
{
|
|
if (!animate)
|
|
{
|
|
_ScrollTimer.Reset();
|
|
_ScrollTarget = target;
|
|
return;
|
|
}
|
|
|
|
if (target == _ScrollTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var start = _GetScrollPosition();
|
|
_ScrollStart = start.Clamp((float)(target - maxDistance), target + maxDistance);
|
|
_ScrollTarget = target;
|
|
_ScrollTimer.Restart();
|
|
}
|
|
|
|
protected float _GetScrollPosition()
|
|
{
|
|
if (!_ScrollTimer.IsRunning)
|
|
{
|
|
return _ScrollTarget;
|
|
}
|
|
|
|
var progress = _ScrollTimer.ElapsedMilliseconds / _ScrollDuration;
|
|
if (progress >= 1f)
|
|
{
|
|
_ScrollTimer.Reset();
|
|
return _ScrollTarget;
|
|
}
|
|
|
|
var eased = 1f - (1f - progress) * (1f - progress);
|
|
return _ScrollStart + (_ScrollTarget - _ScrollStart) * eased;
|
|
}
|
|
|
|
protected bool _IsScrolling()
|
|
{
|
|
return Math.Abs(_GetScrollPosition() - _ScrollTarget) > 0.001f;
|
|
}
|
|
|
|
protected void _DrawClipped(CTextureRef texture, SRectF rect, SColorF color, EAspect aspect)
|
|
{
|
|
var top = Math.Max(rect.Y, Rect.Y);
|
|
var bottom = Math.Min(rect.Bottom, Rect.Bottom);
|
|
if (bottom <= top)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var bounds = new SRectF(rect.X, top, rect.W, bottom - top, rect.Z);
|
|
if (texture == null)
|
|
{
|
|
CBase.Drawing.DrawRect(color, bounds);
|
|
return;
|
|
}
|
|
|
|
CBase.Drawing.DrawTexture(texture, CHelper.FitInBounds(rect, texture.OrigAspect, aspect), color, bounds);
|
|
}
|
|
|
|
protected float _GetVisibleFraction(SRectF rect)
|
|
{
|
|
if (rect.H <= 0)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
var visible = Math.Min(rect.Bottom, Rect.Bottom) - Math.Max(rect.Y, Rect.Y);
|
|
return (visible / rect.H).Clamp(0f, 1f);
|
|
}
|
|
|
|
protected void _DrawScrollBar(CStatic scrollBar, CStatic scrollBarThumb, SRectF normalRect, float position, int numVisible, int numTotal)
|
|
{
|
|
if (scrollBar == null || numTotal <= numVisible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var barTheme = (SThemeStatic)scrollBar.GetTheme();
|
|
var track = barTheme.Rect;
|
|
track.X += Rect.Right - normalRect.Right;
|
|
track.Y += Rect.Y - normalRect.Y;
|
|
track.H += Rect.H - normalRect.H;
|
|
scrollBar.MaxRect = track;
|
|
scrollBar.Draw(EAspect.Stretch);
|
|
|
|
if (scrollBarThumb == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var thumbTheme = (SThemeStatic)scrollBarThumb.GetTheme();
|
|
var thumbH = Math.Min(track.H, Math.Max(track.H * numVisible / numTotal, thumbTheme.Rect.W * 3f));
|
|
var progress = (position / (numTotal - numVisible)).Clamp(0f, 1f);
|
|
scrollBarThumb.MaxRect = new SRectF(track.X + thumbTheme.Rect.X - barTheme.Rect.X, track.Y + (track.H - thumbH) * progress, thumbTheme.Rect.W, thumbH,
|
|
thumbTheme.Rect.Z);
|
|
scrollBarThumb.Draw(EAspect.Stretch);
|
|
}
|
|
|
|
#region ThemeEdit
|
|
public void MoveElement(int stepX, int stepY)
|
|
{
|
|
X += stepX;
|
|
Y += stepY;
|
|
Init();
|
|
}
|
|
|
|
public void ResizeElement(int stepW, int stepH)
|
|
{
|
|
W += stepW;
|
|
if (W < 100)
|
|
{
|
|
W = 100;
|
|
}
|
|
|
|
H += stepH;
|
|
if (H < 100)
|
|
{
|
|
H = 100;
|
|
}
|
|
|
|
Init();
|
|
}
|
|
#endregion ThemeEdit
|
|
}
|
|
}
|