mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-13 03:34:55 +08:00
1321 lines
48 KiB
C#
1321 lines
48 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using BetterInAppLyrics.WinUI3.ViewModels;
|
|
using BetterLyrics.WinUI3.Enums;
|
|
using BetterLyrics.WinUI3.Events;
|
|
using BetterLyrics.WinUI3.Helper;
|
|
using BetterLyrics.WinUI3.Messages;
|
|
using BetterLyrics.WinUI3.Models;
|
|
using BetterLyrics.WinUI3.Rendering;
|
|
using BetterLyrics.WinUI3.Services.Playback;
|
|
using BetterLyrics.WinUI3.Services.Settings;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
|
using Microsoft.Graphics.Canvas;
|
|
using Microsoft.Graphics.Canvas.Brushes;
|
|
using Microsoft.Graphics.Canvas.Effects;
|
|
using Microsoft.Graphics.Canvas.Text;
|
|
using Microsoft.Graphics.Canvas.UI.Xaml;
|
|
using Microsoft.UI;
|
|
using Microsoft.UI.Dispatching;
|
|
using Microsoft.UI.Text;
|
|
using Microsoft.UI.Xaml;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Microsoft.UI.Xaml.Shapes;
|
|
using Windows.Foundation;
|
|
using Windows.Graphics.Imaging;
|
|
using Windows.UI;
|
|
|
|
namespace BetterLyrics.WinUI3.ViewModels
|
|
{
|
|
public partial class LyricsRendererViewModel
|
|
: BaseRendererViewModel,
|
|
IRecipient<PropertyChangedMessage<int>>,
|
|
IRecipient<PropertyChangedMessage<float>>,
|
|
IRecipient<PropertyChangedMessage<double>>,
|
|
IRecipient<PropertyChangedMessage<bool>>,
|
|
IRecipient<PropertyChangedMessage<Color>>,
|
|
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
|
|
IRecipient<PropertyChangedMessage<LyricsFontColorType>>,
|
|
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
|
|
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
|
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
|
IRecipient<PropertyChangedMessage<LyricsGlowEffectScope>>
|
|
{
|
|
private protected CanvasTextFormat _textFormat = new()
|
|
{
|
|
HorizontalAlignment = CanvasHorizontalAlignment.Left,
|
|
VerticalAlignment = CanvasVerticalAlignment.Top,
|
|
};
|
|
|
|
public LyricsDisplayType DisplayType { get; set; }
|
|
|
|
private float _rotateAngle = 0f;
|
|
private byte[] _shaderByteCode = File.ReadAllBytes(AppInfo.CustomShaderPath);
|
|
|
|
private Color ActivatedWindowAccentColor { get; set; } = Colors.Transparent;
|
|
|
|
private bool IsDockMode { get; set; } = false;
|
|
|
|
[ObservableProperty]
|
|
public partial SongInfo? SongInfo { get; set; }
|
|
|
|
private List<LyricsLine>? _lyricsForGlowEffect = [];
|
|
|
|
private SoftwareBitmap? _lastSoftwareBitmap = null;
|
|
private SoftwareBitmap? _softwareBitmap = null;
|
|
private SoftwareBitmap? SoftwareBitmap
|
|
{
|
|
get => _softwareBitmap;
|
|
set
|
|
{
|
|
if (_softwareBitmap != null)
|
|
{
|
|
_lastSoftwareBitmap = _softwareBitmap;
|
|
_transitionStartTime = DateTimeOffset.Now;
|
|
_isTransitioning = true;
|
|
_transitionAlpha = 0f;
|
|
}
|
|
|
|
_softwareBitmap = value;
|
|
}
|
|
}
|
|
|
|
public int CoverImageRadius { get; set; }
|
|
public bool IsCoverOverlayEnabled { get; set; }
|
|
public bool IsDynamicCoverOverlayEnabled { get; set; }
|
|
public int CoverOverlayOpacity { get; set; }
|
|
public int CoverOverlayBlurAmount { get; set; }
|
|
|
|
[ObservableProperty]
|
|
public partial bool IsPlaying { get; set; }
|
|
|
|
private protected Color _fontColor;
|
|
|
|
private Color _lightFontColor = Colors.White;
|
|
private Color _darkFontColor = Colors.Black;
|
|
|
|
private readonly float _defaultOpacity = 0.3f;
|
|
private readonly float _highlightedOpacity = 1.0f;
|
|
|
|
private readonly float _defaultScale = 0.95f;
|
|
private readonly float _highlightedScale = 1.0f;
|
|
|
|
private readonly int _lineEnteringDurationMs = 800;
|
|
private readonly int _lineExitingDurationMs = 800;
|
|
private readonly int _lineScrollDurationMs = 800;
|
|
|
|
private float _lastTotalYScroll = 0.0f;
|
|
private float _totalYScroll = 0.0f;
|
|
|
|
private int _startVisibleLineIndex = -1;
|
|
private int _endVisibleLineIndex = -1;
|
|
|
|
private bool _forceToScroll = false;
|
|
|
|
private readonly float _lyricsGlowEffectAmount = 6f;
|
|
|
|
private readonly double _rightMargin = 36;
|
|
private readonly float _topMargin = 0f;
|
|
|
|
[ObservableProperty]
|
|
public partial double LimitedLineWidth { get; set; }
|
|
|
|
private protected bool _isRelayoutNeeded = true;
|
|
|
|
[ObservableProperty]
|
|
public partial ElementTheme Theme { get; set; }
|
|
|
|
[ObservableProperty]
|
|
public partial LyricsFontColorType LyricsFontColorType { get; set; }
|
|
|
|
[ObservableProperty]
|
|
public partial LyricsFontWeight LyricsFontWeight { get; set; }
|
|
|
|
public LyricsAlignmentType LyricsAlignmentType { get; set; }
|
|
public int LyricsVerticalEdgeOpacity { get; set; }
|
|
|
|
[ObservableProperty]
|
|
public partial float LyricsLineSpacingFactor { get; set; }
|
|
|
|
[ObservableProperty]
|
|
public partial int LyricsFontSize { get; set; }
|
|
public int LyricsBlurAmount { get; set; }
|
|
public bool IsLyricsGlowEffectEnabled { get; set; }
|
|
public LyricsGlowEffectScope LyricsGlowEffectScope { get; set; }
|
|
|
|
private protected readonly IPlaybackService _playbackService;
|
|
|
|
private float _transitionAlpha = 1f;
|
|
private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000);
|
|
private DateTimeOffset _transitionStartTime;
|
|
private bool _isTransitioning = false;
|
|
|
|
private readonly float _coverRotateSpeed = 0.003f;
|
|
|
|
private Color _currentBgColor;
|
|
private Color _targetBgColor;
|
|
private float _colorTransitionProgress = 1f;
|
|
private const float ColorTransitionDuration = 0.3f; // 秒
|
|
private bool _isColorTransitioning = false;
|
|
|
|
public LyricsRendererViewModel(
|
|
ISettingsService settingsService,
|
|
IPlaybackService playbackService
|
|
)
|
|
: base(settingsService)
|
|
{
|
|
CoverImageRadius = _settingsService.CoverImageRadius;
|
|
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
|
|
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
|
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
|
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
|
|
|
LyricsFontColorType = _settingsService.LyricsFontColorType;
|
|
LyricsFontWeight = _settingsService.LyricsFontWeight;
|
|
LyricsAlignmentType = _settingsService.LyricsAlignmentType;
|
|
LyricsVerticalEdgeOpacity = _settingsService.LyricsVerticalEdgeOpacity;
|
|
LyricsLineSpacingFactor = _settingsService.LyricsLineSpacingFactor;
|
|
LyricsFontSize = _settingsService.LyricsFontSize;
|
|
LyricsBlurAmount = _settingsService.LyricsBlurAmount;
|
|
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
|
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
|
|
|
_playbackService = playbackService;
|
|
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
|
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
|
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
|
|
|
RefreshPlaybackInfo();
|
|
|
|
UpdateFontColor();
|
|
}
|
|
|
|
public void RequestRelayout()
|
|
{
|
|
_isRelayoutNeeded = true;
|
|
}
|
|
|
|
private void PlaybackService_PositionChanged(object? sender, PositionChangedEventArgs e)
|
|
{
|
|
TotalTime = e.Position;
|
|
}
|
|
|
|
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
|
{
|
|
SongInfo = e.SongInfo;
|
|
}
|
|
|
|
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
|
|
{
|
|
IsPlaying = e.IsPlaying;
|
|
}
|
|
|
|
public void RefreshPlaybackInfo()
|
|
{
|
|
IsPlaying = _playbackService.IsPlaying;
|
|
SongInfo = _playbackService.SongInfo;
|
|
TotalTime = _playbackService.Position;
|
|
}
|
|
|
|
partial void OnLimitedLineWidthChanged(double value)
|
|
{
|
|
_isRelayoutNeeded = true;
|
|
}
|
|
|
|
async partial void OnSongInfoChanged(SongInfo? value)
|
|
{
|
|
if (value?.AlbumArt is byte[] bytes)
|
|
SoftwareBitmap = await (
|
|
await ImageHelper.GetDecoderFromByte(bytes)
|
|
).GetSoftwareBitmapAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
|
|
UpdateFontColor();
|
|
_isRelayoutNeeded = true;
|
|
}
|
|
|
|
partial void OnLyricsFontSizeChanged(int value)
|
|
{
|
|
_isRelayoutNeeded = true;
|
|
}
|
|
|
|
partial void OnLyricsFontWeightChanged(LyricsFontWeight value)
|
|
{
|
|
_textFormat.FontWeight = value.ToFontWeight();
|
|
}
|
|
|
|
partial void OnLyricsLineSpacingFactorChanged(float value)
|
|
{
|
|
_isRelayoutNeeded = true;
|
|
}
|
|
|
|
partial void OnLyricsFontColorTypeChanged(LyricsFontColorType value)
|
|
{
|
|
UpdateFontColor();
|
|
}
|
|
|
|
partial void OnThemeChanged(ElementTheme value)
|
|
{
|
|
UpdateFontColor();
|
|
}
|
|
|
|
private protected void UpdateFontColor()
|
|
{
|
|
switch (LyricsFontColorType)
|
|
{
|
|
case LyricsFontColorType.Default:
|
|
switch (Theme)
|
|
{
|
|
case ElementTheme.Default:
|
|
switch (Application.Current.RequestedTheme)
|
|
{
|
|
case ApplicationTheme.Light:
|
|
_fontColor = _darkFontColor;
|
|
break;
|
|
case ApplicationTheme.Dark:
|
|
_fontColor = _lightFontColor;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case ElementTheme.Light:
|
|
_fontColor = _darkFontColor;
|
|
break;
|
|
case ElementTheme.Dark:
|
|
_fontColor = _lightFontColor;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case LyricsFontColorType.Dominant:
|
|
_fontColor = SongInfo?.CoverImageDominantColors?[0] ?? _lightFontColor;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private int GetCurrentPlayingLineIndex()
|
|
{
|
|
for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
|
|
{
|
|
var line = SongInfo?.LyricsLines?[i];
|
|
if (line.EndPlayingTimestampMs < TotalTime.TotalMilliseconds)
|
|
{
|
|
continue;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
private Tuple<int, int> GetVisibleLyricsLineIndexBoundaries()
|
|
{
|
|
// _logger.LogDebug($"{_startVisibleLineIndex} {_endVisibleLineIndex}");
|
|
return new Tuple<int, int>(_startVisibleLineIndex, _endVisibleLineIndex);
|
|
}
|
|
|
|
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
|
{
|
|
if (SongInfo == null || SongInfo.LyricsLines == null || SongInfo.LyricsLines.Count == 0)
|
|
{
|
|
return new Tuple<int, int>(-1, -1);
|
|
}
|
|
|
|
return new Tuple<int, int>(0, SongInfo.LyricsLines.Count - 1);
|
|
}
|
|
|
|
private void DrawLyrics(
|
|
ICanvasAnimatedControl control,
|
|
CanvasDrawingSession ds,
|
|
List<LyricsLine>? source,
|
|
float defaultOpacity,
|
|
LyricsHighlightType currentLineHighlightType
|
|
)
|
|
{
|
|
var (displayStartLineIndex, displayEndLineIndex) =
|
|
GetVisibleLyricsLineIndexBoundaries();
|
|
|
|
var currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
|
|
|
for (
|
|
int i = displayStartLineIndex;
|
|
source?.Count > 0 && i >= 0 && i < source?.Count && i <= displayEndLineIndex;
|
|
i++
|
|
)
|
|
{
|
|
var line = source?[i];
|
|
|
|
using var textLayout = new CanvasTextLayout(
|
|
control,
|
|
line?.Text,
|
|
_textFormat,
|
|
(float)LimitedLineWidth,
|
|
(float)control.Size.Height
|
|
);
|
|
|
|
float progressPerChar = 1f / line.Text.Length;
|
|
|
|
var position = new Vector2(line.Position.X, line.Position.Y);
|
|
|
|
float centerX = position.X;
|
|
float centerY = position.Y + (float)textLayout.LayoutBounds.Height / 2;
|
|
|
|
switch (LyricsAlignmentType)
|
|
{
|
|
case LyricsAlignmentType.Left:
|
|
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
|
|
break;
|
|
case LyricsAlignmentType.Center:
|
|
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
|
|
centerX += (float)LimitedLineWidth / 2;
|
|
break;
|
|
case LyricsAlignmentType.Right:
|
|
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
|
|
centerX += (float)LimitedLineWidth;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
int startIndex = 0;
|
|
|
|
// Set brush
|
|
for (int j = 0; j < textLayout.LineCount; j++)
|
|
{
|
|
int count = textLayout.LineMetrics[j].CharacterCount;
|
|
var regions = textLayout.GetCharacterRegions(startIndex, count);
|
|
float subLinePlayingProgress = Math.Clamp(
|
|
(line.PlayingProgress * line.Text.Length - startIndex) / count,
|
|
0,
|
|
1
|
|
);
|
|
|
|
float startX = (float)(regions[0].LayoutBounds.Left + position.X);
|
|
float endX = (float)(regions[^1].LayoutBounds.Right + position.X);
|
|
|
|
if (currentLineHighlightType == LyricsHighlightType.LineByLine)
|
|
{
|
|
float[] pos =
|
|
[
|
|
0,
|
|
subLinePlayingProgress * (1 + progressPerChar) - progressPerChar,
|
|
subLinePlayingProgress * (1 + progressPerChar),
|
|
1.5f,
|
|
];
|
|
float[] opacity =
|
|
[
|
|
line.Opacity,
|
|
line.Opacity,
|
|
defaultOpacity,
|
|
defaultOpacity,
|
|
];
|
|
|
|
using var brush = GetHorizontalFillBrush(
|
|
control,
|
|
pos,
|
|
opacity,
|
|
startX,
|
|
endX
|
|
);
|
|
textLayout.SetBrush(startIndex, count, brush);
|
|
}
|
|
else if (currentLineHighlightType == LyricsHighlightType.CharByChar)
|
|
{
|
|
float[] pos =
|
|
[
|
|
subLinePlayingProgress * (1 + progressPerChar) - 3 * progressPerChar,
|
|
subLinePlayingProgress * (1 + progressPerChar) - progressPerChar,
|
|
subLinePlayingProgress * (1 + progressPerChar),
|
|
1.5f,
|
|
];
|
|
float[] opacity =
|
|
[
|
|
defaultOpacity,
|
|
line.Opacity,
|
|
defaultOpacity,
|
|
defaultOpacity,
|
|
];
|
|
|
|
using var brush = GetHorizontalFillBrush(
|
|
control,
|
|
pos,
|
|
opacity,
|
|
startX,
|
|
endX
|
|
);
|
|
textLayout.SetBrush(startIndex, count, brush);
|
|
}
|
|
|
|
startIndex += count;
|
|
}
|
|
|
|
// Scale
|
|
ds.Transform =
|
|
Matrix3x2.CreateScale(line.Scale, new Vector2(centerX, centerY))
|
|
* Matrix3x2.CreateTranslation(
|
|
(float)(control.Size.Width - _rightMargin - LimitedLineWidth),
|
|
_totalYScroll + (float)(control.Size.Height / 2)
|
|
);
|
|
|
|
ds.DrawTextLayout(textLayout, position, Colors.Transparent);
|
|
// Reset scale
|
|
ds.Transform = Matrix3x2.Identity;
|
|
}
|
|
}
|
|
|
|
private CanvasLinearGradientBrush GetHorizontalFillBrush(
|
|
ICanvasAnimatedControl control,
|
|
float[] stopPosition,
|
|
float[] stopOpacity,
|
|
float startX,
|
|
float endX
|
|
)
|
|
{
|
|
var r = _fontColor.R;
|
|
var g = _fontColor.G;
|
|
var b = _fontColor.B;
|
|
|
|
return new CanvasLinearGradientBrush(
|
|
control,
|
|
[
|
|
new()
|
|
{
|
|
Position = stopPosition[0],
|
|
Color = Color.FromArgb((byte)(255 * stopOpacity[0]), r, g, b),
|
|
},
|
|
new()
|
|
{
|
|
Position = stopPosition[1],
|
|
Color = Color.FromArgb((byte)(255 * stopOpacity[1]), r, g, b),
|
|
},
|
|
new()
|
|
{
|
|
Position = stopPosition[2],
|
|
Color = Color.FromArgb((byte)(255 * stopOpacity[2]), r, g, b),
|
|
},
|
|
new()
|
|
{
|
|
Position = stopPosition[3],
|
|
Color = Color.FromArgb((byte)(255 * stopOpacity[3]), r, g, b),
|
|
},
|
|
]
|
|
)
|
|
{
|
|
StartPoint = new Vector2(startX, 0),
|
|
EndPoint = new Vector2(endX, 0),
|
|
};
|
|
}
|
|
|
|
private void DrawImmersiveBackground(
|
|
ICanvasAnimatedControl control,
|
|
CanvasDrawingSession ds,
|
|
bool withGradient
|
|
)
|
|
{
|
|
ds.FillRectangle(
|
|
new Rect(0, 0, control.Size.Width, control.Size.Height),
|
|
new CanvasLinearGradientBrush(
|
|
control,
|
|
[
|
|
new CanvasGradientStop
|
|
{
|
|
Position = 0f,
|
|
Color = withGradient
|
|
? Color.FromArgb(
|
|
211,
|
|
_currentBgColor.R,
|
|
_currentBgColor.G,
|
|
_currentBgColor.B
|
|
)
|
|
: _currentBgColor,
|
|
},
|
|
new CanvasGradientStop { Position = 1, Color = _currentBgColor },
|
|
]
|
|
)
|
|
{
|
|
StartPoint = new Vector2(0, 0),
|
|
EndPoint = new Vector2(0, (float)control.Size.Height),
|
|
}
|
|
);
|
|
}
|
|
|
|
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
|
{
|
|
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
|
|
|
|
var overlappedCovers = new CanvasCommandList(control.Device);
|
|
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
|
|
|
|
if (_isTransitioning && _lastSoftwareBitmap != null)
|
|
{
|
|
DrawImgae(control, overlappedCoversDs, _lastSoftwareBitmap, 1 - _transitionAlpha);
|
|
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, _transitionAlpha);
|
|
}
|
|
else
|
|
{
|
|
DrawImgae(control, overlappedCoversDs, SoftwareBitmap, 1);
|
|
}
|
|
|
|
using var coverOverlayEffect = new OpacityEffect
|
|
{
|
|
Opacity = CoverOverlayOpacity / 100f,
|
|
Source = new GaussianBlurEffect
|
|
{
|
|
BlurAmount = CoverOverlayBlurAmount,
|
|
Source = overlappedCovers,
|
|
},
|
|
};
|
|
ds.DrawImage(coverOverlayEffect);
|
|
|
|
ds.Transform = Matrix3x2.Identity;
|
|
}
|
|
|
|
public void Draw(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
|
{
|
|
bool isAlbumArtOverlayDrawn = IsCoverOverlayEnabled && SoftwareBitmap != null;
|
|
if (isAlbumArtOverlayDrawn)
|
|
{
|
|
DrawAlbumArtBackground(control, ds);
|
|
}
|
|
|
|
if (IsDockMode)
|
|
{
|
|
DrawImmersiveBackground(control, ds, isAlbumArtOverlayDrawn);
|
|
}
|
|
|
|
// Original lyrics only layer
|
|
using var lyrics = new CanvasCommandList(control);
|
|
using (var lyricsDs = lyrics.CreateDrawingSession())
|
|
{
|
|
switch (DisplayType)
|
|
{
|
|
case LyricsDisplayType.AlbumArtOnly:
|
|
case LyricsDisplayType.PlaceholderOnly:
|
|
break;
|
|
case LyricsDisplayType.LyricsOnly:
|
|
case LyricsDisplayType.SplitView:
|
|
DrawLyrics(
|
|
control,
|
|
lyricsDs,
|
|
SongInfo?.LyricsLines,
|
|
_defaultOpacity,
|
|
LyricsHighlightType.LineByLine
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Lyrics layer with opacity modification (used for glow effect)
|
|
using var modifiedLyrics = new CanvasCommandList(control);
|
|
using (var modifiedLyricsDs = modifiedLyrics.CreateDrawingSession())
|
|
{
|
|
if (IsLyricsGlowEffectEnabled)
|
|
{
|
|
switch (DisplayType)
|
|
{
|
|
case LyricsDisplayType.AlbumArtOnly:
|
|
case LyricsDisplayType.PlaceholderOnly:
|
|
break;
|
|
case LyricsDisplayType.LyricsOnly:
|
|
case LyricsDisplayType.SplitView:
|
|
switch (LyricsGlowEffectScope)
|
|
{
|
|
case LyricsGlowEffectScope.WholeLyrics:
|
|
modifiedLyricsDs.DrawImage(lyrics);
|
|
break;
|
|
case LyricsGlowEffectScope.CurrentLine:
|
|
DrawLyrics(
|
|
control,
|
|
modifiedLyricsDs,
|
|
_lyricsForGlowEffect,
|
|
0,
|
|
LyricsHighlightType.LineByLine
|
|
);
|
|
break;
|
|
case LyricsGlowEffectScope.CurrentChar:
|
|
DrawLyrics(
|
|
control,
|
|
modifiedLyricsDs,
|
|
_lyricsForGlowEffect,
|
|
0,
|
|
LyricsHighlightType.CharByChar
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
using var glowedLyrics = new CanvasCommandList(control);
|
|
using (var glowedLyricsDs = glowedLyrics.CreateDrawingSession())
|
|
{
|
|
glowedLyricsDs.DrawImage(
|
|
new ShadowEffect
|
|
{
|
|
Source = modifiedLyrics,
|
|
BlurAmount = _lyricsGlowEffectAmount,
|
|
ShadowColor = _fontColor,
|
|
Optimization = EffectOptimization.Quality,
|
|
}
|
|
);
|
|
glowedLyricsDs.DrawImage(lyrics);
|
|
}
|
|
|
|
// Mock gradient blurred lyrics layer
|
|
using var blurredLyrics = new CanvasCommandList(control);
|
|
using var blurredLyricsDs = blurredLyrics.CreateDrawingSession();
|
|
if (LyricsBlurAmount == 0)
|
|
{
|
|
blurredLyricsDs.DrawImage(glowedLyrics);
|
|
}
|
|
else
|
|
{
|
|
double step = 0.05;
|
|
double overlapFactor = 0;
|
|
for (double i = 0; i <= 0.5 - step; i += step)
|
|
{
|
|
using var halfBlurredLyrics = new GaussianBlurEffect
|
|
{
|
|
Source = glowedLyrics,
|
|
BlurAmount = (float)(LyricsBlurAmount * (1 - i / (0.5 - step))),
|
|
Optimization = EffectOptimization.Quality,
|
|
BorderMode = EffectBorderMode.Soft,
|
|
};
|
|
using var topCropped = new CropEffect
|
|
{
|
|
Source = halfBlurredLyrics,
|
|
SourceRectangle = new Rect(
|
|
0,
|
|
control.Size.Height * i,
|
|
control.Size.Width,
|
|
control.Size.Height * step * (1 + overlapFactor)
|
|
),
|
|
};
|
|
using var bottomCropped = new CropEffect
|
|
{
|
|
Source = halfBlurredLyrics,
|
|
SourceRectangle = new Rect(
|
|
0,
|
|
control.Size.Height * (1 - i - step * (1 + overlapFactor)),
|
|
control.Size.Width,
|
|
control.Size.Height * step * (1 + overlapFactor)
|
|
),
|
|
};
|
|
blurredLyricsDs.DrawImage(topCropped);
|
|
blurredLyricsDs.DrawImage(bottomCropped);
|
|
}
|
|
}
|
|
|
|
// Masked mock gradient blurred lyrics layer
|
|
using var maskedBlurredLyrics = new CanvasCommandList(control);
|
|
using (var maskedBlurredLyricsDs = maskedBlurredLyrics.CreateDrawingSession())
|
|
{
|
|
if (LyricsVerticalEdgeOpacity == 100)
|
|
{
|
|
maskedBlurredLyricsDs.DrawImage(blurredLyrics);
|
|
}
|
|
else
|
|
{
|
|
using var mask = new CanvasCommandList(control);
|
|
using (var maskDs = mask.CreateDrawingSession())
|
|
{
|
|
DrawGradientOpacityMask(control, maskDs);
|
|
}
|
|
maskedBlurredLyricsDs.DrawImage(
|
|
new AlphaMaskEffect { Source = blurredLyrics, AlphaMask = mask }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Draw the final composed layer
|
|
ds.DrawImage(maskedBlurredLyrics);
|
|
}
|
|
|
|
private void DrawGradientOpacityMask(
|
|
ICanvasAnimatedControl control,
|
|
CanvasDrawingSession ds
|
|
)
|
|
{
|
|
byte verticalEdgeAlpha = (byte)(255 * LyricsVerticalEdgeOpacity / 100f);
|
|
using var maskBrush = new CanvasLinearGradientBrush(
|
|
control,
|
|
[
|
|
new() { Position = 0, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
|
|
new() { Position = 0.5f, Color = Color.FromArgb(255, 0, 0, 0) },
|
|
new() { Position = 1, Color = Color.FromArgb(verticalEdgeAlpha, 0, 0, 0) },
|
|
]
|
|
)
|
|
{
|
|
StartPoint = new Vector2(0, 0),
|
|
EndPoint = new Vector2(0, (float)control.Size.Height),
|
|
};
|
|
ds.FillRectangle(new Rect(0, 0, control.Size.Width, control.Size.Height), maskBrush);
|
|
}
|
|
|
|
private void ReLayout(ICanvasAnimatedControl control)
|
|
{
|
|
if (control == null)
|
|
return;
|
|
|
|
_textFormat.FontSize = LyricsFontSize;
|
|
|
|
float y = _topMargin;
|
|
|
|
// Init Positions
|
|
for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
|
|
{
|
|
var line = SongInfo?.LyricsLines?[i];
|
|
|
|
// Calculate layout bounds
|
|
using var textLayout = new CanvasTextLayout(
|
|
control,
|
|
line.Text,
|
|
_textFormat,
|
|
(float)LimitedLineWidth,
|
|
(float)control.Size.Height
|
|
);
|
|
line.Position = new Vector2(0, y);
|
|
|
|
y +=
|
|
(float)textLayout.LayoutBounds.Height
|
|
/ textLayout.LineCount
|
|
* (textLayout.LineCount + LyricsLineSpacingFactor);
|
|
}
|
|
}
|
|
|
|
public override void Calculate(
|
|
ICanvasAnimatedControl control,
|
|
CanvasAnimatedUpdateEventArgs args
|
|
)
|
|
{
|
|
base.Calculate(control, args);
|
|
|
|
if (_isColorTransitioning)
|
|
{
|
|
_colorTransitionProgress +=
|
|
(float)ElapsedTime.TotalSeconds / ColorTransitionDuration;
|
|
if (_colorTransitionProgress >= 1f)
|
|
{
|
|
_colorTransitionProgress = 1f;
|
|
_isColorTransitioning = false;
|
|
_currentBgColor = _targetBgColor;
|
|
}
|
|
else
|
|
{
|
|
_currentBgColor = Helper.ColorHelper.GetInterpolatedColor(
|
|
_colorTransitionProgress,
|
|
_currentBgColor,
|
|
_targetBgColor
|
|
);
|
|
}
|
|
}
|
|
|
|
if (_isTransitioning)
|
|
{
|
|
var elapsed = DateTimeOffset.Now - _transitionStartTime;
|
|
float progress = (float)(
|
|
elapsed.TotalMilliseconds / _transitionDuration.TotalMilliseconds
|
|
);
|
|
_transitionAlpha = Math.Clamp(progress, 0f, 1f);
|
|
|
|
if (_transitionAlpha >= 1f)
|
|
{
|
|
_isTransitioning = false;
|
|
_lastSoftwareBitmap?.Dispose();
|
|
_lastSoftwareBitmap = null;
|
|
}
|
|
}
|
|
|
|
if (IsDynamicCoverOverlayEnabled)
|
|
{
|
|
_rotateAngle += _coverRotateSpeed;
|
|
_rotateAngle %= MathF.PI * 2;
|
|
}
|
|
|
|
if (_isRelayoutNeeded)
|
|
{
|
|
ReLayout(control);
|
|
_isRelayoutNeeded = false;
|
|
_forceToScroll = true;
|
|
}
|
|
|
|
int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
|
|
|
CalculateLinesProps(SongInfo?.LyricsLines, currentPlayingLineIndex, _defaultOpacity);
|
|
CalculateCanvasYScrollOffset(control, currentPlayingLineIndex);
|
|
|
|
if (IsLyricsGlowEffectEnabled)
|
|
{
|
|
// Deep copy lyrics lines for glow effect
|
|
_lyricsForGlowEffect = SongInfo?.LyricsLines?.Select(line => line.Clone()).ToList();
|
|
switch (LyricsGlowEffectScope)
|
|
{
|
|
case LyricsGlowEffectScope.WholeLyrics:
|
|
break;
|
|
case LyricsGlowEffectScope.CurrentLine:
|
|
CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
|
|
break;
|
|
case LyricsGlowEffectScope.CurrentChar:
|
|
CalculateLinesProps(_lyricsForGlowEffect, currentPlayingLineIndex, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CalculateLinesProps(
|
|
List<LyricsLine>? source,
|
|
int currentPlayingLineIndex,
|
|
float defaultOpacity
|
|
)
|
|
{
|
|
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
|
|
|
for (int i = startLineIndex; source?.Count > 0 && i <= endLineIndex; i++)
|
|
{
|
|
var line = source?[i];
|
|
|
|
bool linePlaying = i == currentPlayingLineIndex;
|
|
|
|
var lineEnteringDurationMs = Math.Min(line.DurationMs, _lineEnteringDurationMs);
|
|
var lineExitingDurationMs = _lineExitingDurationMs;
|
|
if (i + 1 <= endLineIndex)
|
|
{
|
|
lineExitingDurationMs = Math.Min(
|
|
source?[i + 1].DurationMs ?? 0,
|
|
lineExitingDurationMs
|
|
);
|
|
}
|
|
|
|
float lineEnteringProgress = 0.0f;
|
|
float lineExitingProgress = 0.0f;
|
|
|
|
bool lineEntering = false;
|
|
bool lineExiting = false;
|
|
|
|
float scale = _defaultScale;
|
|
float opacity = defaultOpacity;
|
|
|
|
float playProgress = 0;
|
|
|
|
if (linePlaying)
|
|
{
|
|
line.PlayingState = LyricsPlayingState.Playing;
|
|
|
|
scale = _highlightedScale;
|
|
opacity = _highlightedOpacity;
|
|
|
|
playProgress =
|
|
((float)TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs)
|
|
/ line.DurationMs;
|
|
|
|
var durationFromStartMs =
|
|
TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs;
|
|
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
|
|
if (lineEntering)
|
|
{
|
|
lineEnteringProgress = (float)durationFromStartMs / lineEnteringDurationMs;
|
|
scale =
|
|
_defaultScale
|
|
+ (_highlightedScale - _defaultScale) * (float)lineEnteringProgress;
|
|
opacity =
|
|
defaultOpacity
|
|
+ (_highlightedOpacity - defaultOpacity) * (float)lineEnteringProgress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (i < currentPlayingLineIndex)
|
|
{
|
|
line.PlayingState = LyricsPlayingState.Played;
|
|
playProgress = 1;
|
|
|
|
var durationToEndMs =
|
|
TotalTime.TotalMilliseconds - line.EndPlayingTimestampMs;
|
|
lineExiting = durationToEndMs <= lineExitingDurationMs;
|
|
if (lineExiting)
|
|
{
|
|
lineExitingProgress = (float)durationToEndMs / lineExitingDurationMs;
|
|
scale =
|
|
_highlightedScale
|
|
- (_highlightedScale - _defaultScale) * (float)lineExitingProgress;
|
|
opacity =
|
|
_highlightedOpacity
|
|
- (_highlightedOpacity - defaultOpacity)
|
|
* (float)lineExitingProgress;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
line.PlayingState = LyricsPlayingState.NotPlayed;
|
|
}
|
|
}
|
|
|
|
line.EnteringProgress = lineEnteringProgress;
|
|
line.ExitingProgress = lineExitingProgress;
|
|
|
|
line.Scale = scale;
|
|
line.Opacity = opacity;
|
|
|
|
line.PlayingProgress = playProgress;
|
|
}
|
|
}
|
|
|
|
private void CalculateCanvasYScrollOffset(
|
|
ICanvasAnimatedControl control,
|
|
int currentPlayingLineIndex
|
|
)
|
|
{
|
|
if (currentPlayingLineIndex < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
|
|
|
if (startLineIndex < 0 || endLineIndex < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Set _scrollOffsetY
|
|
LyricsLine? currentPlayingLine = SongInfo?.LyricsLines?[currentPlayingLineIndex];
|
|
|
|
if (currentPlayingLine == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var playingTextLayout = new CanvasTextLayout(
|
|
control,
|
|
currentPlayingLine.Text,
|
|
_textFormat,
|
|
(float)LimitedLineWidth,
|
|
(float)control.Size.Height
|
|
);
|
|
|
|
var lineScrollingProgress =
|
|
(TotalTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
|
|
/ Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
|
|
|
|
float targetYScrollOffset =
|
|
(float?)(
|
|
-currentPlayingLine.Position.Y
|
|
+ SongInfo?.LyricsLines?[0].Position.Y
|
|
- playingTextLayout.LayoutBounds.Height / 2
|
|
- _lastTotalYScroll
|
|
) ?? 0f;
|
|
|
|
var yScrollOffset =
|
|
targetYScrollOffset
|
|
* EasingHelper.SmootherStep((float)Math.Min(1, lineScrollingProgress));
|
|
|
|
bool isScrollingNow = lineScrollingProgress <= 1;
|
|
|
|
if (isScrollingNow)
|
|
{
|
|
_totalYScroll = _lastTotalYScroll + yScrollOffset;
|
|
}
|
|
else
|
|
{
|
|
if (_forceToScroll && Math.Abs(targetYScrollOffset) >= 1)
|
|
{
|
|
_totalYScroll = _lastTotalYScroll + targetYScrollOffset;
|
|
_forceToScroll = false;
|
|
}
|
|
_lastTotalYScroll = _totalYScroll;
|
|
}
|
|
|
|
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
|
|
|
// Update visible line indices
|
|
for (
|
|
int i = startLineIndex;
|
|
i >= 0 && i <= endLineIndex && i < SongInfo?.LyricsLines?.Count;
|
|
i++
|
|
)
|
|
{
|
|
var line = SongInfo?.LyricsLines?[i];
|
|
|
|
using var textLayout = new CanvasTextLayout(
|
|
control,
|
|
line.Text,
|
|
_textFormat,
|
|
(float)LimitedLineWidth,
|
|
(float)control.Size.Height
|
|
);
|
|
|
|
if (
|
|
_totalYScroll
|
|
+ (float)(control.Size.Height / 2)
|
|
+ line.Position.Y
|
|
+ textLayout.LayoutBounds.Height
|
|
>= 0
|
|
)
|
|
{
|
|
if (_startVisibleLineIndex == -1)
|
|
{
|
|
_startVisibleLineIndex = i;
|
|
}
|
|
}
|
|
if (
|
|
_totalYScroll
|
|
+ (float)(control.Size.Height / 2)
|
|
+ line.Position.Y
|
|
+ textLayout.LayoutBounds.Height
|
|
>= control.Size.Height
|
|
)
|
|
{
|
|
if (_endVisibleLineIndex == -1)
|
|
{
|
|
_endVisibleLineIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_startVisibleLineIndex != -1 && _endVisibleLineIndex == -1)
|
|
{
|
|
_endVisibleLineIndex = endLineIndex;
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<ElementTheme> message)
|
|
{
|
|
if (message.Sender is SettingsViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(SettingsViewModel.ThemeType))
|
|
{
|
|
Theme = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DrawImgae(
|
|
ICanvasAnimatedControl control,
|
|
CanvasDrawingSession ds,
|
|
SoftwareBitmap softwareBitmap,
|
|
float opacity
|
|
)
|
|
{
|
|
using var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, softwareBitmap);
|
|
float imageWidth = (float)canvasBitmap.Size.Width;
|
|
float imageHeight = (float)canvasBitmap.Size.Height;
|
|
|
|
var scaleFactor =
|
|
(float)Math.Sqrt(Math.Pow(control.Size.Width, 2) + Math.Pow(control.Size.Height, 2))
|
|
/ Math.Min(imageWidth, imageHeight);
|
|
|
|
ds.DrawImage(
|
|
new OpacityEffect
|
|
{
|
|
Source = new ScaleEffect
|
|
{
|
|
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
|
BorderMode = EffectBorderMode.Hard,
|
|
Scale = new Vector2(scaleFactor),
|
|
Source = canvasBitmap,
|
|
},
|
|
Opacity = opacity,
|
|
},
|
|
(float)control.Size.Width / 2 - imageWidth * scaleFactor / 2,
|
|
(float)control.Size.Height / 2 - imageHeight * scaleFactor / 2
|
|
);
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<bool> message)
|
|
{
|
|
if (message.Sender is SettingsViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(SettingsViewModel.IsDynamicCoverOverlayEnabled))
|
|
{
|
|
IsDynamicCoverOverlayEnabled = message.NewValue;
|
|
}
|
|
else if (message.PropertyName == nameof(SettingsViewModel.IsCoverOverlayEnabled))
|
|
{
|
|
IsCoverOverlayEnabled = message.NewValue;
|
|
}
|
|
}
|
|
else if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.IsLyricsGlowEffectEnabled)
|
|
)
|
|
{
|
|
IsLyricsGlowEffectEnabled = message.NewValue;
|
|
}
|
|
}
|
|
else if (message.Sender is HostWindowViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(HostWindowViewModel.IsDockMode))
|
|
{
|
|
IsDockMode = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<int> message)
|
|
{
|
|
if (message.Sender is SettingsViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
|
|
{
|
|
CoverImageRadius = message.NewValue;
|
|
}
|
|
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayOpacity))
|
|
{
|
|
CoverOverlayOpacity = message.NewValue;
|
|
}
|
|
else if (message.PropertyName == nameof(SettingsViewModel.CoverOverlayBlurAmount))
|
|
{
|
|
CoverOverlayBlurAmount = message.NewValue;
|
|
}
|
|
}
|
|
else if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.LyricsVerticalEdgeOpacity)
|
|
)
|
|
{
|
|
LyricsVerticalEdgeOpacity = message.NewValue;
|
|
}
|
|
else if (
|
|
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsBlurAmount)
|
|
)
|
|
{
|
|
LyricsBlurAmount = message.NewValue;
|
|
}
|
|
else if (
|
|
message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize)
|
|
)
|
|
{
|
|
LyricsFontSize = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
|
|
{
|
|
if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.LyricsFontColorType)
|
|
)
|
|
{
|
|
LyricsFontColorType = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<LyricsAlignmentType> message)
|
|
{
|
|
if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.LyricsAlignmentType)
|
|
)
|
|
{
|
|
LyricsAlignmentType = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<float> message)
|
|
{
|
|
if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.LyricsLineSpacingFactor)
|
|
)
|
|
{
|
|
LyricsLineSpacingFactor = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<double> message)
|
|
{
|
|
if (message.Sender is LyricsPageViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(LyricsPageViewModel.LimitedLineWidth))
|
|
{
|
|
LimitedLineWidth = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<LyricsDisplayType> message)
|
|
{
|
|
DisplayType = message.NewValue;
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
|
|
{
|
|
if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontWeight))
|
|
{
|
|
LyricsFontWeight = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<LyricsGlowEffectScope> message)
|
|
{
|
|
if (message.Sender is LyricsSettingsControlViewModel)
|
|
{
|
|
if (
|
|
message.PropertyName
|
|
== nameof(LyricsSettingsControlViewModel.LyricsGlowEffectScope)
|
|
)
|
|
{
|
|
LyricsGlowEffectScope = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Receive(PropertyChangedMessage<Color> message)
|
|
{
|
|
if (message.Sender is HostWindowViewModel)
|
|
{
|
|
if (message.PropertyName == nameof(HostWindowViewModel.ActivatedWindowAccentColor))
|
|
{
|
|
_currentBgColor = _isColorTransitioning
|
|
? Helper.ColorHelper.GetInterpolatedColor(
|
|
_colorTransitionProgress,
|
|
_currentBgColor,
|
|
_targetBgColor
|
|
)
|
|
: ActivatedWindowAccentColor;
|
|
_targetBgColor = message.NewValue;
|
|
_colorTransitionProgress = 0f;
|
|
_isColorTransitioning = true;
|
|
ActivatedWindowAccentColor = message.NewValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|