Files
McSchneidar e665e31f4a Improved animations (#907)
* Smoother countdown and new menu and in-game animations

- Countdown digits pop in and fade out each second, with a ring of dots showing the remaining part of the second
- Medley countdown uses a smoothed timer and no longer stutters
- Rating popups scale in from below and drift upwards while fading out
- Player scores pulse briefly at the end of a line
- Pause menu fades and scales in and out
- Buttons and select slides pulse briefly when they get selected
- Shared easing functions and a draw transform for animations

* Retrigger CI
2026-10-17 12:39:18 +02:00

367 lines
10 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;
namespace VocaluxeLib.Menu
{
[XmlType("Button")]
public struct SThemeButton
{
[XmlAttribute(AttributeName = "Name")]
public string Name;
public string Skin;
public string SkinSelected;
public SRectF Rect;
public SThemeColor Color;
public SThemeColor SelColor;
public SThemeText Text;
public SThemeText? SelText;
public SReflection? Reflection;
public SReflection? SelReflection;
}
public sealed class CButton : CMenuElementBase, IMenuElement, IThemeable
{
private SThemeButton _Theme;
private readonly int _PartyModeId;
public CTextureRef Texture;
public CTextureRef SelTexture;
public SColorF Color;
public SColorF SelColor;
public CText Text;
private CText _SelText;
private bool _Reflection;
private float _ReflectionSpace;
private float _ReflectionHeight;
private bool _SelReflection;
private float _SelReflectionSpace;
private float _SelReflectionHeight;
public bool Pressed;
public bool EditMode
{
get { return Text.EditMode; }
set
{
Text.EditMode = value;
if (_SelText != null)
{
_SelText.EditMode = value;
}
}
}
private readonly Stopwatch _SelectTimer = new Stopwatch();
public override bool Selected
{
set
{
if (value && !base.Selected)
{
_SelectTimer.Restart();
}
base.Selected = value;
Text.Selected = value;
}
}
private bool _Selectable = true;
public bool Selectable
{
get { return _Selectable && Visible; }
set
{
_Selectable = value;
if (!_Selectable)
{
Selected = false;
}
}
}
public string GetThemeName()
{
return _Theme.Name;
}
public bool ThemeLoaded { get; private set; }
public CButton(int partyModeId)
{
_PartyModeId = partyModeId;
Text = new CText(_PartyModeId);
_SelText = new CText(_PartyModeId);
Selected = false;
EditMode = false;
}
public CButton(SThemeButton theme, int partyModeId, bool buttonText = false)
{
_PartyModeId = partyModeId;
_Theme = theme;
Text = new CText(_Theme.Text, _PartyModeId, buttonText);
_SelText = _Theme.SelText.HasValue ? new CText(_Theme.SelText.Value, _PartyModeId, buttonText) : null;
Selected = false;
EditMode = false;
ThemeLoaded = true;
}
public CButton(CButton button)
{
_PartyModeId = button._PartyModeId;
_Theme = new SThemeButton
{
Skin = button._Theme.Skin,
SkinSelected = button._Theme.SkinSelected
};
MaxRect = button.MaxRect;
Color = button.Color;
SelColor = button.SelColor;
Texture = button.Texture;
SelTexture = button.SelTexture;
Text = new CText(button.Text);
_SelText = _SelText == null ? null : new CText(button._SelText);
Selected = false;
EditMode = false;
_Selectable = button._Selectable;
_Reflection = button._Reflection;
_ReflectionHeight = button._ReflectionHeight;
_ReflectionSpace = button._ReflectionSpace;
_SelReflection = button._SelReflection;
_SelReflectionHeight = button._SelReflectionHeight;
_SelReflectionSpace = button._SelReflectionSpace;
}
private void _ReadSubThemeElements()
{
_Theme.Text = (SThemeText)Text.GetTheme();
if (_SelText == null)
{
_Theme.SelText = null;
}
else
{
_Theme.SelText = (SThemeText)_SelText.GetTheme();
}
}
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)
{
return;
}
CTextureRef texture;
if (!Selected && !Pressed || !_Selectable)
{
texture = Texture ?? CBase.Themes.GetSkinTexture(_Theme.Skin, _PartyModeId);
CBase.Drawing.DrawTexture(texture, Rect, Color);
if (_Reflection)
{
CBase.Drawing.DrawTextureReflection(texture, Rect, Color, Rect, _ReflectionSpace, _ReflectionHeight);
Text.DrawRelative(Rect.X, Rect.Y, _ReflectionHeight, _ReflectionSpace, Rect.H);
}
else
{
Text.DrawRelative(Rect.X, Rect.Y);
}
}
else if (_SelText == null)
{
texture = SelTexture ?? CBase.Themes.GetSkinTexture(_Theme.SkinSelected, _PartyModeId);
CBase.Drawing.DrawTexture(texture, Rect, SelColor);
if (_Reflection)
{
CBase.Drawing.DrawTextureReflection(texture, Rect, SelColor, Rect, _ReflectionSpace, _ReflectionHeight);
Text.DrawRelative(Rect.X, Rect.Y, _ReflectionHeight, _ReflectionSpace, Rect.H);
}
else
{
Text.DrawRelative(Rect.X, Rect.Y);
}
}
else
{
texture = SelTexture ?? CBase.Themes.GetSkinTexture(_Theme.SkinSelected, _PartyModeId);
CBase.Drawing.DrawTexture(texture, Rect, SelColor);
if (_Reflection)
{
CBase.Drawing.DrawTextureReflection(texture, Rect, SelColor, Rect, _ReflectionSpace, _ReflectionHeight);
_SelText.DrawRelative(Rect.X, Rect.Y, _ReflectionHeight, _ReflectionSpace, Rect.H);
}
else
{
_SelText.DrawRelative(Rect.X, Rect.Y);
}
}
}
public void UnloadSkin()
{
if (!ThemeLoaded)
{
return;
}
Text.UnloadSkin();
}
public void LoadSkin()
{
if (!ThemeLoaded)
{
return;
}
Text = new CText(_Theme.Text, _PartyModeId);
Text.LoadSkin();
Text.Selected = Selected;
if (_Theme.SelText.HasValue)
{
_SelText = new CText(_Theme.SelText.Value, _PartyModeId);
_SelText.LoadSkin();
}
_Theme.Color.Get(_PartyModeId, out Color);
_Theme.SelColor.Get(_PartyModeId, out SelColor);
MaxRect = _Theme.Rect;
_Reflection = _Theme.Reflection.HasValue;
if (_Reflection)
{
Debug.Assert(_Theme.Reflection != null);
_ReflectionHeight = _Theme.Reflection.Value.Height;
_ReflectionSpace = _Theme.Reflection.Value.Space;
}
_SelReflection = _Theme.Reflection.HasValue;
if (_SelReflection)
{
Debug.Assert(_Theme.SelReflection != null);
_SelReflectionHeight = _Theme.SelReflection.Value.Height;
_SelReflectionSpace = _Theme.SelReflection.Value.Space;
}
if (_Theme.Rect.Z < Text.Z)
{
Text.Z = _Theme.Rect.Z;
}
}
public void ReloadSkin()
{
UnloadSkin();
LoadSkin();
}
public object GetTheme()
{
_ReadSubThemeElements();
return _Theme;
}
#region ThemeEdit
public void MoveElement(int stepX, int stepY)
{
X += stepX;
Y += stepY;
_Theme.Rect.X += stepX;
_Theme.Rect.Y += stepY;
}
public void ResizeElement(int stepW, int stepH)
{
W += stepW;
if (W <= 0)
{
W = 1;
}
H += stepH;
if (H <= 0)
{
H = 1;
}
_Theme.Rect.W = Rect.W;
_Theme.Rect.H = Rect.H;
}
#endregion ThemeEdit
}
}