diff --git a/Output/Themes/Changelog/Changelog_ScreenSing.txt b/Output/Themes/Changelog/Changelog_ScreenSing.txt index bf9b04e2..5289cf9a 100644 --- a/Output/Themes/Changelog/Changelog_ScreenSing.txt +++ b/Output/Themes/Changelog/Changelog_ScreenSing.txt @@ -1,4 +1,10 @@ ScreenSing ++++++++++ +Version 14 + +Add: + Ring of dots around the countdown (skin ), the countdown is centered in this rect and its size depends on the rect size + +++++++++ Version 13 diff --git a/Output/Themes/Vocaluxe 2024/01_Standard.xml b/Output/Themes/Vocaluxe 2024/01_Standard.xml index 62df8237..1abecbef 100644 --- a/Output/Themes/Vocaluxe 2024/01_Standard.xml +++ b/Output/Themes/Vocaluxe 2024/01_Standard.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/02.xml b/Output/Themes/Vocaluxe 2024/02.xml index 76ada708..9074d9b9 100644 --- a/Output/Themes/Vocaluxe 2024/02.xml +++ b/Output/Themes/Vocaluxe 2024/02.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/03.xml b/Output/Themes/Vocaluxe 2024/03.xml index 3f6ea086..66252f9f 100644 --- a/Output/Themes/Vocaluxe 2024/03.xml +++ b/Output/Themes/Vocaluxe 2024/03.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/04.xml b/Output/Themes/Vocaluxe 2024/04.xml index cd7679c1..702b15cd 100644 --- a/Output/Themes/Vocaluxe 2024/04.xml +++ b/Output/Themes/Vocaluxe 2024/04.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/05.xml b/Output/Themes/Vocaluxe 2024/05.xml index fc53ee7f..3eb48539 100644 --- a/Output/Themes/Vocaluxe 2024/05.xml +++ b/Output/Themes/Vocaluxe 2024/05.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/06.xml b/Output/Themes/Vocaluxe 2024/06.xml index 99f5dca6..8a170637 100644 --- a/Output/Themes/Vocaluxe 2024/06.xml +++ b/Output/Themes/Vocaluxe 2024/06.xml @@ -370,6 +370,7 @@ ToneHelper.png Score.png Star.png + CountdownDot.png NoAvatar.png SongMenu.png PauseBG.png diff --git a/Output/Themes/Vocaluxe 2024/CountdownDot.png b/Output/Themes/Vocaluxe 2024/CountdownDot.png new file mode 100644 index 00000000..9f064dc1 Binary files /dev/null and b/Output/Themes/Vocaluxe 2024/CountdownDot.png differ diff --git a/Output/Themes/Vocaluxe 2024/Screens/ScreenSing.xml b/Output/Themes/Vocaluxe 2024/Screens/ScreenSing.xml index 37bedd77..77b69377 100644 --- a/Output/Themes/Vocaluxe 2024/Screens/ScreenSing.xml +++ b/Output/Themes/Vocaluxe 2024/Screens/ScreenSing.xml @@ -2,7 +2,7 @@ ScreenSing - 13 + 14 @@ -153,6 +153,22 @@ -1 + + CountdownDot + + 1 + 1 + 1 + 0.6 + + + 635 + 215 + 650 + 650 + 0 + + PauseBG diff --git a/Vocaluxe/Base/CDraw.cs b/Vocaluxe/Base/CDraw.cs index 1f831277..4bf3e730 100644 --- a/Vocaluxe/Base/CDraw.cs +++ b/Vocaluxe/Base/CDraw.cs @@ -16,6 +16,7 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; using Vocaluxe.Lib.Draw; using VocaluxeLib; @@ -28,6 +29,64 @@ namespace Vocaluxe.Base { private static IDraw _Draw; + private struct STransform + { + public float Scale; + public float CenterX; + public float CenterY; + public float OffsetY; + public float Alpha; + } + + private static readonly List _Transforms = new List(); + + public static void PushTransform(float scale, float centerX, float centerY, float offsetY, float alpha) + { + _Transforms.Add(new STransform { Scale = scale, CenterX = centerX, CenterY = centerY, OffsetY = offsetY, Alpha = alpha }); + } + + public static void PopTransform() + { + if (_Transforms.Count > 0) + { + _Transforms.RemoveAt(_Transforms.Count - 1); + } + } + + private static SRectF _Transform(SRectF rect) + { + for (var i = _Transforms.Count - 1; i >= 0; i--) + { + var transform = _Transforms[i]; + rect.X = transform.CenterX + (rect.X - transform.CenterX) * transform.Scale; + rect.Y = transform.CenterY + (rect.Y - transform.CenterY) * transform.Scale + transform.OffsetY; + rect.W *= transform.Scale; + rect.H *= transform.Scale; + } + + return rect; + } + + private static SColorF _Transform(SColorF color) + { + foreach (var transform in _Transforms) + { + color.A *= transform.Alpha; + } + + return color; + } + + private static float _TransformScale(float value) + { + foreach (var transform in _Transforms) + { + value *= transform.Scale; + } + + return value; + } + /// /// Initializes the Driver selected in the config /// @@ -295,7 +354,7 @@ namespace Vocaluxe.Base /// Render on all monitors public static void DrawRect(SColorF color, SRectF rect, bool allMonitors = true) { - _Draw.DrawRect(color, rect, allMonitors); + _Draw.DrawRect(_Transform(color), _Transform(rect), allMonitors); } /// @@ -307,7 +366,7 @@ namespace Vocaluxe.Base /// Height of the reflection public static void DrawRectReflection(SColorF color, SRectF rect, float space, float height) { - _Draw.DrawRectReflection(color, rect, space, height); + _Draw.DrawRectReflection(_Transform(color), _Transform(rect), _TransformScale(space), _TransformScale(height)); } /// @@ -339,7 +398,7 @@ namespace Vocaluxe.Base /// Render on all monitors public static void DrawTexture(CTextureRef textureRef, SRectF rect, SColorF color, bool mirrored = false, bool allMonitors = true) { - _Draw.DrawTexture(textureRef, rect, color, mirrored, allMonitors); + _Draw.DrawTexture(textureRef, _Transform(rect), _Transform(color), mirrored, allMonitors); } /// @@ -353,7 +412,7 @@ namespace Vocaluxe.Base /// Render on all monitors public static void DrawTexture(CTextureRef textureRef, SRectF rect, SColorF color, SRectF bounds, bool mirrored = false, bool allMonitors = true) { - _Draw.DrawTexture(textureRef, rect, color, bounds, mirrored, allMonitors); + _Draw.DrawTexture(textureRef, _Transform(rect), _Transform(color), _Transform(bounds), mirrored, allMonitors); } /// @@ -367,7 +426,7 @@ namespace Vocaluxe.Base /// Render on all monitors public static void DrawTexture(CTextureRef textureRef, SRectF rect, SColorF color, float begin, float end, bool allMonitors = true) { - _Draw.DrawTexture(textureRef, rect, color, begin, end, allMonitors); + _Draw.DrawTexture(textureRef, _Transform(rect), _Transform(color), begin, end, allMonitors); } /// @@ -410,7 +469,7 @@ namespace Vocaluxe.Base /// Height of the reflection public static void DrawTextureReflection(CTextureRef textureRef, SRectF rect, SColorF color, SRectF bounds, float space, float height, bool allMonitors = true) { - _Draw.DrawTextureReflection(textureRef, rect, color, bounds, space, height, allMonitors); + _Draw.DrawTextureReflection(textureRef, _Transform(rect), _Transform(color), _Transform(bounds), _TransformScale(space), _TransformScale(height), allMonitors); } } } \ No newline at end of file diff --git a/Vocaluxe/Base/CMain.cs b/Vocaluxe/Base/CMain.cs index 5adc2eef..64dac969 100644 --- a/Vocaluxe/Base/CMain.cs +++ b/Vocaluxe/Base/CMain.cs @@ -462,6 +462,16 @@ namespace Vocaluxe.Base { CDraw.DrawRectReflection(color, rect, space, height); } + + public void PushTransform(float scale, float centerX, float centerY, float offsetY, float alpha) + { + CDraw.PushTransform(scale, centerX, centerY, offsetY, alpha); + } + + public void PopTransform() + { + CDraw.PopTransform(); + } } class CBGraphics : IGraphics diff --git a/Vocaluxe/Screens/CScreenSing.cs b/Vocaluxe/Screens/CScreenSing.cs index d1e3b57c..7894bdae 100644 --- a/Vocaluxe/Screens/CScreenSing.cs +++ b/Vocaluxe/Screens/CScreenSing.cs @@ -39,7 +39,7 @@ namespace Vocaluxe.Screens // Version number for theme files. Increment it, if you've changed something on the theme files! protected override int _ScreenVersion { - get { return 13; } + get { return 14; } } private struct STimeRect @@ -74,6 +74,7 @@ namespace Vocaluxe.Screens private const string _StaticLyricHelper = "StaticLyricHelper"; private const string _StaticLyricHelperDuet = "StaticLyricHelperDuet"; private const string _StaticLyricHelperTop = "StaticLyricHelperTop"; + private const string _StaticCountdownRing = "StaticCountdownRing"; private List _StaticsPause; private string[,,] _StaticScores; @@ -141,9 +142,21 @@ namespace Vocaluxe.Screens private Stopwatch _TimerDuetText1; private Stopwatch _TimerDuetText2; private Stopwatch _TimerStartCountdown; + private Stopwatch _TimerMedleyCountdown; + private float _MedleyCountdownStart; + private float _CountdownProgress = -1f; + + private float[] _ScoreTextHeight; + private float[] _ScoreTextY; + + private readonly Stopwatch _TimerPauseAnimation = new Stopwatch(); + private readonly List _PauseAnimationBackgrounds = new List(); + private readonly List _PauseAnimationElements = new List(); + private bool _PauseAnimationOpening; private bool _StartDelayed; private const float _StartCountdownSeconds = 3f; + private const int _CountdownRingDots = 60; private bool _Pause; private bool _Webcam; @@ -180,7 +193,8 @@ namespace Vocaluxe.Screens _StaticTimePointer, _StaticLyricHelper, _StaticLyricHelperDuet, - _StaticLyricHelperTop + _StaticLyricHelperTop, + _StaticCountdownRing }; _BuildStaticStrings(ref statics); _ThemeStatics = statics.ToArray(); @@ -209,6 +223,10 @@ namespace Vocaluxe.Screens _TimerDuetText1 = new Stopwatch(); _TimerDuetText2 = new Stopwatch(); _TimerStartCountdown = new Stopwatch(); + _TimerMedleyCountdown = new Stopwatch(); + + _ScoreTextHeight = new float[CSettings.MaxNumPlayer]; + _ScoreTextY = new float[CSettings.MaxNumPlayer]; _TimerShortInfoText = new System.Timers.Timer(5000); _TimerShortInfoText.AutoReset = false; @@ -241,6 +259,7 @@ namespace Vocaluxe.Screens _SelectSlides[_SelectSlidePauseVocalsVolume].AddValues(new string[] { "0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100" }); _SelectSlides[_SelectSlidePauseVocalsVolume].Selection = CConfig.VocalsVolume / 5; + _Statics[_StaticCountdownRing].Visible = false; //Automatically find statics and texts with pause prefix foreach (var s in _Statics.Where(s => s.ThemeLoaded && s.GetThemeName().StartsWith("StaticPause"))) @@ -531,7 +550,11 @@ namespace Vocaluxe.Screens _TimeToFirstNoteDuet = 0f; _StartDelayed = false; _TimerStartCountdown.Reset(); + _TimerMedleyCountdown.Reset(); + _CountdownProgress = -1f; + _PrepareCountdownFont(); _SetPause(false); + _TimerPauseAnimation.Reset(); _TimeRects.Clear(); try @@ -556,6 +579,8 @@ namespace Vocaluxe.Screens { _Statics[_PlayerStaticAvatar[p]].Aspect = EAspect.Crop; _Texts[_PlayerTextScore[p]].Color = new SColorF(CBase.Themes.GetPlayerColor(p + 1), _Texts[_PlayerTextScore[p]].Color.A); + _ScoreTextHeight[p] = _Texts[_PlayerTextScore[p]].Font.Height; + _ScoreTextY[p] = _Texts[_PlayerTextScore[p]].Y; } _SetVisibility(); @@ -684,8 +709,7 @@ namespace Vocaluxe.Screens for (var p = 0; p < CGame.NumPlayers; p++) { - var fmtString = CGame.Players[p].Points < 10000 ? "0000" : "00000"; - _Texts[_PlayerTextScore[p]].Text = CGame.Players[p].Points.ToString(fmtString); + _UpdateScoreText(p); } if (_CurrentVideo != null && !_FadeOut && CConfig.Config.Video.VideosInSongs == EOffOn.TR_CONFIG_ON) @@ -815,22 +839,105 @@ namespace Vocaluxe.Screens var timeToFirstMedleyNote = CGame.GetTimeFromBeats(song.Medley.StartBeat, song.Bpm) + song.Gap; if (_CurrentTime < timeToFirstMedleyNote) { - var timeDiff = timeToFirstMedleyNote - _CurrentTime + 1; - var fullSeconds = (float)Math.Truncate(timeDiff); - var partSeconds = timeDiff - fullSeconds; - _Texts[_TextMedleyCountdown].Visible = true; - _Texts[_TextMedleyCountdown].Text = fullSeconds.ToString(); - _Texts[_TextMedleyCountdown].Font.Height = Math.Max(CSettings.RenderH / 8f, partSeconds * CSettings.RenderH); - - var textBounds = CFonts.GetTextBounds(_Texts[_TextMedleyCountdown]); - var x = CSettings.RenderW / 2 - textBounds.Width / 2; - var y = CSettings.RenderH / 2 - textBounds.Height / 2; - _Texts[_TextMedleyCountdown].X = x; - _Texts[_TextMedleyCountdown].Y = y; + _SetCountdown(_GetSmoothedMedleyCountdown(timeToFirstMedleyNote - _CurrentTime)); } else { - _Texts[_TextMedleyCountdown].Visible = false; + _TimerMedleyCountdown.Reset(); + _SetCountdown(0f); + } + } + + private float _GetSmoothedMedleyCountdown(float remaining) + { + if (_Pause) + { + _TimerMedleyCountdown.Reset(); + return remaining; + } + + var predicted = _MedleyCountdownStart - _TimerMedleyCountdown.ElapsedMilliseconds / 1000f; + if (!_TimerMedleyCountdown.IsRunning || Math.Abs(predicted - remaining) > 0.1f) + { + _MedleyCountdownStart = remaining; + _TimerMedleyCountdown.Restart(); + return remaining; + } + + return predicted; + } + + private float _GetCountdownHeight() + { + return Math.Min(_Statics[_StaticCountdownRing].Rect.W, _Statics[_StaticCountdownRing].Rect.H) * 0.62f; + } + + private void _PrepareCountdownFont() + { + var text = _Texts[_TextMedleyCountdown]; + text.Text = "0123456789"; + text.Font.Height = _GetCountdownHeight() * 1.2f; + CFonts.GetTextBounds(text); + text.Text = string.Empty; + text.Visible = false; + } + + private void _SetCountdown(float remaining) + { + var text = _Texts[_TextMedleyCountdown]; + if (remaining <= 0f) + { + text.Visible = false; + _CountdownProgress = -1f; + return; + } + + var fullSeconds = (float)Math.Ceiling(remaining); + var phase = fullSeconds - remaining; + var scale = 0.6f + 0.4f * CHelper.EaseOutBack(Math.Min(1f, phase / 0.35f)); + var alpha = 1f; + if (phase > 0.75f) + { + var fade = (phase - 0.75f) / 0.25f; + alpha = 1f - fade; + scale += fade * 0.2f; + } + + text.Visible = true; + text.Text = fullSeconds.ToString("0"); + text.Font.Height = _GetCountdownHeight() * scale; + text.Alpha = alpha; + + var ring = _Statics[_StaticCountdownRing].Rect; + var textBounds = CFonts.GetTextBounds(text); + text.X = ring.X + ring.W / 2f - textBounds.Width / 2f; + text.Y = ring.Y + ring.H / 2f - textBounds.Height / 2f; + + _CountdownProgress = 1f - phase; + } + + private void _DrawCountdownRing() + { + var ring = _Statics[_StaticCountdownRing]; + if (!_Texts[_TextMedleyCountdown].Visible || _CountdownProgress <= 0f || ring.Texture == null) + { + return; + } + + var rect = ring.Rect; + var radius = Math.Min(rect.W, rect.H) / 2f; + var dotSize = radius * 0.05f; + var centerX = rect.X + rect.W / 2f; + var centerY = rect.Y + rect.H / 2f; + var dots = _CountdownRingDots * _CountdownProgress; + + for (var i = 0; i < _CountdownRingDots && i < dots; i++) + { + var angle = (i + 0.5f) / _CountdownRingDots * 2f * (float)Math.PI; + var x = centerX + radius * (float)Math.Sin(angle) - dotSize / 2f; + var y = centerY - radius * (float)Math.Cos(angle) - dotSize / 2f; + var color = new SColorF(ring.Color.R, ring.Color.G, ring.Color.B, ring.Color.A * Math.Min(1f, dots - i)); + CDraw.DrawTexture(ring.Texture, new SRectF(x, y, dotSize, dotSize, rect.Z), color); } } @@ -846,22 +953,11 @@ namespace Vocaluxe.Screens if (remaining > 0f) { - var fullSeconds = (float)Math.Ceiling(remaining); - var partSeconds = remaining - (float)Math.Floor(remaining); - - _Texts[_TextMedleyCountdown].Visible = true; - _Texts[_TextMedleyCountdown].Text = fullSeconds.ToString("0"); - _Texts[_TextMedleyCountdown].Font.Height = Math.Max(CSettings.RenderH / 8f, partSeconds * CSettings.RenderH); - - var textBounds = CFonts.GetTextBounds(_Texts[_TextMedleyCountdown]); - var x = CSettings.RenderW / 2f - textBounds.Width / 2f; - var y = CSettings.RenderH / 2f - textBounds.Height / 2f; - _Texts[_TextMedleyCountdown].X = x; - _Texts[_TextMedleyCountdown].Y = y; + _SetCountdown(remaining); } else { - _Texts[_TextMedleyCountdown].Visible = false; + _SetCountdown(0f); _Texts[_TextMedleyCountdown].Text = string.Empty; _TimerStartCountdown.Reset(); @@ -1076,24 +1172,57 @@ namespace Vocaluxe.Screens } } + private void _UpdateScoreText(int player) + { + var fmtString = CGame.Players[player].Points < 10000 ? "0000" : "00000"; + var text = _Texts[_PlayerTextScore[player]]; + text.Text = CGame.Players[player].Points.ToString(fmtString); + + var scale = 1f; + var timeSinceLastLine = _CurrentTime - CGame.Players[player].TimeLastLineChange; + if (timeSinceLastLine >= 0f && timeSinceLastLine < 0.5f) + { + scale = 1f + 0.05f * (float)Math.Sin(Math.PI * timeSinceLastLine / 0.5f); + } + + text.Font.Height = _ScoreTextHeight[player] * scale; + text.Y = _ScoreTextY[player] - _ScoreTextHeight[player] * (scale - 1f) / 2f; + } + private void _UpdateRatingPopups() { for (var i = 0; i < CGame.NumPlayers; i++) { - var fadeOutTime = 1f; - var showTime = 0.5f; + var fadeInTime = 0.25f; + var fadeOutTime = 0.5f; + var showTime = 0.9f; + var popup = _RatingPopups[_PlayerRatingPopup[i]]; var timeSinceLastLine = Math.Abs(_CurrentTime - CGame.Players[i].TimeLastLineChange); - if (timeSinceLastLine < showTime) + if (timeSinceLastLine < fadeInTime) { - _RatingPopups[_PlayerRatingPopup[i]].Alpha = 1; + var progress = timeSinceLastLine / fadeInTime; + popup.Alpha = Math.Min(1f, timeSinceLastLine / 0.12f); + popup.Scale = 0.6f + 0.4f * CHelper.EaseOutBack(progress); + popup.OffsetY = (1f - CHelper.EaseOutCubic(progress)) * popup.Rect.H * 0.5f; + } + else if (timeSinceLastLine < showTime) + { + popup.Alpha = 1; + popup.Scale = 1; + popup.OffsetY = 0; } else if (timeSinceLastLine < showTime + fadeOutTime) { - _RatingPopups[_PlayerRatingPopup[i]].Alpha = 1 - (timeSinceLastLine - showTime) / fadeOutTime; + var progress = (timeSinceLastLine - showTime) / fadeOutTime; + popup.Alpha = 1 - progress; + popup.Scale = 1; + popup.OffsetY = -CHelper.EaseOutCubic(progress) * popup.Rect.H * 0.5f; } else { - _RatingPopups[_PlayerRatingPopup[i]].Alpha = 0; + popup.Alpha = 0; + popup.Scale = 1; + popup.OffsetY = 0; } switch (CGame.Players[i].RatingLastLine) @@ -1169,8 +1298,26 @@ namespace Vocaluxe.Screens } } + var pauseAnimation = _GetPauseAnimation(out var pauseAlpha, out var pauseScale); + if (pauseAnimation) + { + _SetPauseAnimationVisibility(false); + } + base.Draw(); + if (pauseAnimation) + { + _SetPauseAnimationVisibility(true); + CDraw.PushTransform(1f, 0f, 0f, 0f, pauseAlpha); + _PauseAnimationBackgrounds.ForEach(element => element.Draw()); + CDraw.PushTransform(pauseScale, CSettings.RenderW / 2f, CSettings.RenderH / 2f, 0f, 1f); + _PauseAnimationElements.ForEach(element => element.Draw()); + CDraw.PopTransform(); + CDraw.PopTransform(); + _SetPauseAnimationVisibility(_PauseAnimationOpening); + } + switch (CConfig.Config.Theme.TimerLook) { case ETimerLook.TR_CONFIG_TIMERLOOK_NORMAL: @@ -1186,6 +1333,7 @@ namespace Vocaluxe.Screens } _DrawLyricHelper(); + _DrawCountdownRing(); } private void _DrawLyricHelper() @@ -1433,8 +1581,76 @@ namespace Vocaluxe.Screens _SetPause(!_Pause); } + private bool _GetPauseAnimation(out float alpha, out float scale) + { + alpha = 1f; + scale = 1f; + if (!_TimerPauseAnimation.IsRunning || CSettings.ProgramState == EProgramState.EditTheme) + { + return false; + } + + var duration = _PauseAnimationOpening ? 250f : 200f; + var progress = _TimerPauseAnimation.ElapsedMilliseconds / duration; + if (progress >= 1f) + { + _TimerPauseAnimation.Stop(); + return false; + } + + if (_PauseAnimationOpening) + { + alpha = Math.Min(1f, progress / 0.6f); + scale = 0.85f + 0.15f * CHelper.EaseOutBack(progress); + } + else + { + alpha = 1f - progress; + scale = 1f - 0.08f * progress; + } + + return true; + } + + private void _SetPauseAnimationVisibility(bool visible) + { + foreach (var element in _PauseAnimationBackgrounds.Concat(_PauseAnimationElements)) + { + element.Visible = visible; + } + } + + private void _StorePauseAnimationElements() + { + _PauseAnimationBackgrounds.Clear(); + _PauseAnimationElements.Clear(); + + _PauseAnimationBackgrounds.AddRange(_StaticsPause.Select(s => (IMenuElement)_Statics[s]).Where(s => s.Visible)); + + var elements = new List + { + _Buttons[_ButtonCancel], + _Buttons[_ButtonContinue], + _Buttons[_ButtonSkip], + _Buttons[_ButtonRestartGame], + _Buttons[_ButtonRestartRound], + _SelectSlides[_SelectSlidePauseVocalsVolume] + }; + elements.AddRange(_SelectSlidesPause.Select(s => (IMenuElement)_SelectSlides[s])); + elements.AddRange(_TextsPause.Select(s => (IMenuElement)_Texts[s])); + _PauseAnimationElements.AddRange(elements.Distinct().Where(e => e.Visible)); + } + private void _SetPause(bool paused) { + var pauseChanged = paused != _Pause; + if (pauseChanged && !paused) + { + _StorePauseAnimationElements(); + _PauseAnimationOpening = false; + _TimerPauseAnimation.Restart(); + } + _Pause = paused; foreach (var s in _StaticsPause) @@ -1461,6 +1677,13 @@ namespace Vocaluxe.Screens _SelectSlides[_SelectSlidePauseVocalsVolume].Visible = _Pause && CGame.AudioMode == EAudioMode.TR_AUDIOMODE_VOCALS; _Texts[_TextPauseVocalsVolume].Visible = _Pause && CGame.AudioMode == EAudioMode.TR_AUDIOMODE_VOCALS; + if (pauseChanged && paused) + { + _StorePauseAnimationElements(); + _PauseAnimationOpening = true; + _TimerPauseAnimation.Restart(); + } + if (_StartDelayed) { if (_Pause) diff --git a/VocaluxeLib/CHelper.cs b/VocaluxeLib/CHelper.cs index b3cc1d48..0fa5c9b2 100644 --- a/VocaluxeLib/CHelper.cs +++ b/VocaluxeLib/CHelper.cs @@ -372,5 +372,18 @@ namespace VocaluxeLib return bmp; } + + public static float EaseOutBack(float t) + { + const float overshoot = 1.70158f; + t -= 1f; + return t * t * ((overshoot + 1f) * t + overshoot) + 1f; + } + + public static float EaseOutCubic(float t) + { + t = 1f - t; + return 1f - t * t * t; + } } } \ No newline at end of file diff --git a/VocaluxeLib/IBasic.cs b/VocaluxeLib/IBasic.cs index 84ef64a4..dc973273 100644 --- a/VocaluxeLib/IBasic.cs +++ b/VocaluxeLib/IBasic.cs @@ -149,6 +149,9 @@ namespace VocaluxeLib void DrawRect(SColorF color, SRectF rect, bool allMonitors = true); void DrawRectReflection(SColorF color, SRectF rect, float space, float height); + + void PushTransform(float scale, float centerX, float centerY, float offsetY, float alpha); + void PopTransform(); } public interface IGraphics diff --git a/VocaluxeLib/Menu/CButton.cs b/VocaluxeLib/Menu/CButton.cs index 16a0fe38..2f3e8313 100644 --- a/VocaluxeLib/Menu/CButton.cs +++ b/VocaluxeLib/Menu/CButton.cs @@ -15,6 +15,7 @@ // along with Vocaluxe. If not, see . #endregion +using System; using System.Diagnostics; using System.Xml.Serialization; using VocaluxeLib.Draw; @@ -74,10 +75,17 @@ namespace VocaluxeLib.Menu } } + private readonly Stopwatch _SelectTimer = new Stopwatch(); + public override bool Selected { set { + if (value && !base.Selected) + { + _SelectTimer.Restart(); + } + base.Selected = value; Text.Selected = value; } @@ -171,6 +179,38 @@ namespace VocaluxeLib.Menu } public void Draw() + { + var scale = _GetSelectionScale(); + if (scale > 1f) + { + CBase.Drawing.PushTransform(scale, Rect.X + Rect.W / 2f, Rect.Y + Rect.H / 2f, 0f, 1f); + _DrawButton(); + CBase.Drawing.PopTransform(); + } + else + { + _DrawButton(); + } + } + + private float _GetSelectionScale() + { + if (!Selected || !_Selectable || !_SelectTimer.IsRunning || CBase.Settings.GetProgramState() == EProgramState.EditTheme) + { + return 1f; + } + + var progress = _SelectTimer.ElapsedMilliseconds / 450f; + if (progress >= 1f) + { + _SelectTimer.Stop(); + return 1f; + } + + return 1f + 0.025f * (float)Math.Sin(Math.PI * progress); + } + + private void _DrawButton() { if (!Visible && CBase.Settings.GetProgramState() != EProgramState.EditTheme) { diff --git a/VocaluxeLib/Menu/CRatingPopup.cs b/VocaluxeLib/Menu/CRatingPopup.cs index c3a23d73..6dc45f95 100644 --- a/VocaluxeLib/Menu/CRatingPopup.cs +++ b/VocaluxeLib/Menu/CRatingPopup.cs @@ -1,4 +1,5 @@ -using System.Xml.Serialization; +using System; +using System.Xml.Serialization; using VocaluxeLib.Draw; namespace VocaluxeLib.Menu @@ -29,6 +30,8 @@ namespace VocaluxeLib.Menu public bool ThemeLoaded { get; private set; } public CText Text; public float Alpha = 1; + public float Scale = 1; + public float OffsetY; public SColorF Color; public CRatingPopup(int partyModeId) @@ -65,12 +68,37 @@ namespace VocaluxeLib.Menu color.A = Alpha; Text.Color.A = Alpha; + var w = Rect.W * Scale; + var h = Rect.H * Scale; + var x = Rect.X + (Rect.W - w) / 2f; + var y = Rect.Y + (Rect.H - h) / 2f + OffsetY; + if (_TextureRatingPopup != null) { - CBase.Drawing.DrawTexture(_TextureRatingPopup, Rect, color, false); + CBase.Drawing.DrawTexture(_TextureRatingPopup, new SRectF(x, y, w, h, Rect.Z), color, false); } - Text.DrawRelative(Rect.X, Rect.Y); + if (Math.Abs(Scale - 1f) < 0.001f) + { + Text.DrawRelative(x, y); + return; + } + + var textX = Text.X; + var textY = Text.Y; + var textW = Text.W; + var fontHeight = Text.Font.Height; + + Text.X = textX * Scale; + Text.Y = textY * Scale; + Text.W = textW * Scale; + Text.Font.Height = fontHeight * Scale; + Text.DrawRelative(x, y); + + Text.X = textX; + Text.Y = textY; + Text.W = textW; + Text.Font.Height = fontHeight; } public object GetTheme() diff --git a/VocaluxeLib/Menu/CSelectSlide.cs b/VocaluxeLib/Menu/CSelectSlide.cs index b97fe66e..303e13b8 100644 --- a/VocaluxeLib/Menu/CSelectSlide.cs +++ b/VocaluxeLib/Menu/CSelectSlide.cs @@ -144,10 +144,17 @@ namespace VocaluxeLib.Menu } } + private readonly Stopwatch _SelectTimer = new Stopwatch(); + public override bool Selected { set { + if (value && !base.Selected) + { + _SelectTimer.Restart(); + } + base.Selected = value; if (!value) @@ -582,6 +589,38 @@ namespace VocaluxeLib.Menu } public void Draw() + { + var scale = _GetSelectionScale(); + if (scale > 1f) + { + CBase.Drawing.PushTransform(scale, Rect.X + Rect.W / 2f, Rect.Y + Rect.H / 2f, 0f, 1f); + _DrawSelectSlide(); + CBase.Drawing.PopTransform(); + } + else + { + _DrawSelectSlide(); + } + } + + private float _GetSelectionScale() + { + if (!Selected || !_SelectTimer.IsRunning || CBase.Settings.GetProgramState() == EProgramState.EditTheme) + { + return 1f; + } + + var progress = _SelectTimer.ElapsedMilliseconds / 450f; + if (progress >= 1f) + { + _SelectTimer.Stop(); + return 1f; + } + + return 1f + 0.025f * (float)Math.Sin(Math.PI * progress); + } + + private void _DrawSelectSlide() { if (!Visible && CBase.Settings.GetProgramState() != EProgramState.EditTheme) {