diff --git a/OpenLyricsClient/Frontend/Models/Custom/NewLyricsScrollerViewModel.cs b/OpenLyricsClient/Frontend/Models/Custom/NewLyricsScrollerViewModel.cs index 010ec1d..0070916 100644 --- a/OpenLyricsClient/Frontend/Models/Custom/NewLyricsScrollerViewModel.cs +++ b/OpenLyricsClient/Frontend/Models/Custom/NewLyricsScrollerViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using Avalonia.Controls; @@ -8,6 +9,7 @@ using OpenLyricsClient.Backend.Settings.Sections.Lyrics; using OpenLyricsClient.Frontend.Models.Pages.Settings; using OpenLyricsClient.Shared.Structure.Lyrics; +using OpenLyricsClient.Shared.Utils; namespace OpenLyricsClient.Frontend.Models.Custom; @@ -49,9 +51,15 @@ private void LyricHandlerOnLyricChanged(object sender, LyricChangedEventArgs lyr Lyric = lyricchangedeventargs.LyricPart; } - public LyricPart[]? Lyrics + public ObservableCollection Lyrics { - get => Core.INSTANCE?.SongHandler?.CurrentSong?.Lyrics?.LyricParts!; + get + { + if (!DataValidator.ValidateData(Core.INSTANCE?.SongHandler?.CurrentSong?.Lyrics?.LyricParts)) + return new ObservableCollection(); + + return new ObservableCollection(Core.INSTANCE?.SongHandler?.CurrentSong?.Lyrics?.LyricParts); + } } public LyricPart? Lyric diff --git a/OpenLyricsClient/Frontend/Models/Custom/Tile/Overlays/TextOverlayViewModel.cs b/OpenLyricsClient/Frontend/Models/Custom/Tile/Overlays/TextOverlayViewModel.cs index 92ca1ac..96d8dc9 100644 --- a/OpenLyricsClient/Frontend/Models/Custom/Tile/Overlays/TextOverlayViewModel.cs +++ b/OpenLyricsClient/Frontend/Models/Custom/Tile/Overlays/TextOverlayViewModel.cs @@ -1,15 +1,27 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; using System.Runtime.CompilerServices; +using System.Windows.Input; using Avalonia; using Avalonia.Layout; using Avalonia.Media; +using DevBase.Extensions; using DevBase.Generics; +using Microsoft.CodeAnalysis; using OpenLyricsClient.Backend; using OpenLyricsClient.Backend.Events.EventArgs; using OpenLyricsClient.Backend.Settings.Sections.Lyrics; using OpenLyricsClient.Frontend.Utils; +using OpenLyricsClient.Shared.Structure.Lyrics; +using OpenLyricsClient.Shared.Utils; +using Org.BouncyCastle.Crypto.Parameters; +using ReactiveUI; +using SharpDX; +using Squalr.Engine.Utils.Extensions; namespace OpenLyricsClient.Frontend.Models.Custom.Tile.Overlays; @@ -17,28 +29,97 @@ public class TextOverlayViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; - private AList _lines; + private ObservableCollection<(Rect, double, string)> _lines; + private LyricPart _lyricPart; + private Typeface _typeface; + public ICommand EffectiveViewportChangedCommand { get; } + public TextOverlayViewModel() { - this._lines = new AList(); + this._lines = new ObservableCollection<(Rect, double, string)>(); + + this._typeface = new Typeface(FontFamily.Parse( + "avares://Material.Styles/Fonts/Roboto#Roboto"), + FontStyle.Normal, this.LyricsWeight); + + this._lyricPart = new LyricPart(0,""); + + EffectiveViewportChangedCommand = ReactiveCommand.Create(OnEffectiveViewportChanged); Core.INSTANCE.SettingsHandler.SettingsChanged += SettingsHandlerOnSettingsChanged; + Core.INSTANCE.LyricHandler.LyricsPercentageUpdated += LyricHandlerOnLyricsPercentageUpdated; + } + + private void LyricHandlerOnLyricsPercentageUpdated(object sender, LyricsPercentageUpdatedEventArgs args) + { + if (!args.LyricPart.Equals(this.LyricPart)) + return; + + UpdatePercentage(this.LyricPart.Part); + OnPropertyChanged("LyricsLines"); } - public void UpdateTextWrappingLines(string text, double width, double height) + private void OnEffectiveViewportChanged(EffectiveViewportChangedEventArgs e) + { + UpdateLyricsWrapping(e.EffectiveViewport.Width, e.EffectiveViewport.Height); + } + + public void UpdateLyricsWrapping(double width, double height) + { + if (!DataValidator.ValidateData(this.LyricPart)) + return; + + UpdateTextWrappingLines(this.LyricPart.Part, width, height); + } + + private void UpdateTextWrappingLines(string text, double width, double height) { AList lines = StringUtils.SplitTextToLines( text, width, height, - new Typeface(FontFamily.Parse( - "avares://Material.Styles/Fonts/Roboto#Roboto"), - FontStyle.Normal, this.LyricsWeight), + this._typeface, this.LyricsAlignment, this.LyricsSize); - SetField(ref this._lines, lines); + ObservableCollection<(Rect, double, string)> sizedLines = new ObservableCollection<(Rect, double, string)>(); + + lines.ForEach(l => + { + sizedLines.Add((MeasureSingleString(l), CalculatePercentage(l, text), l)); + }); + + SetField(ref this._lines, sizedLines); + } + + private void UpdatePercentage(string text) + { + this._lines.ForEach(l => + { + l.Item2 = CalculatePercentage(l.Item3, text); + }); + } + + private double CalculatePercentage(string single, string full) + { + double singleWidth = MeasureSingleString(single).Width; + double fullWidth = MeasureSingleString(full).Width; + + return (fullWidth * 0.01) * singleWidth; + } + + private Rect MeasureSingleString(string line) + { + FormattedText formattedCandidateLine = new FormattedText( + line, + this._typeface, + this.LyricsSize, + this.LyricsAlignment, + TextWrapping.NoWrap, + new Size(double.PositiveInfinity, double.PositiveInfinity)); + + return formattedCandidateLine.Bounds; } private void SettingsHandlerOnSettingsChanged(object sender, SettingsChangedEventArgs settingschangedeventargs) @@ -69,7 +150,17 @@ public Thickness LyricsMargin get => Core.INSTANCE.SettingsHandler.Settings().GetValue("Lyrics Margin"); } - public AList LyricsLines + public LyricPart LyricPart + { + get => this._lyricPart; + set + { + SetField(ref this._lyricPart, value); + UpdateLyricsWrapping(400, 400); + } + } + + public ObservableCollection<(Rect, double, string)> LyricsLines { get => this._lines; } diff --git a/OpenLyricsClient/Frontend/Utils/StringUtils.cs b/OpenLyricsClient/Frontend/Utils/StringUtils.cs index cdd2b7a..78b8415 100644 --- a/OpenLyricsClient/Frontend/Utils/StringUtils.cs +++ b/OpenLyricsClient/Frontend/Utils/StringUtils.cs @@ -30,12 +30,14 @@ public static AList SplitTextToLines(string text, double width, double h candidateLine.Append(word); FormattedText formattedCandidateLine = new FormattedText(candidateLine.ToString(), typeface, fontSize, alignment, textWrapping, constraint); + if (formattedCandidateLine.Bounds.Width <= width) { currentLine = candidateLine; } else { + lines.Add(currentLine.ToString()); currentLine.Clear(); currentLine.Append(word); diff --git a/OpenLyricsClient/Frontend/View/Custom/LyricsScroller.axaml.cs b/OpenLyricsClient/Frontend/View/Custom/LyricsScroller.axaml.cs index f7409ad..7f6fb17 100644 --- a/OpenLyricsClient/Frontend/View/Custom/LyricsScroller.axaml.cs +++ b/OpenLyricsClient/Frontend/View/Custom/LyricsScroller.axaml.cs @@ -469,6 +469,7 @@ private Size GetRenderedSize(int index) foreach (FormattedTextLine line in text.GetLines()) { + lineSize += line.Height; } diff --git a/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml b/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml index 04e3af7..a466881 100644 --- a/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml +++ b/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml @@ -4,6 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:custom="clr-namespace:OpenLyricsClient.Frontend.Models.Custom" xmlns:elements="clr-namespace:OpenLyricsClient.Frontend.Models.Elements" + xmlns:tile="clr-namespace:OpenLyricsClient.Frontend.View.Custom.Tile" + xmlns:lyrics="clr-namespace:OpenLyricsClient.Shared.Structure.Lyrics;assembly=OpenLyricsClient.Shared" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OpenLyricsClient.Frontend.View.Custom.NewLyricsScroller"> @@ -18,14 +20,8 @@ Items="{Binding Lyrics}" IsVisible="False"> - - + + @@ -34,14 +30,8 @@ Items="{Binding Lyrics}" Margin="0,0,0,0"> - - + + diff --git a/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml.cs b/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml.cs index f915aa7..f6bf7de 100644 --- a/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml.cs +++ b/OpenLyricsClient/Frontend/View/Custom/NewLyricsScroller.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -21,6 +22,7 @@ using OpenLyricsClient.Frontend.Models.Custom; using OpenLyricsClient.Frontend.Structure.Enum; using OpenLyricsClient.Frontend.Utils; +using OpenLyricsClient.Frontend.View.Custom.Tile; using Squalr.Engine.Utils.Extensions; namespace OpenLyricsClient.Frontend.View.Custom; @@ -65,6 +67,7 @@ public NewLyricsScroller() this._frameRate = 144; + this.DataContext = new NewLyricsScrollerViewModel(); this._viewModel = this.DataContext as NewLyricsScrollerViewModel; this._hiddenRepeater = this.Get(nameof(HIDDEN_CTRL_Repeater)); @@ -74,7 +77,7 @@ public NewLyricsScroller() this._uiThreadRenderTimer = new UiThreadRenderTimer(144); this._uiThreadRenderTimer.Tick += UiThreadRenderTimerOnTick; - + AttachedToVisualTree += OnAttachedToVisualTree; } @@ -160,7 +163,7 @@ private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArg private async Task CalculateOffset() { - var debounced = Debouncer.Debounce( + var debounced = Debouncer.Debounce, double>( async lyrics => await Dispatcher.UIThread.InvokeAsync(() => { @@ -179,7 +182,7 @@ await Dispatcher.UIThread.InvokeAsync(() => } } - private double GetRenderedOffset(LyricPart lyricPart, LyricPart[] lyricParts) + private double GetRenderedOffset(LyricPart lyricPart, ObservableCollection lyricParts) { if (!DataValidator.ValidateData(lyricPart, lyricParts)) return 0; @@ -201,14 +204,14 @@ private double GetRenderedOffset(LyricPart lyricPart, LyricPart[] lyricParts) return position; } - private int GetIndexOfLyric(LyricPart lyricPart, LyricPart[] lyricParts) => lyricParts.IndexOf(lyricPart); + private int GetIndexOfLyric(LyricPart lyricPart, ObservableCollection lyricParts) => lyricParts.IndexOf(lyricPart); private Size GetRenderedSize(int index) { if (!DataValidator.ValidateData(this._viewModel.Lyrics)) return new Size(); - if (index > this._viewModel.Lyrics.Length || index < 0) + if (index > this._viewModel.Lyrics.Count || index < 0) return new Size(); try @@ -220,6 +223,8 @@ private Size GetRenderedSize(int index) if (itemContainer == null) itemContainer = this._hiddenRepeater.GetOrCreateElement(index); + ((LyricsTile)itemContainer).UpdateViewPort(this.Bounds.Width, this.Bounds.Height); + Size constraint = new Size(this.Bounds.Width, this.Bounds.Height); itemContainer.Measure(constraint); @@ -252,7 +257,7 @@ public float CalcSpeed() float highest = 0; int hSum = 0; - for (int i = 0; i < this._viewModel!.Lyrics.Length; i++) + for (int i = 0; i < this._viewModel!.Lyrics.Count; i++) { LyricPart currentPart = this._viewModel!.Lyrics[i]; @@ -278,7 +283,7 @@ public float CalcSpeed() } } - float speed = (sum / this._viewModel.Lyrics.Length); + float speed = (sum / this._viewModel.Lyrics.Count); float hSA = highest / hSum; diff --git a/OpenLyricsClient/Frontend/View/Custom/Tile/LyricsTile.axaml.cs b/OpenLyricsClient/Frontend/View/Custom/Tile/LyricsTile.axaml.cs index 9a14f9f..387ffa0 100644 --- a/OpenLyricsClient/Frontend/View/Custom/Tile/LyricsTile.axaml.cs +++ b/OpenLyricsClient/Frontend/View/Custom/Tile/LyricsTile.axaml.cs @@ -4,22 +4,47 @@ using Avalonia.Markup.Xaml; using OpenLyricsClient.Backend; using OpenLyricsClient.Backend.Events.EventArgs; +using OpenLyricsClient.Frontend.View.Custom.Tile.Overlays; using OpenLyricsClient.Shared.Structure.Lyrics; namespace OpenLyricsClient.Frontend.View.Custom.Tile; public partial class LyricsTile : UserControl { + public static readonly DirectProperty LyricPartProperty = + AvaloniaProperty.RegisterDirect(nameof(LyricPart), o => o.LyricPart, (o, v) => o.LyricPart = v); + private LyricPart _lyricPart; private Decorator _decorator; - + + private TextOverlay _overlay; + public LyricsTile() { AvaloniaXamlLoader.Load(this); this._decorator = this.Get(nameof(PART_Decorator)); + + this._overlay = new TextOverlay(); Core.INSTANCE.LyricHandler.LyricsPercentageUpdated += LyricHandlerOnLyricsPercentageUpdated; + Core.INSTANCE.LyricHandler.LyricsFound += LyricHandlerOnLyricsFound; + } + + public void UpdateViewPort(double width, double height) + { + this._overlay.UpdateViewPort(width, height); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + } + + private void LyricHandlerOnLyricsFound(object sender) + { + } private void LyricHandlerOnLyricsPercentageUpdated(object sender, LyricsPercentageUpdatedEventArgs args) @@ -27,9 +52,15 @@ private void LyricHandlerOnLyricsPercentageUpdated(object sender, LyricsPercenta } - public LyricPart Lyric + public LyricPart LyricPart { - get => this._lyricPart; - set => this._lyricPart = value; + get { return this._lyricPart; } + set + { + this._overlay.LyricPart = value; + this._decorator.Child = this._overlay; + + SetAndRaise(LyricPartProperty, ref _lyricPart, value); + } } } \ No newline at end of file diff --git a/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml b/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml index 487c629..b5551bc 100644 --- a/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml +++ b/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml @@ -3,15 +3,32 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:overlays="clr-namespace:OpenLyricsClient.Frontend.Models.Custom.Tile.Overlays" + xmlns:i="using:Avalonia.Xaml.Interactivity" + xmlns:ei="using:Avalonia.Xaml.Interactions.Core" xmlns:system="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="OpenLyricsClient.Frontend.View.Custom.Tile.Overlays.TextOverlay"> - + - + + Items="{Binding LyricsLines}"> + + + + + + + + + + diff --git a/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml.cs b/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml.cs index 94358eb..3a5c918 100644 --- a/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml.cs +++ b/OpenLyricsClient/Frontend/View/Custom/Tile/Overlays/TextOverlay.axaml.cs @@ -1,18 +1,47 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using OpenLyricsClient.Frontend.Models.Custom.Tile.Overlays; +using OpenLyricsClient.Shared.Structure.Lyrics; namespace OpenLyricsClient.Frontend.View.Custom.Tile.Overlays; public partial class TextOverlay : UserControl { + public static readonly DirectProperty LyricPartProperty = + AvaloniaProperty.RegisterDirect(nameof(LyricPart), o => o.LyricPart, (o, v) => o.LyricPart = v); + + private LyricPart _lyricPart; + private ItemsControl _itemsControl; + + private TextOverlayViewModel _viewModel; + public TextOverlay() { InitializeComponent(); + + this._viewModel = DataContext as TextOverlayViewModel; + + this._itemsControl = this.Get(nameof(PART_Items)); } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); } + + public void UpdateViewPort(double width, double height) + { + this._viewModel.UpdateLyricsWrapping(width, height); + } + + public LyricPart LyricPart + { + get { return this._lyricPart; } + set + { + SetAndRaise(LyricPartProperty, ref _lyricPart, value); + this._viewModel.LyricPart = value; + } + } } \ No newline at end of file diff --git a/OpenLyricsClient/OpenLyricsClient.csproj b/OpenLyricsClient/OpenLyricsClient.csproj index 7b45ecf..798eb85 100644 --- a/OpenLyricsClient/OpenLyricsClient.csproj +++ b/OpenLyricsClient/OpenLyricsClient.csproj @@ -99,6 +99,7 @@ +