diff --git a/OngekiFumenEditor/App.config b/OngekiFumenEditor/App.config index 4e3911d4..8fd90805 100644 --- a/OngekiFumenEditor/App.config +++ b/OngekiFumenEditor/App.config @@ -173,6 +173,9 @@ 136, 3, 152 + + -1 + @@ -190,6 +193,9 @@ True + + -1 + \ No newline at end of file diff --git a/OngekiFumenEditor/Kernel/Graphics/IDrawingContext.cs b/OngekiFumenEditor/Kernel/Graphics/IDrawingContext.cs index 9b882535..02c60d16 100644 --- a/OngekiFumenEditor/Kernel/Graphics/IDrawingContext.cs +++ b/OngekiFumenEditor/Kernel/Graphics/IDrawingContext.cs @@ -46,7 +46,7 @@ public VisibleRect(Vector2 buttomRight, Vector2 topLeft) IPerfomenceMonitor PerfomenceMonitor { get; } - void PrepareEditorLoop(GLWpfControl glView); + void PrepareRenderLoop(GLWpfControl glView); void OnRenderSizeChanged(GLWpfControl glView, SizeChangedEventArgs e); void Render(TimeSpan ts); diff --git a/OngekiFumenEditor/Kernel/SettingPages/FumenVisualEditor/Views/FumenVisualEditorGlobalSettingView.xaml b/OngekiFumenEditor/Kernel/SettingPages/FumenVisualEditor/Views/FumenVisualEditorGlobalSettingView.xaml index c71fe90d..c7c35176 100644 --- a/OngekiFumenEditor/Kernel/SettingPages/FumenVisualEditor/Views/FumenVisualEditorGlobalSettingView.xaml +++ b/OngekiFumenEditor/Kernel/SettingPages/FumenVisualEditor/Views/FumenVisualEditorGlobalSettingView.xaml @@ -2,6 +2,7 @@ x:Class="OngekiFumenEditor.Kernel.SettingPages.FumenVisualEditor.Views.FumenVisualEditorGlobalSettingView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors" xmlns:cal="http://caliburnmicro.com" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:markup="clr-namespace:OngekiFumenEditor.UI.Markup" @@ -100,56 +101,91 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Graphics/WaveformDrawing/DefaultImpls/DefaultWaveformDrawing.cs b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Graphics/WaveformDrawing/DefaultImpls/DefaultWaveformDrawing.cs index f451e747..1d30663d 100644 --- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Graphics/WaveformDrawing/DefaultImpls/DefaultWaveformDrawing.cs +++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Graphics/WaveformDrawing/DefaultImpls/DefaultWaveformDrawing.cs @@ -74,7 +74,8 @@ public override void Draw(IWaveformDrawingContext target, PeakPointCollection pe lineDrawing.Begin(target, 1); { var prevX = 0f; - lineDrawing.PostPoint(new(-width / 2, 0), WhiteColor, InvailedLineDash); + + lineDrawing.PostPoint(new(-width / 2, 0), WhiteColor, InvailedLineDash); for (int i = minIndex; i < maxIndex; i++) { var peakPoint = peakData[i]; diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.WaveformDrawing.cs b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.WaveformDrawing.cs index 0117b04a..f9d8c64b 100644 --- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.WaveformDrawing.cs +++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.WaveformDrawing.cs @@ -1,4 +1,5 @@ using Caliburn.Micro; +using ControlzEx.Standard; using OngekiFumenEditor.Kernel.Audio; using OngekiFumenEditor.Kernel.Graphics; using OngekiFumenEditor.Kernel.Graphics.Performence; @@ -11,6 +12,7 @@ using OpenTK.Mathematics; using OpenTK.Wpf; using System; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -19,236 +21,269 @@ namespace OngekiFumenEditor.Modules.AudioPlayerToolViewer.ViewModels { - public partial class AudioPlayerToolViewerViewModel : IWaveformDrawingContext - { - private float viewWidth; - private float viewHeight; - private int renderViewWidth; - private int renderViewHeight; - private IPerfomenceMonitor performenceMonitor; - private ISamplePeak samplePeak; - private CancellationTokenSource loadWaveformTask; - private CancellationTokenSource resampleTaskCancelTokenSource; - private TaskCompletionSource initTask = new TaskCompletionSource(); - - private PeakPointCollection rawPeakData; - private PeakPointCollection usingPeakData; - - public VisibleRect Rect { get; private set; } - - public Matrix4 ProjectionMatrix { get; private set; } - public Matrix4 ViewMatrix { get; private set; } - public Matrix4 ViewProjectionMatrix { get; private set; } - - public IPerfomenceMonitor PerfomenceMonitor => performenceMonitor; - - public TimeSpan CurrentTime { get; private set; } - public TimeSpan AudioTotalDuration => AudioPlayer?.Duration ?? default; - - private IWaveformDrawing waveformDrawing; - public IWaveformDrawing WaveformDrawing - { - get => waveformDrawing; - set - { - Set(ref waveformDrawing, value); - } - } - - private int resampleSize = Properties.AudioPlayerToolViewerSetting.Default.ResampleSize; - public int ResampleSize - { - get => resampleSize; - set - { - Set(ref resampleSize, value); - ResamplePeak(); - Properties.AudioPlayerToolViewerSetting.Default.ResampleSize = value; - Properties.AudioPlayerToolViewerSetting.Default.Save(); - } - } - - private float waveformVecticalScale = Properties.AudioPlayerToolViewerSetting.Default.WaveformVecticalScale; - public float WaveformVecticalScale - { - get => waveformVecticalScale; - set - { - Set(ref waveformVecticalScale, value); - Properties.AudioPlayerToolViewerSetting.Default.WaveformVecticalScale = value; - Properties.AudioPlayerToolViewerSetting.Default.Save(); - } - } - - private float durationMsPerPixel = Properties.AudioPlayerToolViewerSetting.Default.DurationMsPerPixel; - public float DurationMsPerPixel - { - get => durationMsPerPixel; - set - { - Set(ref durationMsPerPixel, value); - Properties.AudioPlayerToolViewerSetting.Default.DurationMsPerPixel = value; - Properties.AudioPlayerToolViewerSetting.Default.Save(); - } - } - - private float currentTimeXOffset = Properties.AudioPlayerToolViewerSetting.Default.CurrentTimeXOffset; - public float CurrentTimeXOffset - { - get => currentTimeXOffset; - set - { - Set(ref currentTimeXOffset, value); - Properties.AudioPlayerToolViewerSetting.Default.CurrentTimeXOffset = value; - Properties.AudioPlayerToolViewerSetting.Default.Save(); - } - } - - private bool isShowWaveform = true; - public bool IsShowWaveform - { - get => isShowWaveform && Properties.AudioPlayerToolViewerSetting.Default.EnableWaveformDisplay; - set => Set(ref isShowWaveform, value); - } - - public FumenVisualEditorViewModel EditorViewModel => Editor; - - private void RecalcViewProjectionMatrix() - { - ProjectionMatrix = - Matrix4.CreateOrthographic(viewWidth, viewHeight, -1, 1); - ViewMatrix = - Matrix4.CreateTranslation(new Vector3(0, 0, 0)); //todo - - ViewProjectionMatrix = ViewMatrix * ProjectionMatrix; - } - - public void OnRenderSizeChanged(GLWpfControl glView, SizeChangedEventArgs sizeArg) - { - Log.LogDebug($"new size: {sizeArg.NewSize} , glView.RenderSize = {glView.RenderSize}"); - var dpiX = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleX; - var dpiY = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleY; - - viewWidth = (float)sizeArg.NewSize.Width; - viewHeight = (float)sizeArg.NewSize.Height; - renderViewWidth = (int)(sizeArg.NewSize.Width * dpiX); - renderViewHeight = (int)(sizeArg.NewSize.Height * dpiY); - - RecalcViewProjectionMatrix(); - } - - public async void PrepareEditorLoop(GLWpfControl glView) - { - Log.LogDebug($"ready."); - await IoC.Get().CheckOrInitGraphics(); - - InitRender(); - var dpiX = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleX; - var dpiY = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleY; - - viewWidth = (float)glView.ActualWidth; - viewHeight = (float)glView.ActualHeight; - renderViewWidth = (int)(glView.ActualWidth * dpiX); - renderViewHeight = (int)(glView.ActualHeight * dpiY); - - RecalcViewProjectionMatrix(); - - //暂时没有需要显示检测的必要? - //performenceMonitor = IoC.Get(); - performenceMonitor = new DummyPerformenceMonitor(); - - glView.Render += Render; - } - - private void InitRender() - { - samplePeak = IoC.Get(); - WaveformDrawing = IoC.Get(); - initTask.SetResult(); - } - - private void PrepareWaveform(IAudioPlayer player) - { - CleanWaveform(); - loadWaveformTask = new CancellationTokenSource(); - var cancelToken = loadWaveformTask.Token; - - Task.Run(() => OnPrepareWaveform(player, cancelToken), cancelToken); - } - - private async void OnPrepareWaveform(IAudioPlayer player, CancellationToken cancelToken) - { - await initTask.Task; - if (cancelToken.IsCancellationRequested || player is null || samplePeak is null) - return; - var sampleData = await player.GetSamplesAsync(); - rawPeakData = sampleData is not null ? samplePeak.GetPeakValues(sampleData) : null; - ResamplePeak(); - } - - private async void ResamplePeak() - { - resampleTaskCancelTokenSource?.Cancel(); - var tokenSource = new CancellationTokenSource(); - resampleTaskCancelTokenSource = tokenSource; - - if (ResampleSize == 0) - usingPeakData = rawPeakData; - else - { - var newPeakData = rawPeakData is null ? default : await rawPeakData?.GenerateSimplfiedAsync(ResampleSize, tokenSource.Token); - if (!tokenSource.IsCancellationRequested) - usingPeakData = newPeakData; - } - } - - private void CleanWaveform() - { - loadWaveformTask?.Cancel(); - loadWaveformTask = null; - rawPeakData = null; - usingPeakData = null; - } - - public void Render(TimeSpan ts) - { + public partial class AudioPlayerToolViewerViewModel : IWaveformDrawingContext + { + private float viewWidth; + private float viewHeight; + private int renderViewWidth; + private int renderViewHeight; + private IPerfomenceMonitor performenceMonitor; + private Stopwatch sw; + private ISamplePeak samplePeak; + private CancellationTokenSource loadWaveformTask; + private CancellationTokenSource resampleTaskCancelTokenSource; + private TaskCompletionSource initTask = new TaskCompletionSource(); + + private PeakPointCollection rawPeakData; + private PeakPointCollection usingPeakData; + + public VisibleRect Rect { get; private set; } + + public Matrix4 ProjectionMatrix { get; private set; } + public Matrix4 ViewMatrix { get; private set; } + public Matrix4 ViewProjectionMatrix { get; private set; } + + public IPerfomenceMonitor PerfomenceMonitor => performenceMonitor; + + public TimeSpan CurrentTime { get; private set; } + public TimeSpan AudioTotalDuration => AudioPlayer?.Duration ?? default; + + private IWaveformDrawing waveformDrawing; + public IWaveformDrawing WaveformDrawing + { + get => waveformDrawing; + set + { + Set(ref waveformDrawing, value); + } + } + + private int resampleSize = Properties.AudioPlayerToolViewerSetting.Default.ResampleSize; + public int ResampleSize + { + get => resampleSize; + set + { + Set(ref resampleSize, value); + ResamplePeak(); + Properties.AudioPlayerToolViewerSetting.Default.ResampleSize = value; + Properties.AudioPlayerToolViewerSetting.Default.Save(); + } + } + + private float waveformVecticalScale = Properties.AudioPlayerToolViewerSetting.Default.WaveformVecticalScale; + public float WaveformVecticalScale + { + get => waveformVecticalScale; + set + { + Set(ref waveformVecticalScale, value); + Properties.AudioPlayerToolViewerSetting.Default.WaveformVecticalScale = value; + Properties.AudioPlayerToolViewerSetting.Default.Save(); + } + } + + private float durationMsPerPixel = Properties.AudioPlayerToolViewerSetting.Default.DurationMsPerPixel; + public float DurationMsPerPixel + { + get => durationMsPerPixel; + set + { + Set(ref durationMsPerPixel, value); + Properties.AudioPlayerToolViewerSetting.Default.DurationMsPerPixel = value; + Properties.AudioPlayerToolViewerSetting.Default.Save(); + } + } + + private float currentTimeXOffset = Properties.AudioPlayerToolViewerSetting.Default.CurrentTimeXOffset; + public float CurrentTimeXOffset + { + get => currentTimeXOffset; + set + { + Set(ref currentTimeXOffset, value); + Properties.AudioPlayerToolViewerSetting.Default.CurrentTimeXOffset = value; + Properties.AudioPlayerToolViewerSetting.Default.Save(); + } + } + + private bool isShowWaveform = true; + public bool IsShowWaveform + { + get => isShowWaveform && Properties.AudioPlayerToolViewerSetting.Default.EnableWaveformDisplay; + set => Set(ref isShowWaveform, value); + } + + private float actualRenderInterval = float.MaxValue; + private int limitFPS = Properties.AudioPlayerToolViewerSetting.Default.LimitFPS; + public int LimitFPS + { + get => limitFPS; + set + { + Set(ref limitFPS, value); + Properties.AudioPlayerToolViewerSetting.Default.CurrentTimeXOffset = value; + Properties.AudioPlayerToolViewerSetting.Default.Save(); + UpdateActualRenderInterval(); + } + } + + public FumenVisualEditorViewModel EditorViewModel => Editor; + + private void UpdateActualRenderInterval() + { + actualRenderInterval = LimitFPS switch + { + <= 0 => float.MaxValue, + _ => 1000.0F / LimitFPS + }; + } + + private void RecalcViewProjectionMatrix() + { + ProjectionMatrix = + Matrix4.CreateOrthographic(viewWidth, viewHeight, -1, 1); + ViewMatrix = + Matrix4.CreateTranslation(new Vector3(0, 0, 0)); //todo + + ViewProjectionMatrix = ViewMatrix * ProjectionMatrix; + } + + public void OnRenderSizeChanged(GLWpfControl glView, SizeChangedEventArgs sizeArg) + { + Log.LogDebug($"new size: {sizeArg.NewSize} , glView.RenderSize = {glView.RenderSize}"); + var dpiX = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleX; + var dpiY = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleY; + + viewWidth = (float)sizeArg.NewSize.Width; + viewHeight = (float)sizeArg.NewSize.Height; + renderViewWidth = (int)(sizeArg.NewSize.Width * dpiX); + renderViewHeight = (int)(sizeArg.NewSize.Height * dpiY); + + RecalcViewProjectionMatrix(); + } + + public async void PrepareRenderLoop(GLWpfControl glView) + { + Log.LogDebug($"ready."); + await IoC.Get().CheckOrInitGraphics(); + + InitRender(); + var dpiX = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleX; + var dpiY = VisualTreeHelper.GetDpi(Application.Current.MainWindow).DpiScaleY; + + viewWidth = (float)glView.ActualWidth; + viewHeight = (float)glView.ActualHeight; + renderViewWidth = (int)(glView.ActualWidth * dpiX); + renderViewHeight = (int)(glView.ActualHeight * dpiY); + + RecalcViewProjectionMatrix(); + + //暂时没有需要显示检测的必要? + //performenceMonitor = IoC.Get(); + performenceMonitor = new DummyPerformenceMonitor(); + + sw = new Stopwatch(); + sw.Start(); + glView.Render += Render; + } + + private void InitRender() + { + samplePeak = IoC.Get(); + WaveformDrawing = IoC.Get(); + initTask.SetResult(); + } + + private void PrepareWaveform(IAudioPlayer player) + { + CleanWaveform(); + loadWaveformTask = new CancellationTokenSource(); + var cancelToken = loadWaveformTask.Token; + + Task.Run(() => OnPrepareWaveform(player, cancelToken), cancelToken); + } + + private async void OnPrepareWaveform(IAudioPlayer player, CancellationToken cancelToken) + { + await initTask.Task; + if (cancelToken.IsCancellationRequested || player is null || samplePeak is null) + return; + var sampleData = await player.GetSamplesAsync(); + rawPeakData = sampleData is not null ? samplePeak.GetPeakValues(sampleData) : null; + ResamplePeak(); + } + + private async void ResamplePeak() + { + resampleTaskCancelTokenSource?.Cancel(); + var tokenSource = new CancellationTokenSource(); + resampleTaskCancelTokenSource = tokenSource; + + if (ResampleSize == 0) + usingPeakData = rawPeakData; + else + { + var newPeakData = rawPeakData is null ? default : await rawPeakData?.GenerateSimplfiedAsync(ResampleSize, tokenSource.Token); + if (!tokenSource.IsCancellationRequested) + usingPeakData = newPeakData; + } + } + + private void CleanWaveform() + { + loadWaveformTask?.Cancel(); + loadWaveformTask = null; + rawPeakData = null; + usingPeakData = null; + } + + public void Render(TimeSpan ts) + { #if DEBUG - GLUtility.CheckError(); + GLUtility.CheckError(); #endif - - GL.ClearColor(16 / 255f, 16 / 255f, 16 / 255f, 1f); - GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - - if (Editor is null || !IsShowWaveform) - return; - - PerfomenceMonitor.PostUIRenderTime(ts); - PerfomenceMonitor.OnBeforeRender(); - - GL.Viewport(0, 0, renderViewWidth, renderViewHeight); - - UpdateDrawingContext(); - - if (usingPeakData is not null) - WaveformDrawing.Draw(this, usingPeakData); - - PerfomenceMonitor.OnAfterRender(); - } - - private void UpdateDrawingContext() - { - Rect = new VisibleRect(new(0 + viewWidth, 0), new(0, 0 + viewHeight)); - - if (AudioPlayer?.IsPlaying ?? false) - CurrentTime = AudioPlayer.CurrentTime; - else - { - var tGrid = Editor?.GetCurrentTGrid(); - if (tGrid is null) - return; - var editorAudioTime = TGridCalculator.ConvertTGridToAudioTime(tGrid, Editor); - CurrentTime = editorAudioTime; - } - } - } + //limit + if (LimitFPS > 0) + { + if (sw.ElapsedMilliseconds < actualRenderInterval) + return; + sw.Restart(); + } + + GL.ClearColor(16 / 255f, 16 / 255f, 16 / 255f, 1f); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + if (Editor is null || !IsShowWaveform) + return; + + PerfomenceMonitor.PostUIRenderTime(ts); + PerfomenceMonitor.OnBeforeRender(); + + GL.Viewport(0, 0, renderViewWidth, renderViewHeight); + + UpdateDrawingContext(); + + if (usingPeakData is not null) + WaveformDrawing.Draw(this, usingPeakData); + + PerfomenceMonitor.OnAfterRender(); + } + + private void UpdateDrawingContext() + { + Rect = new VisibleRect(new(0 + viewWidth, 0), new(0, 0 + viewHeight)); + + if (AudioPlayer?.IsPlaying ?? false) + CurrentTime = AudioPlayer.CurrentTime; + else + { + var tGrid = Editor?.GetCurrentTGrid(); + if (tGrid is null) + return; + var editorAudioTime = TGridCalculator.ConvertTGridToAudioTime(tGrid, Editor); + CurrentTime = editorAudioTime; + } + } + } } diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs index c0f88c70..1e9ba646 100644 --- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs +++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/ViewModels/AudioPlayerToolViewerViewModel.cs @@ -134,8 +134,9 @@ public AudioPlayerToolViewerViewModel() IoC.Get().OnActivateEditorChanged += OnActivateEditorChanged; Editor = IoC.Get().CurrentActivatedEditor; - CompositionTarget.Rendering += CompositionTarget_Rendering; - } + UpdateActualRenderInterval(); + CompositionTarget.Rendering += CompositionTarget_Rendering; + } private void OnActivateEditorChanged(FumenVisualEditorViewModel @new, FumenVisualEditorViewModel old) { @@ -167,7 +168,6 @@ private void CompositionTarget_Rendering(object sender, EventArgs e) Process(AudioPlayer.CurrentTime); } - private void Process(TimeSpan time) { if (Editor is null) diff --git a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml index 40fb1f44..15bfc9e6 100644 --- a/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml +++ b/OngekiFumenEditor/Modules/AudioPlayerToolViewer/Views/AudioPlayerToolViewerView.xaml @@ -2,6 +2,7 @@ x:Class="OngekiFumenEditor.Modules.AudioPlayerToolViewer.Views.AudioPlayerToolViewerView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:Behaviors="http://schemas.microsoft.com/xaml/behaviors" xmlns:cal="http://caliburnmicro.com" xmlns:controls="clr-namespace:OngekiFumenEditor.UI.Controls" xmlns:converters1="clr-namespace:OngekiFumenEditor.Modules.AudioPlayerToolViewer.Converters" @@ -9,9 +10,10 @@ xmlns:gemini="http://schemas.timjones.io/gemini" xmlns:local="clr-namespace:OngekiFumenEditor.Modules.FumenMetaInfoBrowser.Views" xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls" + xmlns:markup="clr-namespace:OngekiFumenEditor.UI.Markup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:objectinspector="clr-namespace:OngekiFumenEditor.UI.Controls.ObjectInspector" - xmlns:res="clr-namespace:OngekiFumenEditor.Properties" xmlns:markup="clr-namespace:OngekiFumenEditor.UI.Markup" + xmlns:res="clr-namespace:OngekiFumenEditor.Properties" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:vm="clr-namespace:OngekiFumenEditor.Modules.AudioPlayerToolViewer.ViewModels" xmlns:wpf="clr-namespace:OpenTK.Wpf;assembly=GLWpfControl" @@ -193,6 +195,39 @@ Foreground="{StaticResource EnvironmentToolWindowText}" Header="{markup:Translate [SwitchControl]}"> + + + + + + + + + + + + + + + + + + displayFPS; @@ -140,7 +145,7 @@ public void OnRenderSizeChanged(GLWpfControl glView, SizeChangedEventArgs sizeAr renderViewHeight = (int)(sizeArg.NewSize.Height * dpiY); } - public async void PrepareEditorLoop(GLWpfControl openGLView) + public async void PrepareRenderLoop(GLWpfControl openGLView) { Log.LogDebug("ready."); await IoC.Get().CheckOrInitGraphics(); @@ -173,6 +178,10 @@ public async void PrepareEditorLoop(GLWpfControl openGLView) actualPerformenceMonitor = IoC.Get(); IsDisplayFPS = IsDisplayFPS; + UpdateActualRenderInterval(); + sw = new Stopwatch(); + sw.Start(); + openGLView.Render += OnEditorLoop; } @@ -186,14 +195,32 @@ private void OnEditorLoop(TimeSpan ts) public void Render(TimeSpan ts) => OnEditorRender(ts); - private void OnEditorRender(TimeSpan ts) + private void UpdateActualRenderInterval() { - PerfomenceMonitor.PostUIRenderTime(ts); - PerfomenceMonitor.OnBeforeRender(); + actualRenderInterval = EditorGlobalSetting.Default.LimitFPS switch + { + <= 0 => 0, + _ => 1000.0F / EditorGlobalSetting.Default.LimitFPS + }; + } + private void OnEditorRender(TimeSpan ts) + { #if DEBUG GLUtility.CheckError(); #endif + //limit + if (actualRenderInterval > 0) + { + var ms = sw.ElapsedMilliseconds; + if (ms < actualRenderInterval) + goto End; + ts = TimeSpan.FromMilliseconds(ms); + sw.Restart(); + } + + PerfomenceMonitor.PostUIRenderTime(ts); + PerfomenceMonitor.OnBeforeRender(); CleanRender(); GL.Viewport(0, 0, renderViewWidth, renderViewHeight); @@ -202,7 +229,7 @@ private void OnEditorRender(TimeSpan ts) var fumen = Fumen; if (fumen is null) - return; + goto End; var tGrid = GetCurrentTGrid(); @@ -218,7 +245,7 @@ private void OnEditorRender(TimeSpan ts) var maxTGrid = TGridCalculator.ConvertYToTGrid_DesignMode(maxY, this); if (maxTGrid is null || minTGrid is null) - return; + goto End; visibleTGridRanges.Add((minTGrid, maxTGrid)); } else @@ -231,7 +258,7 @@ private void OnEditorRender(TimeSpan ts) foreach (var x in ranges) { if (x.maxTGrid is null || x.minTGrid is null) - return; + goto End; visibleTGridRanges.Add((x.minTGrid, x.maxTGrid)); } } @@ -312,13 +339,13 @@ private void OnEditorRender(TimeSpan ts) playerLocationHelper.Draw(this); selectingRangeHelper.Draw(this); - //clean up - drawMap.Clear(); foreach (var list in map.Values) ObjectPool>.Return(list); ObjectPool>>.Return(map); + End: + drawMap.Clear(); PerfomenceMonitor.OnAfterRender(); } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs index d59524db..bc4d0d04 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.UserInteractionActions.cs @@ -753,6 +753,27 @@ public void KeyboardAction_FastSetObjectIsCritical(ActionExecutionContext e) })); } + public void KeyboardAction_FastSwitchFlickDirection(ActionExecutionContext e) + { + var selectedFlicks = SelectObjects.OfType().ToList(); + + if (selectedFlicks.Count == 0) + { + ToastNotify(Resources.NoFlickCouldBeSwitched); + return; + } + + UndoRedoManager.ExecuteAction(LambdaUndoAction.Create(Resources.BatchSwitchFlickDirection, ChangeFlicks, ChangeFlicks)); + + void ChangeFlicks() + { + foreach (var flick in selectedFlicks) + flick.Direction = flick.Direction == Flick.FlickDirection.Left + ? Flick.FlickDirection.Right + : Flick.FlickDirection.Left; + } + } + public bool CheckAndNotifyIfPlaceBeyondDuration(Point placePoint) { if (placePoint.Y > TotalDurationHeight || placePoint.Y < 0) diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs index 3c1e32cc..dd8c0e98 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/ViewModels/FumenVisualEditorViewModel.cs @@ -105,6 +105,9 @@ private void OnSettingPropertyChanged(object sender, PropertyChangedEventArgs e) enableShowPlayerLocation = EditorGlobalSetting.Default.EnableShowPlayerLocation; PlayerLocationRecorder.Clear(); break; + case nameof(EditorGlobalSetting.LimitFPS): + UpdateActualRenderInterval(); + break; case nameof(EditorGlobalSetting.XGridUnitSpace): case nameof(EditorGlobalSetting.DisplayTimeFormat): case nameof(EditorGlobalSetting.BeatSplit): @@ -177,7 +180,7 @@ public FumenVisualEditorViewModel() : base() //replace owned impl UndoRedoManager = new DefaultEditorUndoManager(this); - Properties.EditorGlobalSetting.Default.PropertyChanged += OnSettingPropertyChanged; + EditorGlobalSetting.Default.PropertyChanged += OnSettingPropertyChanged; DisplayName = default; } diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml index 1ddd594a..eac012d7 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml @@ -16,7 +16,7 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:valueconverters="clr-namespace:OngekiFumenEditor.UI.ValueConverters" xmlns:viewmodels="clr-namespace:OngekiFumenEditor.Modules.FumenVisualEditor.ViewModels" - cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; " + cal:Message.Attach="[Key OemTilde] = [Action KeyboardAction_FastPlaceDockableObjectToWallLeft]; [Key D4] = [Action KeyboardAction_FastPlaceDockableObjectToWallRight]; [Key D3] = [Action KeyboardAction_FastPlaceDockableObjectToRight]; [Key D2] = [Action KeyboardAction_FastPlaceDockableObjectToCenter]; [Key D1] = [Action KeyboardAction_FastPlaceDockableObjectToLeft]; [Event DragEnter] = [Action Grid_DragEnter($executionContext)]; [Event Drop] = [Action Grid_Drop($executionContext)]; [Event FocusableChanged] = [Action OnFocusableChanged($executionContext)]; [Key Delete] = [Action KeyboardAction_DeleteSelectingObjects]; [Gesture Ctrl+A] = [Action KeyboardAction_SelectAllObjects]; [Key Escape] = [Action KeyboardAction_CancelSelectingObjects]; [Key C] = [Action KeyboardAction_FastSetObjectIsCritical($executionContext)]; [Key Q] = [Action KeyboardAction_HideOrShow];[Key A] = [Action KeyboardAction_FastAddConnectableChild($executionContext)]; [Key F] = [Action KeyboardAction_FastSwitchFlickDirection($executionContext)]; [Gesture Ctrl+C]=[Action MenuItemAction_CopySelectedObjects]; [Gesture Ctrl+V]=[Action MenuItemAction_PasteCopiesObjects]; " d:DataContext="{d:DesignInstance Type=viewmodels:FumenVisualEditorViewModel}" d:DesignHeight="450" d:DesignWidth="800" diff --git a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml.cs b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml.cs index 6e35c7cc..ca558966 100644 --- a/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml.cs +++ b/OngekiFumenEditor/Modules/FumenVisualEditor/Views/FumenVisualEditorView.xaml.cs @@ -23,7 +23,7 @@ private void glView_Ready() { if (DataContext is IDrawingContext fumenPreviewer) { - fumenPreviewer.PrepareEditorLoop(glView); + fumenPreviewer.PrepareRenderLoop(glView); } }); } diff --git a/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.Designer.cs b/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.Designer.cs index b8eba317..1636fc46 100644 --- a/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.Designer.cs +++ b/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.Designer.cs @@ -12,7 +12,7 @@ namespace OngekiFumenEditor.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.11.0.0")] public sealed partial class AudioPlayerToolViewerSetting : global::System.Configuration.ApplicationSettingsBase { private static AudioPlayerToolViewerSetting defaultInstance = ((AudioPlayerToolViewerSetting)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new AudioPlayerToolViewerSetting()))); @@ -82,5 +82,17 @@ public bool EnableWaveformDisplay { this["EnableWaveformDisplay"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public int LimitFPS { + get { + return ((int)(this["LimitFPS"])); + } + set { + this["LimitFPS"] = value; + } + } } } diff --git a/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.settings b/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.settings index b4ebd8f9..838037c4 100644 --- a/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.settings +++ b/OngekiFumenEditor/Properties/AudioPlayerToolViewerSetting.settings @@ -17,5 +17,8 @@ True + + -1 + \ No newline at end of file diff --git a/OngekiFumenEditor/Properties/EditorGlobalSetting.Designer.cs b/OngekiFumenEditor/Properties/EditorGlobalSetting.Designer.cs index 7c4f40d5..611533a7 100644 --- a/OngekiFumenEditor/Properties/EditorGlobalSetting.Designer.cs +++ b/OngekiFumenEditor/Properties/EditorGlobalSetting.Designer.cs @@ -454,5 +454,17 @@ public bool EnableShowPlayerLocation { this["ColorHoldWallLeft"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("-1")] + public int LimitFPS { + get { + return ((int)(this["LimitFPS"])); + } + set { + this["LimitFPS"] = value; + } + } } } diff --git a/OngekiFumenEditor/Properties/EditorGlobalSetting.settings b/OngekiFumenEditor/Properties/EditorGlobalSetting.settings index 08848239..9290be03 100644 --- a/OngekiFumenEditor/Properties/EditorGlobalSetting.settings +++ b/OngekiFumenEditor/Properties/EditorGlobalSetting.settings @@ -110,5 +110,8 @@ 136, 3, 152 + + -1 + \ No newline at end of file diff --git a/OngekiFumenEditor/Properties/Resources.Designer.cs b/OngekiFumenEditor/Properties/Resources.Designer.cs index e6e4e7b9..88207716 100644 --- a/OngekiFumenEditor/Properties/Resources.Designer.cs +++ b/OngekiFumenEditor/Properties/Resources.Designer.cs @@ -357,6 +357,15 @@ public static string BatchSetIsCritical { } } + /// + /// 查找类似 Batch switch flick direction 的本地化字符串。 + /// + public static string BatchSwitchFlickDirection { + get { + return ResourceManager.GetString("BatchSwitchFlickDirection", resourceCulture); + } + } + /// /// 查找类似 Subdivision multiplier for the meter: 的本地化字符串。 /// @@ -2765,6 +2774,15 @@ public static string NoEditorTarget { } } + /// + /// 查找类似 No flicks to switch in selection 的本地化字符串。 + /// + public static string NoFlickCouldBeSwitched { + get { + return ResourceManager.GetString("NoFlickCouldBeSwitched", resourceCulture); + } + } + /// /// 查找类似 No fumen input 的本地化字符串。 /// @@ -2774,6 +2792,15 @@ public static string NoFumenInput { } } + /// + /// 查找类似 (No Limit) 的本地化字符串。 + /// + public static string NoLimit { + get { + return ResourceManager.GetString("NoLimit", resourceCulture); + } + } + /// /// 查找类似 No suitable objects to setting property IsCritical 的本地化字符串。 /// diff --git a/OngekiFumenEditor/Properties/Resources.ja.resx b/OngekiFumenEditor/Properties/Resources.ja.resx index eda75f85..22b48a6f 100644 --- a/OngekiFumenEditor/Properties/Resources.ja.resx +++ b/OngekiFumenEditor/Properties/Resources.ja.resx @@ -216,6 +216,9 @@ 一括でクリティカル設定 + + Flickの向きを一括変更する + 拍の分割数: @@ -999,6 +1002,9 @@ IsCritical属性を一括設定できるオブジェクトがありません + + 方向を変更する前にFlickを選択してください + 曲線レーンが補完されていません @@ -1539,4 +1545,7 @@ このアプリケーションのすべての構成オプションをリセットしますか? + + (無制限) + \ No newline at end of file diff --git a/OngekiFumenEditor/Properties/Resources.resx b/OngekiFumenEditor/Properties/Resources.resx index 5066ed26..38f301e7 100644 --- a/OngekiFumenEditor/Properties/Resources.resx +++ b/OngekiFumenEditor/Properties/Resources.resx @@ -216,6 +216,9 @@ Batch set property IsCritical + + Batch switch flick direction + Subdivision multiplier for the meter: @@ -1014,6 +1017,9 @@ No suitable objects to setting property IsCritical + + No flicks to switch in selection + The curve lane has not been interpolated yet @@ -1566,4 +1572,7 @@ Mirror lane colors + + (No Limit) + \ No newline at end of file diff --git a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx index d6fccf76..5a674199 100644 --- a/OngekiFumenEditor/Properties/Resources.zh-Hans.resx +++ b/OngekiFumenEditor/Properties/Resources.zh-Hans.resx @@ -216,6 +216,9 @@ 快速批量设置IsCritical + + 批量改变Flick物件方向 + 节奏线细分倍率: @@ -1005,6 +1008,9 @@ 无合适物件批量设置IsCritical属性 + + 请先选择Flick物件再更改方向 + 轨道物件曲线还没被插值 @@ -1548,4 +1554,7 @@ 是否重置本应用所有配置选项? + + (无限制) + \ No newline at end of file