From 594e960e97773e19ad9a1590abed5de1ad82ceab Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 8 Jan 2017 23:55:21 +0100 Subject: [PATCH 1/5] WIP on new audio layers --- Artemis/Artemis/Artemis.csproj | 15 +- .../Artemis/Modules/Abstract/ModuleModel.cs | 5 + .../Games/RocketLeague/RocketLeagueModel.cs | 17 +- Artemis/Artemis/Modules/Games/WoW/WoWModel.cs | 20 +- .../Audio/AudioCapturing/AudioCapture.cs | 135 +++++++++++++ .../AudioCapturing/AudioCaptureManager.cs | 130 ++---------- .../AudioCapturing/BaseSpectrumProvider.cs | 54 +++++ .../Audio/AudioCapturing/FftEventArgs.cs | 17 -- .../Audio/AudioCapturing/ISpectrumProvider.cs | 8 + .../Audio/AudioCapturing/LineSpectrum.cs | 56 ++++++ .../Audio/AudioCapturing/SampleAggregator.cs | 52 ----- .../Audio/AudioCapturing/SpectrumBase.cs | 189 ++++++++++++++++++ .../Types/Audio/AudioPropertiesModel.cs | 7 + .../Profiles/Layers/Types/Audio/AudioType.cs | 116 ++--------- .../ViewModels/Profiles/ProfileViewModel.cs | 2 +- Artemis/Artemis/packages.config | 2 +- 16 files changed, 513 insertions(+), 312 deletions(-) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs delete mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs delete mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index f80ef2f2c..ab2de8b36 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -151,6 +151,10 @@ ..\packages\Colore.5.1.0\lib\net35\Corale.Colore.dll True + + ..\packages\CSCore.1.1.0\lib\net35-client\CSCore.dll + True + ..\packages\CUE.NET.1.1.0.2\lib\net45\CUE.NET.dll True @@ -211,10 +215,6 @@ ..\packages\MoonSharp.2.0.0.0\lib\net40-client\MoonSharp.Interpreter.dll True - - ..\packages\NAudio.1.7.3\lib\net35\NAudio.dll - True - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll True @@ -489,9 +489,12 @@ + - - + + + + AudioPropertiesView.xaml diff --git a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs index a3bbfc5c8..6734de719 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleModel.cs @@ -99,6 +99,11 @@ namespace Artemis.Modules.Abstract ProfileModel = profileModel; ProfileModel?.Activate(_luaManager); + if (ProfileModel != null) + { + Settings.LastProfile = ProfileModel.Name; + Settings.Save(); + } RaiseProfileChangedEvent(new ProfileChangedEventArgs(ProfileModel)); } diff --git a/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs b/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs index 750c29643..729b16765 100644 --- a/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs +++ b/Artemis/Artemis/Modules/Games/RocketLeague/RocketLeagueModel.cs @@ -54,18 +54,21 @@ namespace Artemis.Modules.Games.RocketLeague { Updater.GetPointers(); _pointer = SettingsProvider.Load().RocketLeague; - - var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); - if (tempProcess == null) - return; - - _memory = new Memory(tempProcess); - + base.Enable(); } public override void Update() { + if (_memory == null) + { + var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); + if (tempProcess == null) + return; + + _memory = new Memory(tempProcess); + } + if (ProfileModel == null || DataModel == null || _memory == null) return; diff --git a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs index e1aa1688a..1df6ff02f 100644 --- a/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs +++ b/Artemis/Artemis/Modules/Games/WoW/WoWModel.cs @@ -72,19 +72,17 @@ namespace Artemis.Modules.Games.WoW _process = null; } - public override void Enable() - { - var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); - if (tempProcess == null) - return; - - _process = new ProcessSharp(tempProcess, MemoryType.Remote); - - base.Enable(); - } - public override void Update() { + if (_process == null) + { + var tempProcess = MemoryHelpers.GetProcessIfRunning(ProcessName); + if (tempProcess == null) + return; + + _process = new ProcessSharp(tempProcess, MemoryType.Remote); + } + if (ProfileModel == null || DataModel == null || _process == null) return; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs new file mode 100644 index 000000000..a8602a20b --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -0,0 +1,135 @@ +using System; +using System.Timers; +using CSCore; +using CSCore.CoreAudioAPI; +using CSCore.DSP; +using CSCore.SoundIn; +using CSCore.Streams; +using Ninject.Extensions.Logging; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class AudioCapture + { + private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private WasapiLoopbackCapture _soundIn; + private GainSource _source; + private BasicSpectrumProvider _spectrumProvider; + private GainSource _volume; + private Timer _timer; + + public AudioCapture(ILogger logger, MMDevice device) + { + Logger = logger; + Device = device; + + _timer = new Timer(1000); + _timer.Elapsed += TimerOnElapsed; + } + + private void TimerOnElapsed(object sender, ElapsedEventArgs e) + { + // If MayStop is true for longer than a second, this will stop the audio capture + if (MayStop) + { + Stop(); + MayStop = false; + } + else + MayStop = true; + } + + public bool MayStop { get; set; } + + public ILogger Logger { get; } + + public float Volume + { + get { return _volume.Volume; } + set { _volume.Volume = value; } + } + + public MMDevice Device { get; } + public bool Running { get; set; } + + public LineSpectrum GetLineSpectrum(int barCount, int volume, ScalingStrategy scalingStrategy) + { + return new LineSpectrum(FftSize) + { + SpectrumProvider = _spectrumProvider, + UseAverage = true, + BarCount = barCount, + IsXLogScale = true, + ScalingStrategy = scalingStrategy + }; + } + + public void Start() + { + if (Running) + return; + + try + { + _soundIn = new WasapiLoopbackCapture(); + _soundIn.Initialize(); + // Not sure if this null check is needed but doesnt hurt + if (Device != null) + _soundIn.Device = Device; + + var soundInSource = new SoundInSource(_soundIn); + _source = soundInSource.ToSampleSource().AppendSource(x => new GainSource(x), out _volume); + + // create a spectrum provider which provides fft data based on some input + _spectrumProvider = new BasicSpectrumProvider(_source.WaveFormat.Channels, _source.WaveFormat.SampleRate, + FftSize); + + // the SingleBlockNotificationStream is used to intercept the played samples + var notificationSource = new SingleBlockNotificationStream(_source); + // pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them) + notificationSource.SingleBlockRead += (s, a) => _spectrumProvider.Add(a.Left, a.Right); + + var waveSource = notificationSource.ToWaveSource(16); + // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated + var buffer = new byte[waveSource.WaveFormat.BytesPerSecond / 2]; + soundInSource.DataAvailable += (s, aEvent) => + { + while (waveSource.Read(buffer, 0, buffer.Length) > 0) + { + } + }; + + _soundIn.Start(); + _timer.Start(); + Running = true; + MayStop = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to start WASAPI audio capture"); + } + } + + public void Stop() + { + if (!Running) + return; + + try + { + _timer.Stop(); + _soundIn.Stop(); + _soundIn.Dispose(); + _source.Dispose(); + _soundIn = null; + _source = null; + + Running = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to stop WASAPI audio capture"); + } + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs index 04ca51a5d..cece1ddb0 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCaptureManager.cs @@ -1,133 +1,33 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using NAudio.CoreAudioApi; -using NAudio.Dsp; -using NAudio.Wave; +using CSCore.CoreAudioAPI; using Ninject.Extensions.Logging; namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing { public class AudioCaptureManager { - private readonly SampleAggregator _sampleAggregator = new SampleAggregator(1024); - private readonly WasapiLoopbackCapture _waveIn; - private Complex[] _fft; - private DateTime _lastAudioUpdate; - private DateTime _lastRequest; + private readonly List _audioCaptures; public AudioCaptureManager(ILogger logger) { Logger = logger; - Device = new MMDeviceEnumerator().EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active).FirstOrDefault(); + _audioCaptures = new List(); + } - _sampleAggregator.FftCalculated += FftCalculated; - _sampleAggregator.PerformFFT = true; + public AudioCapture GetAudioCapture(MMDevice device) + { + // Return existing audio capture if found + var audioCapture = _audioCaptures.FirstOrDefault(a => a.Device == device); + if (audioCapture != null) + return audioCapture; - // Start listening for sound data - _waveIn = new WasapiLoopbackCapture(); - _waveIn.DataAvailable += OnDataAvailable; + // Else create a new one and return that + var newAudioCapture = new AudioCapture(Logger, device); + _audioCaptures.Add(newAudioCapture); + return newAudioCapture; } public ILogger Logger { get; set; } - public MMDevice Device { get; set; } - - public bool Running { get; set; } - - public void Start() - { - if (Running) - return; - - try - { - _waveIn.StartRecording(); - Running = true; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to start WASAPI audio capture"); - } - } - - public void Stop() - { - if (!Running) - return; - - try - { - _waveIn.StopRecording(); - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to start WASAPI audio capture"); - } - } - - private void FftCalculated(object sender, FftEventArgs e) - { - _fft = e.Result; - } - - private void OnDataAvailable(object sender, WaveInEventArgs e) - { - if (DateTime.Now - _lastAudioUpdate < TimeSpan.FromMilliseconds(40)) - return; - if (DateTime.Now - _lastRequest > TimeSpan.FromSeconds(5)) - { - Stop(); - return; - } - - _lastAudioUpdate = DateTime.Now; - - var buffer = e.Buffer; - var bytesRecorded = e.BytesRecorded; - var bufferIncrement = _waveIn.WaveFormat.BlockAlign; - - for (var index = 0; index < bytesRecorded; index += bufferIncrement) - { - var sample32 = BitConverter.ToSingle(buffer, index); - _sampleAggregator.Add(sample32); - } - } - - public List GetSpectrumData(int lines) - { - _lastRequest = DateTime.Now; - if (!Running) - Start(); - - var spectrumData = new List(); - - if (_fft == null) - return spectrumData; - - int x; - var b0 = 0; - - for (x = 0; x < lines; x++) - { - float peak = 0; - var b1 = (int) Math.Pow(2, x*10.0/(lines - 1)); - if (b1 > 1023) - b1 = 1023; - if (b1 <= b0) - b1 = b0 + 1; - for (; b0 < b1; b0++) - if (peak < _fft[1 + b0].X) - peak = _fft[1 + b0].X; - var y = (int) (Math.Sqrt(peak)*3*255 - 4); - if (y > 255) - y = 255; - if (y < 0) - y = 0; - spectrumData.Add((byte) y); - } - - return spectrumData; - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs new file mode 100644 index 000000000..a89a3724f --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/BaseSpectrumProvider.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + /// + /// BasicSpectrumProvider + /// + public class BasicSpectrumProvider : FftProvider, ISpectrumProvider + { + private readonly List _contexts = new List(); + private readonly int _sampleRate; + + public BasicSpectrumProvider(int channels, int sampleRate, FftSize fftSize) + : base(channels, fftSize) + { + if (sampleRate <= 0) + throw new ArgumentOutOfRangeException("sampleRate"); + _sampleRate = sampleRate; + } + + public int GetFftBandIndex(float frequency) + { + var fftSize = (int) FftSize; + var f = _sampleRate / 2.0; + // ReSharper disable once PossibleLossOfFraction + return (int) (frequency / f * (fftSize / 2)); + } + + public bool GetFftData(float[] fftResultBuffer, object context) + { + if (_contexts.Contains(context)) + return false; + + _contexts.Add(context); + GetFftData(fftResultBuffer); + return true; + } + + public override void Add(float[] samples, int count) + { + base.Add(samples, count); + if (count > 0) + _contexts.Clear(); + } + + public override void Add(float left, float right) + { + base.Add(left, right); + _contexts.Clear(); + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs deleted file mode 100644 index fb5466c7b..000000000 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/FftEventArgs.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Diagnostics; -using NAudio.Dsp; - -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing -{ - public class FftEventArgs : EventArgs - { - [DebuggerStepThrough] - public FftEventArgs(Complex[] result) - { - Result = result; - } - - public Complex[] Result { get; private set; } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs new file mode 100644 index 000000000..bf6d8abb1 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/ISpectrumProvider.cs @@ -0,0 +1,8 @@ +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public interface ISpectrumProvider + { + bool GetFftData(float[] fftBuffer, object context); + int GetFftBandIndex(float frequency); + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs new file mode 100644 index 000000000..39c900ea4 --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using Artemis.Profiles.Layers.Models; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class LineSpectrum : SpectrumBase + { + private int _barCount; + + public LineSpectrum(FftSize fftSize) + { + FftSize = fftSize; + } + + public int BarCount + { + get { return _barCount; } + set + { + if (value <= 0) + throw new ArgumentOutOfRangeException("value"); + _barCount = value; + SpectrumResolution = value; + UpdateFrequencyMapping(); + } + } + + public void SetupLayersVertical(double height, List audioLayers) + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (!SpectrumProvider.GetFftData(fftBuffer, this)) + return; + + var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); + foreach (var p in spectrumPoints) + audioLayers[p.SpectrumPointIndex].Height = p.Value; + } + + public void SetupLayersHorizontal(double width, List audioLayers) + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (!SpectrumProvider.GetFftData(fftBuffer, this)) + return; + + var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); + foreach (var p in spectrumPoints) + audioLayers[p.SpectrumPointIndex].Width = p.Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs deleted file mode 100644 index ed286733a..000000000 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SampleAggregator.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using NAudio.Dsp; - -namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing -{ - public class SampleAggregator - { - private readonly FftEventArgs fftArgs; - - // This Complex is NAudio's own! - private readonly Complex[] fftBuffer; - private readonly int fftLength; - private readonly int m; - private int fftPos; - - public SampleAggregator(int fftLength) - { - if (!IsPowerOfTwo(fftLength)) - throw new ArgumentException("FFT Length must be a power of two"); - m = (int) Math.Log(fftLength, 2.0); - this.fftLength = fftLength; - fftBuffer = new Complex[fftLength]; - fftArgs = new FftEventArgs(fftBuffer); - } - - public bool PerformFFT { get; set; } - // FFT - public event EventHandler FftCalculated; - - private bool IsPowerOfTwo(int x) - { - return (x & (x - 1)) == 0; - } - - public void Add(float value) - { - if (PerformFFT && FftCalculated != null) - { - // Remember the window function! There are many others as well. - fftBuffer[fftPos].X = (float) (value * FastFourierTransform.HammingWindow(fftPos, fftLength)); - fftBuffer[fftPos].Y = 0; // This is always zero with audio. - fftPos++; - if (fftPos >= fftLength) - { - fftPos = 0; - FastFourierTransform.FFT(true, m, fftBuffer); - FftCalculated(this, fftArgs); - } - } - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs new file mode 100644 index 000000000..3c361f0cc --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SpectrumBase.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using CSCore; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public class SpectrumBase + { + private const int ScaleFactorLinear = 9; + protected const int ScaleFactorSqr = 2; + protected const double MinDbValue = -90; + protected const double MaxDbValue = 0; + protected const double DbScale = MaxDbValue - MinDbValue; + + private int _fftSize; + private bool _isXLogScale; + private int _maxFftIndex; + private int _maximumFrequency = 20000; + private int _maximumFrequencyIndex; + private int _minimumFrequency = 20; //Default spectrum from 20Hz to 20kHz + private int _minimumFrequencyIndex; + private int[] _spectrumIndexMax; + private int[] _spectrumLogScaleIndexMax; + private ISpectrumProvider _spectrumProvider; + + protected int SpectrumResolution; + + public int MaximumFrequency + { + get { return _maximumFrequency; } + set + { + if (value <= MinimumFrequency) + throw new ArgumentOutOfRangeException("value", + "Value must not be less or equal the MinimumFrequency."); + _maximumFrequency = value; + UpdateFrequencyMapping(); + } + } + + public int MinimumFrequency + { + get { return _minimumFrequency; } + set + { + if (value < 0) + throw new ArgumentOutOfRangeException("value"); + _minimumFrequency = value; + UpdateFrequencyMapping(); + } + } + + [Browsable(false)] + public ISpectrumProvider SpectrumProvider + { + get { return _spectrumProvider; } + set + { + if (value == null) + throw new ArgumentNullException("value"); + _spectrumProvider = value; + } + } + + public bool IsXLogScale + { + get { return _isXLogScale; } + set + { + _isXLogScale = value; + UpdateFrequencyMapping(); + } + } + + public ScalingStrategy ScalingStrategy { get; set; } + + public bool UseAverage { get; set; } + + [Browsable(false)] + public FftSize FftSize + { + get { return (FftSize) _fftSize; } + protected set + { + if ((int) Math.Log((int) value, 2) % 1 != 0) + throw new ArgumentOutOfRangeException("value"); + + _fftSize = (int) value; + _maxFftIndex = _fftSize / 2 - 1; + } + } + + protected virtual void UpdateFrequencyMapping() + { + _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, _maxFftIndex); + _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), _maxFftIndex); + + var actualResolution = SpectrumResolution; + + var indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex; + var linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3); + + _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true); + _spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true); + + var maxLog = Math.Log(actualResolution, actualResolution); + for (var i = 1; i < actualResolution; i++) + { + var logIndex = + (int) ((maxLog - Math.Log(actualResolution + 1 - i, actualResolution + 1)) * indexCount) + + _minimumFrequencyIndex; + + _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize); + _spectrumLogScaleIndexMax[i - 1] = logIndex; + } + + if (actualResolution > 0) + _spectrumIndexMax[_spectrumIndexMax.Length - 1] = + _spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex; + } + + protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer) + { + var dataPoints = new List(); + + double value0 = 0, value = 0; + double lastValue = 0; + var actualMaxValue = maxValue; + var spectrumPointIndex = 0; + + for (var i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++) + { + switch (ScalingStrategy) + { + case ScalingStrategy.Decibel: + value0 = (20 * Math.Log10(fftBuffer[i]) - MinDbValue) / DbScale * actualMaxValue; + break; + case ScalingStrategy.Linear: + value0 = fftBuffer[i] * ScaleFactorLinear * actualMaxValue; + break; + case ScalingStrategy.Sqrt: + value0 = Math.Sqrt(fftBuffer[i]) * ScaleFactorSqr * actualMaxValue; + break; + } + + var recalc = true; + + value = Math.Max(0, Math.Max(value0, value)); + + while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 && + i == + (IsXLogScale + ? _spectrumLogScaleIndexMax[spectrumPointIndex] + : _spectrumIndexMax[spectrumPointIndex])) + { + if (!recalc) + value = lastValue; + + if (value > maxValue) + value = maxValue; + + if (UseAverage && spectrumPointIndex > 0) + value = (lastValue + value) / 2.0; + + dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value}); + + lastValue = value; + value = 0.0; + spectrumPointIndex++; + recalc = false; + } + + //value = 0; + } + + return dataPoints.ToArray(); + } + + [DebuggerDisplay("{Value}")] + protected struct SpectrumPointData + { + public int SpectrumPointIndex; + public double Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs index b31c0972e..1c2b2b9be 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesModel.cs @@ -21,4 +21,11 @@ namespace Artemis.Profiles.Layers.Types.Audio [Description("Left to right")] LeftToRight, [Description("Right to left")] RightToLeft } + + public enum ScalingStrategy + { + [Description("Decibel")] Decibel, + [Description("Linear")] Linear, + [Description("Square root")] Sqrt + } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index e2ee2999a..99879e224 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -24,16 +24,15 @@ namespace Artemis.Profiles.Layers.Types.Audio private DateTime _lastUpdate; private int _lines; private string _previousSettings; + private LineSpectrum _lineSpectrum; + private AudioCapture _audioCapture; public AudioType(IKernel kernel, AudioCaptureManager audioCaptureManager) { _kernel = kernel; - AudioCaptureManager = audioCaptureManager; + _audioCapture = audioCaptureManager.GetAudioCapture(null); } - [JsonIgnore] - public AudioCaptureManager AudioCaptureManager { get; set; } - public string Name => "Keyboard - Audio visualization"; public bool ShowInEdtor => true; public DrawType DrawType => DrawType.Keyboard; @@ -83,23 +82,25 @@ namespace Artemis.Profiles.Layers.Types.Audio if (isPreview) return; + // Start audio capture in case it wasn't running + _audioCapture.Start(); + _audioCapture.MayStop = false; + lock (_audioLayers) { + // Called every update but only runs every second SetupLayers(layerModel); - var spectrumData = AudioCaptureManager.GetSpectrumData(_lines); - if (!spectrumData.Any()) - return; - + var settings = (AudioPropertiesModel) layerModel.Properties; switch (settings.Direction) { case Direction.TopToBottom: case Direction.BottomToTop: - ApplyVertical(spectrumData, settings); + _lineSpectrum.SetupLayersVertical(layerModel.Height, _audioLayers); break; case Direction.LeftToRight: case Direction.RightToLeft: - ApplyHorizontal(spectrumData, settings); + _lineSpectrum.SetupLayersHorizontal(layerModel.Width, _audioLayers); break; } } @@ -124,97 +125,7 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } - - private void ApplyVertical(List spectrumData, AudioPropertiesModel settings) - { - var index = 0; - foreach (var audioLayer in _audioLayers) - { - int height; - if (spectrumData.Count > index) - height = (int) Math.Round(spectrumData[index]/2.55); - else - height = 0; - - // Apply Sensitivity setting - height = height*settings.Sensitivity; - - var newHeight = settings.Height/100.0*height; - if (newHeight >= audioLayer.Properties.Height) - audioLayer.Properties.Height = newHeight; - else - audioLayer.Properties.Height = audioLayer.Properties.Height - settings.FadeSpeed; - if (audioLayer.Properties.Height < 0) - audioLayer.Properties.Height = 0; - - // Reverse the direction if settings require it - if (settings.Direction == Direction.BottomToTop) - audioLayer.Properties.Y = settings.Y + (settings.Height - audioLayer.Properties.Height); - - FakeUpdate(settings, audioLayer); - index++; - } - } - - private void ApplyHorizontal(List spectrumData, AudioPropertiesModel settings) - { - var index = 0; - foreach (var audioLayer in _audioLayers) - { - int width; - if (spectrumData.Count > index) - width = (int) Math.Round(spectrumData[index]/2.55); - else - width = 0; - - // Apply Sensitivity setting - width = width*settings.Sensitivity; - - var newWidth = settings.Width/100.0*width; - if (newWidth >= audioLayer.Properties.Width) - audioLayer.Properties.Width = newWidth; - else - audioLayer.Properties.Width = audioLayer.Properties.Width - settings.FadeSpeed; - if (audioLayer.Properties.Width < 0) - audioLayer.Properties.Width = 0; - - audioLayer.Properties.Brush = settings.Brush; - audioLayer.Properties.Contain = false; - - // Reverse the direction if settings require it - if (settings.Direction == Direction.RightToLeft) - audioLayer.Properties.X = settings.X + (settings.Width - audioLayer.Properties.Width); - - FakeUpdate(settings, audioLayer); - index++; - } - } - - /// - /// Updates the layer manually faking the width and height for a properly working animation - /// - /// - /// - private static void FakeUpdate(LayerPropertiesModel settings, LayerModel audioLayer) - { - // Call the regular update - audioLayer.LayerType?.Update(audioLayer, null); - - // Store the original height and width - var oldHeight = audioLayer.Properties.Height; - var oldWidth = audioLayer.Properties.Width; - - // Fake the height and width and update the animation - audioLayer.Properties.Width = settings.Width; - audioLayer.Properties.Height = settings.Height; - audioLayer.LastRender = DateTime.Now; - audioLayer.LayerAnimation?.Update(audioLayer, true); - - // Restore the height and width - audioLayer.Properties.Height = oldHeight; - audioLayer.Properties.Width = oldWidth; - } - + /// /// Sets up the inner layers when the settings have changed /// @@ -230,7 +141,7 @@ namespace Artemis.Profiles.Layers.Types.Audio var currentSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); var currentType = _audioLayers.FirstOrDefault()?.LayerAnimation?.GetType(); - if (currentSettings == _previousSettings && (layerModel.LayerAnimation.GetType() == currentType)) + if (currentSettings == _previousSettings && layerModel.LayerAnimation.GetType() == currentType) return; _previousSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); @@ -249,6 +160,7 @@ namespace Artemis.Profiles.Layers.Types.Audio default: throw new ArgumentOutOfRangeException(); } + _lineSpectrum = _audioCapture.GetLineSpectrum(_audioLayers.Count, 5, ScalingStrategy.Decibel); } private void SetupVertical(LayerModel layerModel) diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index 0c5009373..0df27992d 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -118,7 +118,7 @@ namespace Artemis.ViewModels.Profiles var renderLayers = GetRenderLayers(); // Draw the current frame to the preview - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(4); + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); var visual = new DrawingVisual(); using (var drawingContext = visual.RenderOpen()) { diff --git a/Artemis/Artemis/packages.config b/Artemis/Artemis/packages.config index 507184401..55b725806 100644 --- a/Artemis/Artemis/packages.config +++ b/Artemis/Artemis/packages.config @@ -5,6 +5,7 @@ + @@ -15,7 +16,6 @@ - From c1d1766a4193a80c1d837ed2c8887ffe823d654f Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 11 Jan 2017 22:34:05 +0100 Subject: [PATCH 2/5] Audio layer progress --- Artemis/Artemis/Artemis.csproj | 1 + Artemis/Artemis/ArtemisBootstrapper.cs | 1 + Artemis/Artemis/DAL/ProfileProvider.cs | 2 +- .../DeviceProviders/Corsair/CorsairHeadset.cs | 2 +- .../DeviceProviders/Corsair/CorsairMouse.cs | 2 +- .../Corsair/CorsairMousemat.cs | 2 +- .../DeviceProviders/KeyboardProvider.cs | 5 +- .../Logitech/LogitechGeneric.cs | 2 +- Artemis/Artemis/Managers/ProfileManager.cs | 5 + .../Audio/AudioCapturing/AudioCapture.cs | 147 ++++++++++---- .../Audio/AudioCapturing/LineSpectrum.cs | 25 ++- .../Audio/AudioCapturing/SingleSpectrum.cs | 31 +++ .../Profiles/Layers/Types/Audio/AudioType.cs | 188 ++++++------------ Artemis/Artemis/Profiles/ProfileModel.cs | 8 +- Artemis/Artemis/Utilities/ImageUtilities.cs | 1 + .../Profiles/ProfileEditorViewModel.cs | 15 +- .../ViewModels/Profiles/ProfileViewModel.cs | 3 +- 17 files changed, 232 insertions(+), 208 deletions(-) create mode 100644 Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index ab2de8b36..8f81676a9 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -494,6 +494,7 @@ + diff --git a/Artemis/Artemis/ArtemisBootstrapper.cs b/Artemis/Artemis/ArtemisBootstrapper.cs index e15174d44..bfd41ad63 100644 --- a/Artemis/Artemis/ArtemisBootstrapper.cs +++ b/Artemis/Artemis/ArtemisBootstrapper.cs @@ -91,6 +91,7 @@ namespace Artemis JsonConvert.DefaultSettings = () => settings; //TODO DarthAffe 17.12.2016: Is this the right location for this? + //TODO Move to Mainmanager and make disposable ActiveWindowHelper.Initialize(); } diff --git a/Artemis/Artemis/DAL/ProfileProvider.cs b/Artemis/Artemis/DAL/ProfileProvider.cs index 547582dc0..2721ae7d6 100644 --- a/Artemis/Artemis/DAL/ProfileProvider.cs +++ b/Artemis/Artemis/DAL/ProfileProvider.cs @@ -89,7 +89,7 @@ namespace Artemis.DAL } File.WriteAllText(path + $@"\{prof.Slug}.json", json); - Logger.Trace("Saved profile {0}/{1}/{2}", prof.KeyboardSlug, prof.GameName, prof.Name); + Logger.Debug("Saved profile {0}/{1}/{2}", prof.KeyboardSlug, prof.GameName, prof.Name); } } diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs index 908d4ae07..9883c0253 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairHeadset.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs index 7e18ba746..20767e34b 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMouse.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs index 3062862d5..421d3b958 100644 --- a/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs +++ b/Artemis/Artemis/DeviceProviders/Corsair/CorsairMousemat.cs @@ -34,7 +34,7 @@ namespace Artemis.DeviceProviders.Corsair public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } public override void UpdateDevice(Bitmap bitmap) diff --git a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs index afa83b09c..59b4889fa 100644 --- a/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs +++ b/Artemis/Artemis/DeviceProviders/KeyboardProvider.cs @@ -89,13 +89,12 @@ namespace Artemis.DeviceProviders public override void UpdateDevice(Bitmap bitmap) { - throw new NotImplementedException("KeyboardProvider doesn't implement UpdateDevice, use DrawBitmap instead."); + throw new NotSupportedException("KeyboardProvider doesn't implement UpdateDevice, use DrawBitmap instead."); } public override bool TryEnable() { - throw new NotImplementedException( - "KeyboardProvider doesn't implement TryEnable, use CanEnableAsync instead."); + throw new NotSupportedException("KeyboardProvider doesn't implement TryEnable, use CanEnableAsync instead."); } /// diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs index 5da53fe28..9c978a791 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechGeneric.cs @@ -49,7 +49,7 @@ namespace Artemis.DeviceProviders.Logitech public override void Disable() { - throw new NotImplementedException("Can only disable a keyboard"); + throw new NotSupportedException("Can only disable a keyboard"); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Managers/ProfileManager.cs b/Artemis/Artemis/Managers/ProfileManager.cs index a28397959..ed0268ff8 100644 --- a/Artemis/Artemis/Managers/ProfileManager.cs +++ b/Artemis/Artemis/Managers/ProfileManager.cs @@ -76,6 +76,11 @@ namespace Artemis.Managers return; _logger.Debug("Deactivate profile preview"); + + // Save the profile the editor was using + var activePreview = PreviewViewModules.FirstOrDefault(p => p.IsModuleActive); + activePreview?.ProfileEditor?.SaveSelectedProfile(); + var lastModule = _moduleManager.GetLastModule(); if (lastModule != null) _moduleManager.ChangeActiveModule(lastModule); diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index a8602a20b..c1092fc31 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Timers; using CSCore; using CSCore.CoreAudioAPI; @@ -12,32 +13,31 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public class AudioCapture { private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; + private readonly Timer _activityTimer; + private readonly Timer _volumeTimer; + private readonly double[] _volumeValues; + private SingleSpectrum _singleSpectrum; private WasapiLoopbackCapture _soundIn; private GainSource _source; private BasicSpectrumProvider _spectrumProvider; private GainSource _volume; - private Timer _timer; + private int _volumeIndex; public AudioCapture(ILogger logger, MMDevice device) { Logger = logger; Device = device; + DesiredAverage = 0.75; - _timer = new Timer(1000); - _timer.Elapsed += TimerOnElapsed; + _volumeValues = new double[5]; + _volumeIndex = 0; + _activityTimer = new Timer(1000); + _activityTimer.Elapsed += ActivityTimerOnElapsed; + _volumeTimer = new Timer(200); + _volumeTimer.Elapsed += VolumeTimerOnElapsed; } - private void TimerOnElapsed(object sender, ElapsedEventArgs e) - { - // If MayStop is true for longer than a second, this will stop the audio capture - if (MayStop) - { - Stop(); - MayStop = false; - } - else - MayStop = true; - } + public double DesiredAverage { get; set; } public bool MayStop { get; set; } @@ -52,7 +52,64 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public MMDevice Device { get; } public bool Running { get; set; } - public LineSpectrum GetLineSpectrum(int barCount, int volume, ScalingStrategy scalingStrategy) + private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) + { + if (Volume <= 0) + Volume = 1; + + var currentValue = _singleSpectrum.GetValue(); + if (currentValue == null) + return; + + _volumeValues[_volumeIndex] = currentValue.Value; + + if (_volumeIndex == 4) + { + _volumeIndex = 0; + } + else + { + _volumeIndex++; + return; + } + + var averageVolume = _volumeValues.Average(); + // Don't adjust when there is virtually no audio + if (averageVolume < 0.01) + return; + // Don't bother when the volume with within a certain marigin + if (averageVolume > DesiredAverage - 0.1 && averageVolume < DesiredAverage + 0.1) + return; + + if (averageVolume < DesiredAverage && Volume < 50) + { + Logger.Trace("averageVolume:{0} | DesiredAverage:{1} | Volume:{2} so increase.", currentValue, + DesiredAverage, Volume); + Volume++; + } + else if (Volume > 1) + { + Logger.Trace("averageVolume:{0} | DesiredAverage:{1} | Volume:{2} so decrease.", currentValue, + DesiredAverage, Volume); + Volume--; + } + } + + private void ActivityTimerOnElapsed(object sender, ElapsedEventArgs e) + { + // If MayStop is true for longer than a second, this will stop the audio capture + if (MayStop) + { + Stop(); + MayStop = false; + } + else + { + MayStop = true; + } + } + + public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { return new LineSpectrum(FftSize) { @@ -64,11 +121,41 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } - public void Start() + public void Stop() { + if (!Running) + return; + + Logger.Debug("Stopping audio capture for device: {0}", Device?.FriendlyName ?? "default"); + + try + { + _activityTimer.Stop(); + _volumeTimer.Stop(); + + _soundIn.Stop(); + _soundIn.Dispose(); + _source.Dispose(); + _soundIn = null; + _source = null; + + Running = false; + } + catch (Exception e) + { + Logger.Warn(e, "Failed to stop WASAPI audio capture"); + } + } + + public void Pulse() + { + MayStop = false; + if (Running) return; + Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); + try { _soundIn = new WasapiLoopbackCapture(); @@ -99,8 +186,12 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } }; + _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); + + _activityTimer.Start(); + _volumeTimer.Start(); + _soundIn.Start(); - _timer.Start(); Running = true; MayStop = false; } @@ -109,27 +200,5 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing Logger.Warn(e, "Failed to start WASAPI audio capture"); } } - - public void Stop() - { - if (!Running) - return; - - try - { - _timer.Stop(); - _soundIn.Stop(); - _soundIn.Dispose(); - _source.Dispose(); - _soundIn = null; - _source = null; - - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to stop WASAPI audio capture"); - } - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs index 39c900ea4..c4373e78b 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using Artemis.Profiles.Layers.Models; +using System.Windows; using CSCore.DSP; namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing @@ -27,30 +26,36 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - public void SetupLayersVertical(double height, List audioLayers) + public void UpdateLinesVertical(double height, Point[] points) { - var fftBuffer = new float[(int)FftSize]; + var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) return; var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); - foreach (var p in spectrumPoints) - audioLayers[p.SpectrumPointIndex].Height = p.Value; + for (var index = 0; index < spectrumPoints.Length; index++) + { + var spectrumPointData = spectrumPoints[index]; + points[index].Y = spectrumPointData.Value; + } } - public void SetupLayersHorizontal(double width, List audioLayers) + public void UpdateLinesHorizontal(double width, Point[] points) { - var fftBuffer = new float[(int)FftSize]; + var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) return; var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); - foreach (var p in spectrumPoints) - audioLayers[p.SpectrumPointIndex].Width = p.Value; + for (var index = 0; index < spectrumPoints.Length; index++) + { + var spectrumPointData = spectrumPoints[index]; + points[index].X = spectrumPointData.Value; + } } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs new file mode 100644 index 000000000..4757f1dfa --- /dev/null +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/SingleSpectrum.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Artemis.Profiles.Layers.Models; +using CSCore.DSP; + +namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing +{ + public sealed class SingleSpectrum : SpectrumBase + { + public SingleSpectrum(FftSize fftSize, ISpectrumProvider spectrumProvider) + { + SpectrumProvider = spectrumProvider; + SpectrumResolution = 1; + FftSize = fftSize; + + UpdateFrequencyMapping(); + } + + public double? GetValue() + { + var fftBuffer = new float[(int)FftSize]; + + // get the fft result from the spectrum provider + if (SpectrumProvider == null || !SpectrumProvider.GetFftData(fftBuffer, this)) + return null; + + var spectrumPoints = CalculateSpectrumPoints(1, fftBuffer); + return spectrumPoints[0].Value; + } + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 99879e224..f2d3dfde8 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; +using System.Windows; using System.Windows.Media; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; @@ -11,25 +8,18 @@ using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; using Artemis.ViewModels.Profiles; -using Newtonsoft.Json; -using Ninject; namespace Artemis.Profiles.Layers.Types.Audio { public class AudioType : ILayerType { - private readonly List _audioLayers = new List(); - private readonly IKernel _kernel; - - private DateTime _lastUpdate; + private readonly AudioCapture _audioCapture; private int _lines; - private string _previousSettings; private LineSpectrum _lineSpectrum; - private AudioCapture _audioCapture; + private Point[] _points; - public AudioType(IKernel kernel, AudioCaptureManager audioCaptureManager) + public AudioType(AudioCaptureManager audioCaptureManager) { - _kernel = kernel; _audioCapture = audioCaptureManager.GetAudioCapture(null); } @@ -52,26 +42,29 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Draw(LayerModel layerModel, DrawingContext c) { - lock (_audioLayers) + var parentX = layerModel.X * 4; + var parentY = layerModel.Y * 4; + var pen = new Pen(layerModel.Brush, 4); + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) { - foreach (var audioLayer in _audioLayers) + for (var index = 0; index < _points.Length; index++) { - // This is cheating but it ensures that the brush is drawn across the entire main-layer - var oldWidth = audioLayer.Properties.Width; - var oldHeight = audioLayer.Properties.Height; - var oldX = audioLayer.Properties.X; - var oldY = audioLayer.Properties.Y; - - audioLayer.Properties.Width = layerModel.Properties.Width; - audioLayer.Properties.Height = layerModel.Properties.Height; - audioLayer.Properties.X = layerModel.Properties.X; - audioLayer.Properties.Y = layerModel.Properties.Y; - audioLayer.LayerType.Draw(audioLayer, c); - - audioLayer.Properties.Width = oldWidth; - audioLayer.Properties.Height = oldHeight; - audioLayer.Properties.X = oldX; - audioLayer.Properties.Y = oldY; + var startPoint = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); + var endPoint = new Point(index * 4 + 2 + parentX, parentY); + var clip = new Rect(startPoint, endPoint); + clip.Width = 4; + c.PushClip(new RectangleGeometry(new Rect(startPoint, endPoint))); + var point = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); + c.DrawLine(pen, startPoint, endPoint); + } + } + else + { + for (var index = 0; index < _points.Length; index++) + { + var point = new Point(_points[index].X * 4 + parentX, index * 4 + 2 + parentY); + c.DrawLine(pen, point, new Point(parentX, index * 4 + 2 + parentY)); } } } @@ -79,31 +72,39 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) { layerModel.ApplyProperties(true); - if (isPreview) + + var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; + + int currentLines; + double currentHeight; + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + { + currentLines = (int) layerModel.Width; + currentHeight = layerModel.Height; + } + else + { + currentLines = (int) layerModel.Height; + currentHeight = layerModel.Width; + } + + if (_lines != currentLines) + { + _lines = currentLines; + _points = new Point[_lines]; + _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + } + + // Let audio capture know it is being listened to + _audioCapture.Pulse(); + + if (_lineSpectrum == null) return; - // Start audio capture in case it wasn't running - _audioCapture.Start(); - _audioCapture.MayStop = false; - - lock (_audioLayers) - { - // Called every update but only runs every second - SetupLayers(layerModel); - - var settings = (AudioPropertiesModel) layerModel.Properties; - switch (settings.Direction) - { - case Direction.TopToBottom: - case Direction.BottomToTop: - _lineSpectrum.SetupLayersVertical(layerModel.Height, _audioLayers); - break; - case Direction.LeftToRight: - case Direction.RightToLeft: - _lineSpectrum.SetupLayersHorizontal(layerModel.Width, _audioLayers); - break; - } - } + if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + _lineSpectrum.UpdateLinesVertical(currentHeight, _points); + else + _lineSpectrum.UpdateLinesHorizontal(currentHeight, _points); } public void SetupProperties(LayerModel layerModel) @@ -125,82 +126,5 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } - - /// - /// Sets up the inner layers when the settings have changed - /// - /// - private void SetupLayers(LayerModel layerModel) - { - // Checking on settings update is expensive, only do it every second - if (DateTime.Now - _lastUpdate < TimeSpan.FromSeconds(1)) - return; - _lastUpdate = DateTime.Now; - - var settings = (AudioPropertiesModel) layerModel.Properties; - var currentSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); - var currentType = _audioLayers.FirstOrDefault()?.LayerAnimation?.GetType(); - - if (currentSettings == _previousSettings && layerModel.LayerAnimation.GetType() == currentType) - return; - - _previousSettings = JsonConvert.SerializeObject(settings, Formatting.Indented); - - _audioLayers.Clear(); - switch (settings.Direction) - { - case Direction.TopToBottom: - case Direction.BottomToTop: - SetupVertical(layerModel); - break; - case Direction.LeftToRight: - case Direction.RightToLeft: - SetupHorizontal(layerModel); - break; - default: - throw new ArgumentOutOfRangeException(); - } - _lineSpectrum = _audioCapture.GetLineSpectrum(_audioLayers.Count, 5, ScalingStrategy.Decibel); - } - - private void SetupVertical(LayerModel layerModel) - { - _lines = (int) layerModel.Properties.Width; - for (var i = 0; i < _lines; i++) - { - var layer = LayerModel.CreateLayer(); - layer.Properties.X = layerModel.Properties.X + i; - layer.Properties.Y = layerModel.Properties.Y; - layer.Properties.Width = 1; - layer.Properties.Height = 0; - layer.Properties.AnimationSpeed = layerModel.Properties.AnimationSpeed; - layer.Properties.Brush = layerModel.Properties.Brush; - layer.Properties.Contain = false; - layer.LayerAnimation = (ILayerAnimation) _kernel.Get(layerModel.LayerAnimation.GetType()); - - _audioLayers.Add(layer); - layer.Update(null, false, true); - } - } - - private void SetupHorizontal(LayerModel layerModel) - { - _lines = (int) layerModel.Properties.Height; - for (var i = 0; i < _lines; i++) - { - var layer = LayerModel.CreateLayer(); - layer.Properties.X = layerModel.Properties.X; - layer.Properties.Y = layerModel.Properties.Y + i; - layer.Properties.Width = 0; - layer.Properties.Height = 1; - layer.Properties.AnimationSpeed = layerModel.Properties.AnimationSpeed; - layer.Properties.Brush = layerModel.Properties.Brush; - layer.Properties.Contain = false; - layer.LayerAnimation = (ILayerAnimation) _kernel.Get(layerModel.LayerAnimation.GetType()); - - _audioLayers.Add(layer); - layer.Update(null, false, true); - } - } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/ProfileModel.cs b/Artemis/Artemis/Profiles/ProfileModel.cs index 0f076058a..5dd3cbfed 100644 --- a/Artemis/Artemis/Profiles/ProfileModel.cs +++ b/Artemis/Artemis/Profiles/ProfileModel.cs @@ -108,8 +108,10 @@ namespace Artemis.Profiles Rect rect, bool preview) { renderLayers = renderLayers.Where(rl => rl.LayerType.DrawType == drawType).ToList(); + if (!renderLayers.Any()) + return; + var visual = new DrawingVisual(); - var layerModels = renderLayers.ToList(); using (var c = visual.RenderOpen()) { // Setup the DrawingVisual's size @@ -117,12 +119,12 @@ namespace Artemis.Profiles c.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, rect); // Update the layers - foreach (var layerModel in layerModels) + foreach (var layerModel in renderLayers) layerModel.Update(dataModel, preview, true); RaiseDeviceUpdatedEvent(new ProfileDeviceEventsArg(drawType, dataModel, preview, null)); // Draw the layers - foreach (var layerModel in layerModels) + foreach (var layerModel in renderLayers) layerModel.Draw(dataModel, c, preview, true); RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(drawType, dataModel, preview, c)); diff --git a/Artemis/Artemis/Utilities/ImageUtilities.cs b/Artemis/Artemis/Utilities/ImageUtilities.cs index 51fc2f66f..d8ac1122d 100644 --- a/Artemis/Artemis/Utilities/ImageUtilities.cs +++ b/Artemis/Artemis/Utilities/ImageUtilities.cs @@ -56,6 +56,7 @@ namespace Artemis.Utilities public static Bitmap DrawingVisualToBitmap(DrawingVisual visual, Rect rect) { + // TODO: Improve performance by dividing by 4 here var bmp = new RenderTargetBitmap((int) rect.Width, (int) rect.Height, 96, 96, PixelFormats.Pbgra32); bmp.Render(visual); diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index 13afbfbd3..af0659dd9 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Timers; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; @@ -31,7 +30,6 @@ using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using Screen = Caliburn.Micro.Screen; -using Timer = System.Timers.Timer; namespace Artemis.ViewModels.Profiles { @@ -41,7 +39,6 @@ namespace Artemis.ViewModels.Profiles private readonly MetroDialogService _dialogService; private readonly LuaManager _luaManager; private readonly ModuleModel _moduleModel; - private readonly Timer _saveTimer; private readonly WindowService _windowService; private ImageSource _keyboardPreview; private ObservableCollection _layers; @@ -69,10 +66,6 @@ namespace Artemis.ViewModels.Profiles _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; LoadProfiles(); - - _saveTimer = new Timer(5000); - _saveTimer.Elapsed += ProfileSaveHandler; - _saveTimer.Start(); } public ProfileViewModel ProfileViewModel { get; set; } @@ -695,11 +688,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => LayerSelected); } - private void ProfileSaveHandler(object sender, ElapsedEventArgs e) - { - SaveSelectedProfile(); - } - public void SaveSelectedProfile() { if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) @@ -779,9 +767,8 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { + SaveSelectedProfile(); ProfileViewModel.Dispose(); - _saveTimer?.Stop(); - _saveTimer?.Dispose(); _watcher?.Dispose(); } diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index 0df27992d..bc74299a0 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -167,7 +167,7 @@ namespace Artemis.ViewModels.Profiles new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); } - SelectedProfile.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); + SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); // Remove the clip drawingContext.Pop(); @@ -380,7 +380,6 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { - _keyboardPreviewCursor?.Dispose(); _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; } From 5ce65962e7bbaf7c7a9dd4d8e80da6fefeae7516 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 12 Jan 2017 14:21:27 +0100 Subject: [PATCH 3/5] Added AudioType brush drawing and animations back in --- .../Audio/AudioCapturing/LineSpectrum.cs | 27 +--- .../Profiles/Layers/Types/Audio/AudioType.cs | 123 ++++++++++++------ 2 files changed, 91 insertions(+), 59 deletions(-) diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs index c4373e78b..1830033be 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/LineSpectrum.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Windows; using CSCore.DSP; @@ -26,36 +28,17 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - public void UpdateLinesVertical(double height, Point[] points) + public List GetLineValues(double height) { var fftBuffer = new float[(int) FftSize]; // get the fft result from the spectrum provider if (!SpectrumProvider.GetFftData(fftBuffer, this)) - return; + return null; var spectrumPoints = CalculateSpectrumPoints(height, fftBuffer); - for (var index = 0; index < spectrumPoints.Length; index++) - { - var spectrumPointData = spectrumPoints[index]; - points[index].Y = spectrumPointData.Value; - } - } + return spectrumPoints?.Select(s => s.Value).ToList(); - public void UpdateLinesHorizontal(double width, Point[] points) - { - var fftBuffer = new float[(int) FftSize]; - - // get the fft result from the spectrum provider - if (!SpectrumProvider.GetFftData(fftBuffer, this)) - return; - - var spectrumPoints = CalculateSpectrumPoints(width, fftBuffer); - for (var index = 0; index < spectrumPoints.Length; index++) - { - var spectrumPointData = spectrumPoints[index]; - points[index].X = spectrumPointData.Value; - } } } } \ No newline at end of file diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index f2d3dfde8..5bf340179 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -1,7 +1,9 @@ -using System.Windows; +using System.Collections.Generic; +using System.Windows; using System.Windows.Media; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; +using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -13,10 +15,11 @@ namespace Artemis.Profiles.Layers.Types.Audio { public class AudioType : ILayerType { + private const GeometryCombineMode CombineMode = GeometryCombineMode.Union; private readonly AudioCapture _audioCapture; private int _lines; private LineSpectrum _lineSpectrum; - private Point[] _points; + private List _lineValues; public AudioType(AudioCaptureManager audioCaptureManager) { @@ -42,31 +45,56 @@ namespace Artemis.Profiles.Layers.Types.Audio public void Draw(LayerModel layerModel, DrawingContext c) { - var parentX = layerModel.X * 4; - var parentY = layerModel.Y * 4; - var pen = new Pen(layerModel.Brush, 4); + if (_lineValues == null) + return; + + var parentX = layerModel.X; + var parentY = layerModel.Y; var direction = ((AudioPropertiesModel) layerModel.Properties).Direction; - if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) + + // Create a geometry that will be formed by all the bars + Geometry barGeometry = new RectangleGeometry(); + + switch (direction) { - for (var index = 0; index < _points.Length; index++) - { - var startPoint = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); - var endPoint = new Point(index * 4 + 2 + parentX, parentY); - var clip = new Rect(startPoint, endPoint); - clip.Width = 4; - c.PushClip(new RectangleGeometry(new Rect(startPoint, endPoint))); - var point = new Point(index * 4 + 2 + parentX, _points[index].Y * 4 + parentY); - c.DrawLine(pen, startPoint, endPoint); - } - } - else - { - for (var index = 0; index < _points.Length; index++) - { - var point = new Point(_points[index].X * 4 + parentX, index * 4 + 2 + parentY); - c.DrawLine(pen, point, new Point(parentX, index * 4 + 2 + parentY)); - } + case Direction.BottomToTop: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + case Direction.TopToBottom: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + case Direction.LeftToRight: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; + default: + for (var index = 0; index < _lineValues.Count; index++) + { + var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + var barRect = new RectangleGeometry(clipRect); + barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); + } + break; } + + // Push the created geometry + c.PushClip(barGeometry); + BrushDraw(layerModel, c); + c.Pop(); } public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false) @@ -88,23 +116,18 @@ namespace Artemis.Profiles.Layers.Types.Audio currentHeight = layerModel.Width; } - if (_lines != currentLines) - { - _lines = currentLines; - _points = new Point[_lines]; - _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); - } - // Let audio capture know it is being listened to _audioCapture.Pulse(); - if (_lineSpectrum == null) - return; + if (_lines != currentLines || _lineSpectrum == null) + { + _lines = currentLines; + _lineSpectrum = _audioCapture.GetLineSpectrum(_lines, ScalingStrategy.Decibel); + } - if (direction == Direction.BottomToTop || direction == Direction.TopToBottom) - _lineSpectrum.UpdateLinesVertical(currentHeight, _points); - else - _lineSpectrum.UpdateLinesHorizontal(currentHeight, _points); + var newLineValues = _lineSpectrum?.GetLineValues(currentHeight); + if (newLineValues != null) + _lineValues = newLineValues; } public void SetupProperties(LayerModel layerModel) @@ -126,5 +149,31 @@ namespace Artemis.Profiles.Layers.Types.Audio return layerPropertiesViewModel; return new AudioPropertiesViewModel(layerEditorViewModel); } + + public void BrushDraw(LayerModel layerModel, DrawingContext c) + { + // If an animation is present, let it handle the drawing + if (layerModel.LayerAnimation != null && !(layerModel.LayerAnimation is NoneAnimation)) + { + layerModel.LayerAnimation.Draw(layerModel, c); + return; + } + + // Otherwise draw the rectangle with its layer.AppliedProperties dimensions and brush + var rect = layerModel.Properties.Contain + ? layerModel.LayerRect() + : new Rect(layerModel.Properties.X*4, layerModel.Properties.Y*4, + layerModel.Properties.Width*4, layerModel.Properties.Height*4); + + var clip = layerModel.LayerRect(); + + // Can't meddle with the original brush because it's frozen. + var brush = layerModel.Brush.Clone(); + brush.Opacity = layerModel.Opacity; + + c.PushClip(new RectangleGeometry(clip)); + c.DrawRectangle(brush, null, rect); + c.Pop(); + } } } \ No newline at end of file From ddb336f35a48fb0ed46f1bfb1e3c53df8994fe49 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 12 Jan 2017 19:50:56 +0100 Subject: [PATCH 4/5] Implemented BottomToTop again --- Artemis/Artemis/Artemis.csproj | 3 +- .../InjectionModules/ManagerModules.cs | 2 +- Artemis/Artemis/Managers/MainManager.cs | 6 +- .../{ProfileManager.cs => PreviewManager.cs} | 6 +- .../Modules/Abstract/ModuleViewModel.cs | 1 - .../Audio/AudioCapturing/AudioCapture.cs | 65 ++----------------- .../Profiles/Layers/Types/Audio/AudioType.cs | 8 +-- Artemis/Artemis/Utilities/EditorHelper.cs | 12 ++++ .../Artemis/ViewModels/GeneralViewModel.cs | 6 +- .../Profiles/ProfileEditorViewModel.cs | 58 +++++++++++------ .../ViewModels/Profiles/ProfileViewModel.cs | 17 ++--- Artemis/Artemis/ViewModels/ShellViewModel.cs | 2 +- .../Views/Profiles/ProfileEditorView.xaml | 2 +- 13 files changed, 77 insertions(+), 111 deletions(-) rename Artemis/Artemis/Managers/{ProfileManager.cs => PreviewManager.cs} (95%) create mode 100644 Artemis/Artemis/Utilities/EditorHelper.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 8f81676a9..3500de5f3 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -355,7 +355,7 @@ - + @@ -622,6 +622,7 @@ + diff --git a/Artemis/Artemis/InjectionModules/ManagerModules.cs b/Artemis/Artemis/InjectionModules/ManagerModules.cs index 0d5870d63..b01f7fab9 100644 --- a/Artemis/Artemis/InjectionModules/ManagerModules.cs +++ b/Artemis/Artemis/InjectionModules/ManagerModules.cs @@ -11,7 +11,7 @@ namespace Artemis.InjectionModules Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); - Bind().ToSelf().InSingletonScope(); + Bind().ToSelf().InSingletonScope(); Bind().ToSelf().InSingletonScope(); } } diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index 6dcd1490b..f4de0de5f 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -22,14 +22,14 @@ namespace Artemis.Managers private readonly Timer _processTimer; public MainManager(ILogger logger, LoopManager loopManager, DeviceManager deviceManager, - ModuleManager moduleManager, ProfileManager profileManager, PipeServer pipeServer, + ModuleManager moduleManager, PreviewManager previewManager, PipeServer pipeServer, GameStateWebServer gameStateWebServer) { Logger = logger; LoopManager = loopManager; DeviceManager = deviceManager; ModuleManager = moduleManager; - ProfileManager = profileManager; + PreviewManager = previewManager; PipeServer = pipeServer; _processTimer = new Timer(1000); @@ -64,7 +64,7 @@ namespace Artemis.Managers public LoopManager LoopManager { get; } public DeviceManager DeviceManager { get; set; } public ModuleManager ModuleManager { get; set; } - public ProfileManager ProfileManager { get; set; } + public PreviewManager PreviewManager { get; set; } public PipeServer PipeServer { get; set; } public GameStateWebServer GameStateWebServer { get; set; } diff --git a/Artemis/Artemis/Managers/ProfileManager.cs b/Artemis/Artemis/Managers/PreviewManager.cs similarity index 95% rename from Artemis/Artemis/Managers/ProfileManager.cs rename to Artemis/Artemis/Managers/PreviewManager.cs index ed0268ff8..ff5657762 100644 --- a/Artemis/Artemis/Managers/ProfileManager.cs +++ b/Artemis/Artemis/Managers/PreviewManager.cs @@ -9,7 +9,7 @@ using Ninject.Extensions.Logging; namespace Artemis.Managers { - public class ProfileManager + public class PreviewManager { private readonly DeviceManager _deviceManager; private readonly GeneralSettings _generalSettings; @@ -17,7 +17,7 @@ namespace Artemis.Managers private readonly LoopManager _loopManager; private readonly ModuleManager _moduleManager; - public ProfileManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, + public PreviewManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager, LoopManager loopManager) { _logger = logger; @@ -32,7 +32,7 @@ namespace Artemis.Managers profilePreviewTimer.Elapsed += SetupProfilePreview; profilePreviewTimer.Start(); - _logger.Info("Intialized ProfileManager"); + _logger.Info("Intialized PreviewManager"); } public List PreviewViewModules { get; set; } diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index a1a6fd435..55fdfef5d 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -26,7 +26,6 @@ namespace Artemis.Modules.Abstract _generalSettings = DAL.SettingsProvider.Load(); ModuleModel = moduleModel; Settings = moduleModel.Settings; - _mainManager.EnabledChanged += MainManagerOnEnabledChanged; _moduleManager.EffectChanged += ModuleManagerOnModuleChanged; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs index c1092fc31..0cdeafc7c 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioCapturing/AudioCapture.cs @@ -13,7 +13,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing public class AudioCapture { private const FftSize FftSize = CSCore.DSP.FftSize.Fft4096; - private readonly Timer _activityTimer; private readonly Timer _volumeTimer; private readonly double[] _volumeValues; private SingleSpectrum _singleSpectrum; @@ -31,17 +30,15 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _volumeValues = new double[5]; _volumeIndex = 0; - _activityTimer = new Timer(1000); - _activityTimer.Elapsed += ActivityTimerOnElapsed; _volumeTimer = new Timer(200); _volumeTimer.Elapsed += VolumeTimerOnElapsed; + + Start(); } - public double DesiredAverage { get; set; } - - public bool MayStop { get; set; } - public ILogger Logger { get; } + public MMDevice Device { get; } + public double DesiredAverage { get; set; } public float Volume { @@ -49,9 +46,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing set { _volume.Volume = value; } } - public MMDevice Device { get; } - public bool Running { get; set; } - private void VolumeTimerOnElapsed(object sender, ElapsedEventArgs e) { if (Volume <= 0) @@ -95,20 +89,6 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing } } - private void ActivityTimerOnElapsed(object sender, ElapsedEventArgs e) - { - // If MayStop is true for longer than a second, this will stop the audio capture - if (MayStop) - { - Stop(); - MayStop = false; - } - else - { - MayStop = true; - } - } - public LineSpectrum GetLineSpectrum(int barCount, ScalingStrategy scalingStrategy) { return new LineSpectrum(FftSize) @@ -121,39 +101,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing }; } - public void Stop() + private void Start() { - if (!Running) - return; - - Logger.Debug("Stopping audio capture for device: {0}", Device?.FriendlyName ?? "default"); - - try - { - _activityTimer.Stop(); - _volumeTimer.Stop(); - - _soundIn.Stop(); - _soundIn.Dispose(); - _source.Dispose(); - _soundIn = null; - _source = null; - - Running = false; - } - catch (Exception e) - { - Logger.Warn(e, "Failed to stop WASAPI audio capture"); - } - } - - public void Pulse() - { - MayStop = false; - - if (Running) - return; - Logger.Debug("Starting audio capture for device: {0}", Device?.FriendlyName ?? "default"); try @@ -188,12 +137,8 @@ namespace Artemis.Profiles.Layers.Types.Audio.AudioCapturing _singleSpectrum = new SingleSpectrum(FftSize, _spectrumProvider); - _activityTimer.Start(); _volumeTimer.Start(); - _soundIn.Start(); - Running = true; - MayStop = false; } catch (Exception e) { diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index 5bf340179..e983d0969 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -57,7 +57,7 @@ namespace Artemis.Profiles.Layers.Types.Audio switch (direction) { - case Direction.BottomToTop: + case Direction.TopToBottom: for (var index = 0; index < _lineValues.Count; index++) { var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); @@ -65,10 +65,11 @@ namespace Artemis.Profiles.Layers.Types.Audio barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); } break; - case Direction.TopToBottom: + case Direction.BottomToTop: for (var index = 0; index < _lineValues.Count; index++) { var clipRect = new Rect((parentX + index)*4, parentY*4, 4, _lineValues[index]*4); + clipRect.Y = clipRect.Y + layerModel.Height*4 - clipRect.Height; var barRect = new RectangleGeometry(clipRect); barGeometry = Geometry.Combine(barGeometry, barRect, CombineMode, Transform.Identity); } @@ -116,9 +117,6 @@ namespace Artemis.Profiles.Layers.Types.Audio currentHeight = layerModel.Width; } - // Let audio capture know it is being listened to - _audioCapture.Pulse(); - if (_lines != currentLines || _lineSpectrum == null) { _lines = currentLines; diff --git a/Artemis/Artemis/Utilities/EditorHelper.cs b/Artemis/Artemis/Utilities/EditorHelper.cs new file mode 100644 index 000000000..ba7ab2eb1 --- /dev/null +++ b/Artemis/Artemis/Utilities/EditorHelper.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Utilities +{ + public static class EditorHelper + { + } +} diff --git a/Artemis/Artemis/ViewModels/GeneralViewModel.cs b/Artemis/Artemis/ViewModels/GeneralViewModel.cs index f800712fe..c32531bf1 100644 --- a/Artemis/Artemis/ViewModels/GeneralViewModel.cs +++ b/Artemis/Artemis/ViewModels/GeneralViewModel.cs @@ -10,14 +10,14 @@ namespace Artemis.ViewModels { private readonly List _vms; - public GeneralViewModel(List moduleViewModels, ProfileManager profileManager) + public GeneralViewModel(List moduleViewModels, PreviewManager previewManager) { DisplayName = "General"; _vms = moduleViewModels.Where(m => !m.ModuleModel.IsOverlay && !m.ModuleModel.IsBoundToProcess) .OrderBy(m => m.DisplayName).ToList(); - profileManager.PreviewViewModules.Clear(); - profileManager.PreviewViewModules.AddRange(moduleViewModels.Where(m => m.UsesProfileEditor)); + previewManager.PreviewViewModules.Clear(); + previewManager.PreviewViewModules.AddRange(moduleViewModels.Where(m => m.UsesProfileEditor)); } protected override void OnActivate() diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index af0659dd9..734598963 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -45,6 +45,8 @@ namespace Artemis.ViewModels.Profiles private ObservableCollection _profileNames; private bool _saving; private FileSystemWatcher _watcher; + private ProfileViewModel _profileViewModel; + private LayerModel _selectedLayer; public ProfileEditorViewModel(DeviceManager deviceManager, LuaManager luaManager, ModuleModel moduleModel, ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService) @@ -62,13 +64,22 @@ namespace Artemis.ViewModels.Profiles ProfileViewModel.ModuleModel = _moduleModel; PropertyChanged += EditorStateHandler; - ProfileViewModel.PropertyChanged += LayerSelectedHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; LoadProfiles(); } - public ProfileViewModel ProfileViewModel { get; set; } + public ProfileViewModel ProfileViewModel + { + get { return _profileViewModel; } + set + { + if (Equals(value, _profileViewModel)) return; + _profileViewModel = value; + NotifyOfPropertyChange(); + NotifyOfPropertyChange(nameof(LayerSelected)); + } + } public bool EditorEnabled => @@ -76,6 +87,21 @@ namespace Artemis.ViewModels.Profiles _deviceManager.ActiveKeyboard != null; public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; + public LayerModel SelectedLayer + { + get { return _selectedLayer; } + set + { + if (_profileViewModel != null) + _profileViewModel.SelectedLayer = value; + if (Equals(value, _selectedLayer)) + return; + + _selectedLayer = value; + NotifyOfPropertyChange(() => SelectedLayer); + NotifyOfPropertyChange(() => LayerSelected); + } + } public ObservableCollection ProfileNames { @@ -126,7 +152,7 @@ namespace Artemis.ViewModels.Profiles public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; - public bool LayerSelected => SelectedProfile != null && ProfileViewModel.SelectedLayer != null; + public bool LayerSelected => SelectedProfile != null && SelectedLayer != null; public void DragOver(IDropInfo dropInfo) { @@ -230,7 +256,7 @@ namespace Artemis.ViewModels.Profiles public void EditLayerFromDoubleClick() { - if (ProfileViewModel.SelectedLayer?.LayerType is FolderType) + if (SelectedLayer?.LayerType is FolderType) return; EditLayer(); @@ -238,10 +264,10 @@ namespace Artemis.ViewModels.Profiles public void EditLayer() { - if (ProfileViewModel.SelectedLayer == null) + if (SelectedLayer == null) return; - var selectedLayer = ProfileViewModel.SelectedLayer; + var selectedLayer = SelectedLayer; EditLayer(selectedLayer); } @@ -290,7 +316,7 @@ namespace Artemis.ViewModels.Profiles if (SelectedProfile == null) return null; - var layer = SelectedProfile.AddLayer(ProfileViewModel.SelectedLayer); + var layer = SelectedProfile.AddLayer(SelectedLayer); UpdateLayerList(layer); return layer; @@ -314,7 +340,7 @@ namespace Artemis.ViewModels.Profiles /// public void RemoveLayer() { - RemoveLayer(ProfileViewModel.SelectedLayer); + RemoveLayer(SelectedLayer); } /// @@ -368,10 +394,10 @@ namespace Artemis.ViewModels.Profiles /// public void CloneLayer() { - if (ProfileViewModel.SelectedLayer == null) + if (SelectedLayer == null) return; - CloneLayer(ProfileViewModel.SelectedLayer); + CloneLayer(SelectedLayer); } /// @@ -390,7 +416,7 @@ namespace Artemis.ViewModels.Profiles { // Update the UI Layers.Clear(); - ProfileViewModel.SelectedLayer = null; + SelectedLayer = null; if (SelectedProfile != null) Layers.AddRange(SelectedProfile.Layers); @@ -402,7 +428,7 @@ namespace Artemis.ViewModels.Profiles Task.Factory.StartNew(() => { Thread.Sleep(100); - ProfileViewModel.SelectedLayer = selectModel; + SelectedLayer = selectModel; }); } @@ -680,14 +706,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => ProfileSelected); } - private void LayerSelectedHandler(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName != "SelectedLayer") - return; - - NotifyOfPropertyChange(() => LayerSelected); - } - public void SaveSelectedProfile() { if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs index bc74299a0..9487b026a 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs @@ -32,7 +32,6 @@ namespace Artemis.ViewModels.Profiles private DrawingImage _keyboardPreview; private Cursor _keyboardPreviewCursor; private bool _resizing; - private LayerModel _selectedLayer; private bool _showAll; public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) @@ -47,18 +46,9 @@ namespace Artemis.ViewModels.Profiles } public ModuleModel ModuleModel { get; set; } - public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; + public LayerModel SelectedLayer { get; set; } - public LayerModel SelectedLayer - { - get { return _selectedLayer; } - set - { - if (Equals(value, _selectedLayer)) return; - _selectedLayer = value; - NotifyOfPropertyChange(() => SelectedLayer); - } - } + public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; public DrawingImage KeyboardPreview { @@ -380,10 +370,13 @@ namespace Artemis.ViewModels.Profiles public void Dispose() { + Disposed = true; _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; } + public bool Disposed { get; set; } + #endregion } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/ShellViewModel.cs b/Artemis/Artemis/ViewModels/ShellViewModel.cs index c8b83a39f..4d59daf60 100644 --- a/Artemis/Artemis/ViewModels/ShellViewModel.cs +++ b/Artemis/Artemis/ViewModels/ShellViewModel.cs @@ -216,7 +216,7 @@ namespace Artemis.ViewModels dialog.SetIndeterminate(); while (MainManager.DeviceManager.ChangingKeyboard) - await Task.Delay(10); + await Task.Delay(1000); try { diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml index c4cfd119c..4a3c9e8b7 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml @@ -185,7 +185,7 @@ cal:Message.Attach="[Event MouseDoubleClick] = [Action EditLayerFromDoubleClick]"> + SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" /> Date: Sat, 14 Jan 2017 00:26:55 +0100 Subject: [PATCH 5/5] Added ProfileEditorModel Refactored some namespaces Got rid of ProfileViewModel --- Artemis/Artemis/Artemis.csproj | 15 +- .../Artemis/InjectionModules/BaseModules.cs | 10 +- Artemis/Artemis/Models/LayerEditorModel.cs | 12 + Artemis/Artemis/Models/ProfileEditorModel.cs | 354 +++++ .../Modules/Abstract/ModuleViewModel.cs | 1 + .../Abstract/LayerPropertiesViewModel.cs | 1 + .../Profiles/Layers/Interfaces/ILayerType.cs | 1 + .../AmbientLightPropertiesViewModel.cs | 1 + .../Types/AmbientLight/AmbientLightType.cs | 1 + .../Types/Audio/AudioPropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Audio/AudioType.cs | 1 + .../Types/Folder/FolderPropertiesViewModel.cs | 1 + .../Layers/Types/Folder/FolderType.cs | 1 + .../Generic/GenericPropertiesViewModel.cs | 1 + .../Layers/Types/Generic/GenericType.cs | 1 + .../Headset/HeadsetPropertiesViewModel.cs | 1 + .../Layers/Types/Headset/HeadsetType.cs | 1 + .../KeyPress/KeyPressPropertiesViewModel.cs | 1 + .../Layers/Types/KeyPress/KeyPressType.cs | 1 + .../Keyboard/KeyboardPropertiesViewModel.cs | 1 + .../Layers/Types/Keyboard/KeyboardType.cs | 1 + .../Types/KeyboardGif/KeyboardGifType.cs | 1 + .../Types/Mouse/MousePropertiesViewModel.cs | 1 + .../Profiles/Layers/Types/Mouse/MouseType.cs | 1 + .../Mousemat/MousematPropertiesViewModel.cs | 1 + .../Layers/Types/Mousemat/MousematType.cs | 1 + .../{Profiles => }/LayerEditorViewModel.cs | 13 +- .../{Profiles => }/ProfileEditorViewModel.cs | 1178 +++++++++-------- .../ViewModels/Profiles/ProfileViewModel.cs | 382 ------ .../Views/{Profiles => }/LayerEditorView.xaml | 2 +- .../{Profiles => }/LayerEditorView.xaml.cs | 2 +- .../{Profiles => }/ProfileEditorView.xaml | 583 ++++---- .../{Profiles => }/ProfileEditorView.xaml.cs | 2 +- 33 files changed, 1303 insertions(+), 1272 deletions(-) create mode 100644 Artemis/Artemis/Models/LayerEditorModel.cs create mode 100644 Artemis/Artemis/Models/ProfileEditorModel.cs rename Artemis/Artemis/ViewModels/{Profiles => }/LayerEditorViewModel.cs (95%) rename Artemis/Artemis/ViewModels/{Profiles => }/ProfileEditorViewModel.cs (52%) delete mode 100644 Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml (99%) rename Artemis/Artemis/Views/{Profiles => }/LayerEditorView.xaml.cs (89%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml (93%) rename Artemis/Artemis/Views/{Profiles => }/ProfileEditorView.xaml.cs (89%) diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 3500de5f3..6452856de 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -356,6 +356,8 @@ + + @@ -654,17 +656,16 @@ - - + - + @@ -709,7 +710,7 @@ LayerDynamicPropertiesView.xaml - + LayerEditorView.xaml @@ -721,7 +722,7 @@ LayerTweenView.xaml - + ProfileEditorView.xaml @@ -952,7 +953,7 @@ Designer MSBuild:Compile - + MSBuild:Compile Designer @@ -968,7 +969,7 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile diff --git a/Artemis/Artemis/InjectionModules/BaseModules.cs b/Artemis/Artemis/InjectionModules/BaseModules.cs index bd0108284..eb4bc0bad 100644 --- a/Artemis/Artemis/InjectionModules/BaseModules.cs +++ b/Artemis/Artemis/InjectionModules/BaseModules.cs @@ -1,4 +1,5 @@ using Artemis.DeviceProviders; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; @@ -8,7 +9,6 @@ using Artemis.Utilities.DataReaders; using Artemis.Utilities.GameState; using Artemis.ViewModels; using Artemis.ViewModels.Abstract; -using Artemis.ViewModels.Profiles; using Ninject.Extensions.Conventions; using Ninject.Modules; @@ -18,10 +18,16 @@ namespace Artemis.InjectionModules { public override void Load() { + #region Models + + Bind().ToSelf(); + Bind().ToSelf(); + + #endregion + #region ViewModels Bind().ToSelf().InSingletonScope(); - Bind().ToSelf(); Bind().ToSelf(); Bind().ToSelf().InSingletonScope(); Kernel.Bind(x => diff --git a/Artemis/Artemis/Models/LayerEditorModel.cs b/Artemis/Artemis/Models/LayerEditorModel.cs new file mode 100644 index 000000000..c8c374310 --- /dev/null +++ b/Artemis/Artemis/Models/LayerEditorModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Models +{ + public class LayerEditorModel + { + } +} diff --git a/Artemis/Artemis/Models/ProfileEditorModel.cs b/Artemis/Artemis/Models/ProfileEditorModel.cs new file mode 100644 index 000000000..caefa2926 --- /dev/null +++ b/Artemis/Artemis/Models/ProfileEditorModel.cs @@ -0,0 +1,354 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Artemis.DAL; +using Artemis.Managers; +using Artemis.Modules.Abstract; +using Artemis.Profiles; +using Artemis.Profiles.Layers.Models; +using Artemis.Profiles.Layers.Types.Folder; +using Artemis.Properties; +using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels; +using Ninject.Parameters; + +namespace Artemis.Models +{ + public class ProfileEditorModel : IDisposable + { + private readonly DeviceManager _deviceManager; + private readonly LuaManager _luaManager; + private readonly DialogService _dialogService; + private readonly WindowService _windowService; + private FileSystemWatcher _watcher; + private ProfileModel _luaProfile; + + public ProfileEditorModel(WindowService windowService, MetroDialogService dialogService, + DeviceManager deviceManager, LuaManager luaManager) + { + _windowService = windowService; + _dialogService = dialogService; + _deviceManager = deviceManager; + _luaManager = luaManager; + } + + #region Layers + + /// + /// Opens a new LayerEditorView for the given layer + /// + /// The layer to open the view for + /// The datamodel to bind the editor to + public void EditLayer(LayerModel layer, ModuleDataModel dataModel) + { + IParameter[] args = + { + new ConstructorArgument("dataModel", dataModel), + new ConstructorArgument("layer", layer) + }; + _windowService.ShowDialog(args); + + // If the layer was a folder, but isn't anymore, assign it's children to it's parent. + if (layer.LayerType is FolderType || !layer.Children.Any()) + return; + + while (layer.Children.Any()) + { + var child = layer.Children[0]; + layer.Children.Remove(child); + if (layer.Parent != null) + { + layer.Parent.Children.Add(child); + layer.Parent.FixOrder(); + } + else + { + layer.Profile.Layers.Add(child); + layer.Profile.FixOrder(); + } + } + } + + /// + /// Removes the given layer from the profile + /// + /// The layer to remove + /// The profile to remove it from + public void RemoveLayer(LayerModel layer, ProfileModel profileModel) + { + if (layer == null) + return; + + if (layer.Parent != null) + { + var parent = layer.Parent; + layer.Parent.Children.Remove(layer); + parent.FixOrder(); + } + else if (layer.Profile != null) + { + var profile = layer.Profile; + layer.Profile.Layers.Remove(layer); + profile.FixOrder(); + } + + // Extra cleanup in case of a wonky layer that has no parent + if (profileModel.Layers.Contains(layer)) + profileModel.Layers.Remove(layer); + } + + #endregion + + #region Profiles + + public async Task AddProfile(ModuleModel moduleModel) + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return null; + } + + var name = await GetValidProfileName("Name profile", "Please enter a unique name for your new profile"); + // User cancelled + if (name == null) + return null; + + var profile = new ProfileModel + { + Name = name, + KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, + Width = _deviceManager.ActiveKeyboard.Width, + Height = _deviceManager.ActiveKeyboard.Height, + GameName = moduleModel.Name + }; + + if (!ProfileProvider.IsProfileUnique(profile)) + { + var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", + "A profile with this name already exists for this game. Would you like to overwrite it?"); + if (!overwrite.Value) + return null; + } + + ProfileProvider.AddOrUpdate(profile); + return profile; + } + + public async Task RenameProfile(ProfileModel profileModel) + { + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + ProfileProvider.RenameProfile(profileModel, profileModel.Name); + } + + public async Task DuplicateProfile(ProfileModel selectedProfile) + { + var newProfile = GeneralHelpers.Clone(selectedProfile); + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(newProfile, name, newProfile.Name); + if (!doRename) + return; + + // Make sure it's not default, in case of copying a default profile + newProfile.IsDefault = false; + ProfileProvider.AddOrUpdate(newProfile); + } + + public async Task DeleteProfile(ProfileModel selectedProfile, ModuleModel moduleModel) + { + var confirm = await _dialogService.ShowQuestionMessageBox("Delete profile", + $"Are you sure you want to delete the profile named: {selectedProfile.Name}?\n\n" + + "This cannot be undone."); + if (!confirm.Value) + return; + + var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, moduleModel, "Default"); + var deleteProfile = selectedProfile; + + moduleModel.ChangeProfile(defaultProfile); + ProfileProvider.DeleteProfile(deleteProfile); + } + + public async Task ImportProfile(ModuleModel moduleModel) + { + var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + var profileModel = ProfileProvider.LoadProfileIfValid(dialog.FileName); + if (profileModel == null) + { + _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + + "If this keeps happening, please make an issue on GitHub and provide the profile."); + return; + } + + // Verify the game + if (profileModel.GameName != moduleModel.Name) + { + _dialogService.ShowErrorMessageBox( + $"Oh oops! This profile is ment for {profileModel.GameName}, not {moduleModel.Name} :c"); + return; + } + + // Verify the keyboard + var deviceManager = _deviceManager; + if (profileModel.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) + { + var adjustKeyboard = await _dialogService.ShowQuestionMessageBox("Profile not made for this keyboard", + $"Watch out, this profile wasn't ment for this keyboard, but for the {profileModel.KeyboardSlug}. " + + "You can still import it but you'll probably have to do some adjusting\n\n" + + "Continue?"); + if (!adjustKeyboard.Value) + return; + + // Resize layers that are on the full keyboard width + profileModel.ResizeLayers(deviceManager.ActiveKeyboard); + // Put layers back into the canvas if they fell outside it + profileModel.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); + + // Setup profile metadata to match the new keyboard + profileModel.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; + profileModel.Width = deviceManager.ActiveKeyboard.Width; + profileModel.Height = deviceManager.ActiveKeyboard.Height; + } + + var name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + // User cancelled + if (name == null) + return; + var doRename = await MakeProfileUnique(profileModel, name, profileModel.Name); + if (!doRename) + return; + + profileModel.IsDefault = false; + ProfileProvider.AddOrUpdate(profileModel); + } + + private async Task GetValidProfileName(string title, string text) + { + var name = await _dialogService.ShowInputDialog(title, text); + + // Null when the user cancelled + if (name == null) + return null; + + if (name.Length >= 2) + return name; + + _dialogService.ShowMessageBox("Invalid profile name", + "Please provide a valid profile name that's longer than 2 symbols"); + + return await GetValidProfileName(title, text); + } + + private async Task MakeProfileUnique(ProfileModel profileModel, string name, string oldName) + { + profileModel.Name = name; + if (ProfileProvider.IsProfileUnique(profileModel)) + return true; + + name = await GetValidProfileName("Rename profile", "Please enter a unique new profile name"); + if (name != null) + return await MakeProfileUnique(profileModel, name, oldName); + + // If cancelled, restore old name and stop + profileModel.Name = oldName; + return false; + } + + #endregion + + #region LUA + + public void OpenLuaEditor(ProfileModel profileModel) + { + // Clean up old environment + DisposeLuaWatcher(); + + // Create a temp file + var fileName = Guid.NewGuid() + ".lua"; + var file = File.Create(Path.GetTempPath() + fileName); + file.Dispose(); + + // Add instructions to LUA script if it's a new file + if (string.IsNullOrEmpty(profileModel.LuaScript)) + profileModel.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); + File.WriteAllText(Path.GetTempPath() + fileName, profileModel.LuaScript); + + // Watch the file for changes + _luaProfile = profileModel; + _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); + _watcher.Changed += LuaFileChanged; + _watcher.EnableRaisingEvents = true; + _watcher.Path = Path.GetTempPath(); + _watcher.Filter = fileName; + + // Open the temp file with the default editor + System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); + } + + private void LuaFileChanged(object sender, FileSystemEventArgs args) + { + if (_luaProfile == null) + { + DisposeLuaWatcher(); + return; + } + + if (args.ChangeType != WatcherChangeTypes.Changed) + return; + + lock (_luaProfile) + { + using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var sr = new StreamReader(fs)) + { + _luaProfile.LuaScript = sr.ReadToEnd(); + } + } + + ProfileProvider.AddOrUpdate(_luaProfile); + _luaManager.SetupLua(_luaProfile); + } + } + + private void DisposeLuaWatcher() + { + if (_watcher == null) return; + _watcher.Changed -= LuaFileChanged; + _watcher.Dispose(); + _watcher = null; + } + + public void Dispose() + { + DisposeLuaWatcher(); + } + + #endregion + + #region Rendering + + + + #endregion + } +} \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs index 55fdfef5d..fa0eeaca5 100644 --- a/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs +++ b/Artemis/Artemis/Modules/Abstract/ModuleViewModel.cs @@ -2,6 +2,7 @@ using Artemis.Managers; using Artemis.Services; using Artemis.Settings; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; using Ninject; diff --git a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs index 33ea330e7..3669713a0 100644 --- a/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Abstract/LayerPropertiesViewModel.cs @@ -1,5 +1,6 @@ using System.Windows.Media; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs index 83f8c9185..0faba2b3c 100644 --- a/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs +++ b/Artemis/Artemis/Profiles/Layers/Interfaces/ILayerType.cs @@ -2,6 +2,7 @@ using Artemis.Modules.Abstract; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs index 7e71757d0..d4fcf84c1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.AmbientLight diff --git a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs index d1170138b..01c4922ee 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/AmbientLight/AmbientLightType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Types.AmbientLight.Model.Extensions; using Artemis.Profiles.Layers.Types.AmbientLight.ScreenCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Newtonsoft.Json; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs index ca276c666..079b386b4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs index e983d0969..a115f9bcb 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Audio/AudioType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Audio.AudioCapturing; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Audio diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs index 318662690..ca33f411a 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs index 52f5668c2..cfec51a00 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Folder/FolderType.cs @@ -6,6 +6,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Folder diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs index 80c7aa4ec..3de180e31 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs index 04a01016e..f7ca251d5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Generic/GenericType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Generic diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs index e6db95ab0..5e81c97c5 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs index 1df4777bc..3c99e55f4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Headset/HeadsetType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Headset diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs index 9efbd9907..352f7c45f 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressPropertiesViewModel.cs @@ -1,4 +1,5 @@ using Artemis.Profiles.Layers.Abstract; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs index 74f4d7cb0..a2a366ec4 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyPress/KeyPressType.cs @@ -13,6 +13,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; using Artemis.Utilities.Keyboard; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyPress diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs index c84b3c3aa..eeb7943b7 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardPropertiesViewModel.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs index 6e6627d43..2ace6c684 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Keyboard/KeyboardType.cs @@ -5,6 +5,7 @@ using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Animations; using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Keyboard diff --git a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs index bd0527a47..ef3e201e2 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/KeyboardGif/KeyboardGifType.cs @@ -9,6 +9,7 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.KeyboardGif diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs index 00d683cc8..d88b692f9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MousePropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs index df6d46592..31a2d4fa1 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mouse/MouseType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mouse diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs index 65c42d6a2..b960966e9 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematPropertiesViewModel.cs @@ -1,6 +1,7 @@ using System.Linq; using Artemis.Profiles.Layers.Abstract; using Artemis.Profiles.Layers.Interfaces; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; using Caliburn.Micro; diff --git a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs index 58583e475..f1379e754 100644 --- a/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs +++ b/Artemis/Artemis/Profiles/Layers/Types/Mousemat/MousematType.cs @@ -8,6 +8,7 @@ using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Properties; using Artemis.Utilities; +using Artemis.ViewModels; using Artemis.ViewModels.Profiles; namespace Artemis.Profiles.Layers.Types.Mousemat diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs similarity index 95% rename from Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs rename to Artemis/Artemis/ViewModels/LayerEditorViewModel.cs index 035fd42ad..1c387abe2 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/LayerEditorViewModel.cs @@ -10,13 +10,14 @@ using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Keyboard; using Artemis.Profiles.Layers.Types.KeyboardGif; using Artemis.Services; +using Artemis.Utilities; +using Artemis.ViewModels.Profiles; using Artemis.ViewModels.Profiles.Events; using Caliburn.Micro; using Newtonsoft.Json; using Ninject; -using static Artemis.Utilities.GeneralHelpers; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class LayerEditorViewModel : Screen { @@ -30,13 +31,13 @@ namespace Artemis.ViewModels.Profiles IEnumerable layerAnimations) { Layer = layer; - ProposedLayer = Clone(layer); + ProposedLayer = GeneralHelpers.Clone(layer); ProposedLayer.Children.Clear(); DataModel = DataModel; LayerTypes = new BindableCollection(types.OrderBy(t => t.Name)); LayerAnimations = layerAnimations.OrderBy(l => l.Name).ToList(); - DataModelProps = new BindableCollection(GenerateTypeMap(dataModel)); + DataModelProps = new BindableCollection(GeneralHelpers.GenerateTypeMap(dataModel)); if (Layer.Properties == null) Layer.SetupProperties(); @@ -57,7 +58,7 @@ namespace Artemis.ViewModels.Profiles public MetroDialogService DialogService { get; set; } public BindableCollection LayerTypes { get; set; } - public BindableCollection DataModelProps { get; set; } + public BindableCollection DataModelProps { get; set; } public BindableCollection LayerConditionVms { get; set; } public bool KeyboardGridIsVisible => ProposedLayer.LayerType is KeyboardType; public bool GifGridIsVisible => ProposedLayer.LayerType is KeyboardGifType; @@ -215,7 +216,7 @@ namespace Artemis.ViewModels.Profiles // Ignore the children, can't just temporarily add them to the proposed layer because // that would upset the child layers' relations (sounds like Dr. Phil amirite?) - var currentObj = Clone(Layer); + var currentObj = GeneralHelpers.Clone(Layer); currentObj.Children.Clear(); // Apply the IsEvent boolean diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs similarity index 52% rename from Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs rename to Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs index 734598963..2b1da49ae 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/ProfileEditorViewModel.cs @@ -1,11 +1,12 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; @@ -13,8 +14,10 @@ using Artemis.DAL; using Artemis.DeviceProviders; using Artemis.Events; using Artemis.Managers; +using Artemis.Models; using Artemis.Modules.Abstract; using Artemis.Profiles; +using Artemis.Profiles.Layers.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Profiles.Layers.Types.Folder; using Artemis.Properties; @@ -22,86 +25,84 @@ using Artemis.Services; using Artemis.Styles.DropTargetAdorners; using Artemis.Utilities; using Caliburn.Micro; +using Castle.Components.DictionaryAdapter; using GongSolutions.Wpf.DragDrop; +using MahApps.Metro; using MahApps.Metro.Controls.Dialogs; -using Ninject.Parameters; using NuGet; +using Application = System.Windows.Application; +using Cursor = System.Windows.Input.Cursor; +using Cursors = System.Windows.Input.Cursors; using DragDropEffects = System.Windows.DragDropEffects; using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using Screen = Caliburn.Micro.Screen; -namespace Artemis.ViewModels.Profiles +namespace Artemis.ViewModels { public sealed class ProfileEditorViewModel : Screen, IDropTarget, IDisposable { private readonly DeviceManager _deviceManager; private readonly MetroDialogService _dialogService; - private readonly LuaManager _luaManager; + private readonly LoopManager _loopManager; private readonly ModuleModel _moduleModel; - private readonly WindowService _windowService; private ImageSource _keyboardPreview; private ObservableCollection _layers; private ObservableCollection _profileNames; private bool _saving; - private FileSystemWatcher _watcher; - private ProfileViewModel _profileViewModel; private LayerModel _selectedLayer; + private bool _showAll; - public ProfileEditorViewModel(DeviceManager deviceManager, LuaManager luaManager, ModuleModel moduleModel, - ProfileViewModel profileViewModel, MetroDialogService dialogService, WindowService windowService) + public ProfileEditorViewModel(ProfileEditorModel profileEditorModel, DeviceManager deviceManager, + LoopManager loopManager, ModuleModel moduleModel, MetroDialogService dialogService) { _deviceManager = deviceManager; - _luaManager = luaManager; + _loopManager = loopManager; _moduleModel = moduleModel; _dialogService = dialogService; - _windowService = windowService; ProfileNames = new ObservableCollection(); Layers = new ObservableCollection(); - ProfileViewModel = profileViewModel; - - ProfileViewModel.ModuleModel = _moduleModel; + ProfileEditorModel = profileEditorModel; PropertyChanged += EditorStateHandler; _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; _moduleModel.ProfileChanged += ModuleModelOnProfileChanged; + _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; LoadProfiles(); } - public ProfileViewModel ProfileViewModel + public void Dispose() { - get { return _profileViewModel; } - set + SaveSelectedProfile(); + ProfileEditorModel.Dispose(); + _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; + _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; + } + + #region LUA + + public void EditLua() + { + if (SelectedProfile == null) + return; + try { - if (Equals(value, _profileViewModel)) return; - _profileViewModel = value; - NotifyOfPropertyChange(); - NotifyOfPropertyChange(nameof(LayerSelected)); + ProfileEditorModel.OpenLuaEditor(SelectedProfile); + } + catch (Exception e) + { + _dialogService.ShowMessageBox("Couldn't open LUA file", + "Please make sure you have a text editor associated with the .lua extension.\n\n" + + "Windows error message: \n" + e.Message); } } - public bool EditorEnabled - => - SelectedProfile != null && !SelectedProfile.IsDefault && - _deviceManager.ActiveKeyboard != null; + #endregion - public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; - public LayerModel SelectedLayer - { - get { return _selectedLayer; } - set - { - if (_profileViewModel != null) - _profileViewModel.SelectedLayer = value; - if (Equals(value, _selectedLayer)) - return; - - _selectedLayer = value; - NotifyOfPropertyChange(() => SelectedLayer); - NotifyOfPropertyChange(() => LayerSelected); - } - } + #region Properties + + public ProfileEditorModel ProfileEditorModel { get; } public ObservableCollection ProfileNames { @@ -125,6 +126,29 @@ namespace Artemis.ViewModels.Profiles } } + + public ImageSource KeyboardPreview + { + get { return _keyboardPreview; } + set + { + if (Equals(value, _keyboardPreview)) return; + _keyboardPreview = value; + NotifyOfPropertyChange(() => KeyboardPreview); + } + } + + public bool ShowAll + { + get { return _showAll; } + set + { + if (value == _showAll) return; + _showAll = value; + NotifyOfPropertyChange(); + } + } + public string SelectedProfileName { get { return SelectedProfile?.Name; } @@ -138,22 +162,542 @@ namespace Artemis.ViewModels.Profiles } } - public ImageSource KeyboardPreview + public LayerModel SelectedLayer { - get { return _keyboardPreview; } + get { return _selectedLayer; } set { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); + if (Equals(value, _selectedLayer)) + return; + + _selectedLayer = value; + NotifyOfPropertyChange(() => SelectedLayer); + NotifyOfPropertyChange(() => LayerSelected); } } - public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; + public ImageSource KeyboardImage => ImageUtilities.BitmapToBitmapImage( + _deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); + public ProfileModel SelectedProfile => _moduleModel?.ProfileModel; + public PreviewSettings? PreviewSettings => _deviceManager.ActiveKeyboard?.PreviewSettings; public bool ProfileSelected => SelectedProfile != null; public bool LayerSelected => SelectedProfile != null && SelectedLayer != null; + public bool EditorEnabled => SelectedProfile != null && !SelectedProfile.IsDefault && + _deviceManager.ActiveKeyboard != null; + + #endregion + + #region Layers + + public void EditLayerFromDoubleClick() + { + if (SelectedLayer?.LayerType is FolderType) + return; + + EditLayer(); + } + + public void EditLayer() + { + if (SelectedLayer == null) + return; + + ProfileEditorModel.EditLayer(SelectedLayer, _moduleModel.DataModel); + UpdateLayerList(SelectedLayer); + } + + public LayerModel AddLayer() + { + if (SelectedProfile == null) + return null; + + var layer = SelectedProfile.AddLayer(SelectedLayer); + UpdateLayerList(layer); + + return layer; + } + + public LayerModel AddFolder() + { + if (SelectedProfile == null) + return null; + + var layer = AddLayer(); + if (layer == null) + return null; + + layer.Name = "New folder"; + layer.LayerType = new FolderType(); + layer.LayerType.SetupProperties(layer); + + return layer; + } + + public void RemoveLayer() + { + RemoveLayer(SelectedLayer); + } + + public void RemoveLayer(LayerModel layer) + { + ProfileEditorModel.RemoveLayer(layer, SelectedProfile); + UpdateLayerList(null); + } + + public async void RenameLayer(LayerModel layer) + { + if (layer == null) + return; + + var newName = await _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", + new MetroDialogSettings {DefaultText = layer.Name}); + + // Null when the user cancelled + if (string.IsNullOrEmpty(newName)) + return; + + layer.Name = newName; + UpdateLayerList(layer); + } + + /// + /// Clones the currently selected layer and adds it to the profile, after the original + /// + public void CloneLayer() + { + if (SelectedLayer == null) + return; + + CloneLayer(SelectedLayer); + } + + /// + /// Clones the given layer and adds it to the profile, after the original + /// + /// + public void CloneLayer(LayerModel layer) + { + var clone = GeneralHelpers.Clone(layer); + layer.InsertAfter(clone); + + UpdateLayerList(clone); + } + + private void UpdateLayerList(LayerModel selectModel) + { + // Update the UI + Layers.Clear(); + SelectedLayer = null; + + if (SelectedProfile != null) + Layers.AddRange(SelectedProfile.Layers); + + if (selectModel == null) + return; + + // A small delay to allow the profile list to rebuild + Task.Factory.StartNew(() => + { + Thread.Sleep(100); + SelectedLayer = selectModel; + }); + } + + #endregion + + #region Profiles + + /// + /// Loads all profiles for the current game and keyboard + /// + private void LoadProfiles() + { + Execute.OnUIThread(() => + { + ProfileNames.Clear(); + if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) + ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); + + NotifyOfPropertyChange(() => SelectedProfile); + }); + } + + public void SaveSelectedProfile() + { + if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) + return; + + _saving = true; + try + { + ProfileProvider.AddOrUpdate(SelectedProfile); + } + catch (Exception) + { + // ignored + } + _saving = false; + } + + public async void AddProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot add profile.", + "To add a profile, please select a keyboard in the options menu first."); + return; + } + + var profile = await ProfileEditorModel.AddProfile(_moduleModel); + if (profile == null) + return; + + LoadProfiles(); + } + + public async void RenameProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.RenameProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DuplicateProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DuplicateProfile(SelectedProfile); + LoadProfiles(); + } + + public async void DeleteProfile() + { + if (SelectedProfile == null) + return; + + await ProfileEditorModel.DeleteProfile(SelectedProfile, _moduleModel); + LoadProfiles(); + } + + public async void ImportProfile() + { + if (_deviceManager.ActiveKeyboard == null) + { + _dialogService.ShowMessageBox("Cannot import profile.", + "To import a profile, please select a keyboard in the options menu first."); + return; + } + + await ProfileEditorModel.ImportProfile(_moduleModel); + LoadProfiles(); + } + + public void ExportProfile() + { + if (SelectedProfile == null) + return; + + var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; + var result = dialog.ShowDialog(); + if (result != DialogResult.OK) + return; + + ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); + } + + #endregion + + #region Rendering + + private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) + { + // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard + if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || + _deviceManager.ActiveKeyboard.Slug == "none") + { + KeyboardPreview = null; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = new List(); + + return; + } + + var renderLayers = GetRenderLayers(); + // Draw the current frame to the preview + var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); + var visual = new DrawingVisual(); + using (var drawingContext = visual.RenderOpen()) + { + // Setup the DrawingVisual's size + drawingContext.PushClip(new RectangleGeometry(keyboardRect)); + drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); + + // Draw the layers + foreach (var layer in renderLayers) + { + layer.Update(null, true, false); + if (layer.LayerType.ShowInEdtor) + layer.Draw(null, drawingContext, true, false); + } + + // Get the selection color + var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; + if (accentColor == null) + { + var preview = new DrawingImage(); + preview.Freeze(); + KeyboardPreview = preview; + return; + } + + var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); + + // Draw the selection outline and resize indicator + if (SelectedLayer != null && SelectedLayer.MustDraw()) + { + var layerRect = SelectedLayer.Properties.PropertiesRect(); + // Deflate the rect so that the border is drawn on the inside + layerRect.Inflate(-0.2, -0.2); + + // Draw an outline around the selected layer + drawingContext.DrawRectangle(null, pen, layerRect); + // Draw a resize indicator in the bottom-right + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); + drawingContext.DrawLine(pen, + new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), + new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); + } + + SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, + drawingContext)); + + // Remove the clip + drawingContext.Pop(); + } + var drawnPreview = new DrawingImage(visual.Drawing); + drawnPreview.Freeze(); + KeyboardPreview = drawnPreview; + + // Setup layers for the next frame + if (_moduleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) + _moduleModel.PreviewLayers = renderLayers; + } + + public List GetRenderLayers() + { + // Get the layers that must be drawn + List drawLayers; + if (ShowAll) + return SelectedProfile.GetRenderLayers(null, false, true); + + if (SelectedLayer == null || !SelectedLayer.Enabled) + return new EditableList(); + + if (SelectedLayer.LayerType is FolderType) + drawLayers = SelectedLayer.GetRenderLayers(null, false, true); + else + drawLayers = new List {SelectedLayer}; + + return drawLayers; + } + + #endregion + + #region Mouse actions + + private DateTime _downTime; + private LayerModel _draggingLayer; + private Point? _draggingLayerOffset; + private Cursor _keyboardPreviewCursor; + private bool _resizing; + + /// + /// Handler for clicking + /// + /// + public void MouseDownKeyboardPreview(MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + _downTime = DateTime.Now; + } + + /// + /// Second handler for clicking, selects a the layer the user clicked on. + /// + /// + public void MouseUpKeyboardPreview(MouseButtonEventArgs e) + { + if (SelectedProfile == null || SelectedProfile.IsDefault) + return; + + var timeSinceDown = DateTime.Now - _downTime; + if (!(timeSinceDown.TotalMilliseconds < 500)) + return; + if (_draggingLayer != null) + return; + + var keyboard = _deviceManager.ActiveKeyboard; + var pos = e.GetPosition((Image) e.OriginalSource); + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + if (hoverLayer != null) + SelectedLayer = hoverLayer; + } + + /// + /// Handler for resizing and moving the currently selected layer + /// + /// + public void MouseMoveKeyboardPreview(MouseEventArgs e) + { + if (SelectedProfile == null) + return; + + var pos = e.GetPosition((Image) e.OriginalSource); + var keyboard = _deviceManager.ActiveKeyboard; + var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); + var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); + var hoverLayer = GetLayers().Where(l => l.MustDraw()) + .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); + + HandleDragging(e, x, y, hoverLayer); + + if (hoverLayer == null) + { + KeyboardPreviewCursor = Cursors.Arrow; + return; + } + + // Turn the mouse pointer into a hand if hovering over an active layer + if (hoverLayer == SelectedLayer) + { + var rect = hoverLayer.Properties.PropertiesRect(1); + KeyboardPreviewCursor = + Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 + ? Cursors.SizeNWSE + : Cursors.SizeAll; + } + else + { + KeyboardPreviewCursor = Cursors.Hand; + } + } + + public Cursor KeyboardPreviewCursor + { + get { return _keyboardPreviewCursor; } + set + { + if (Equals(value, _keyboardPreviewCursor)) return; + _keyboardPreviewCursor = value; + NotifyOfPropertyChange(() => KeyboardPreviewCursor); + } + } + + /// + /// Handles dragging the given layer + /// + /// + /// + /// + /// + private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) + { + // Reset the dragging state on mouse release + if (e.LeftButton == MouseButtonState.Released || + _draggingLayer != null && SelectedLayer != _draggingLayer) + { + _draggingLayerOffset = null; + _draggingLayer = null; + return; + } + + if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) + return; + + // Setup the dragging state on mouse press + if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) + { + var layerRect = hoverLayer.Properties.PropertiesRect(1); + + _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); + _draggingLayer = hoverLayer; + // Detect dragging if cursor is in the bottom right + _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + + Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; + } + + if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) + return; + + var draggingProps = _draggingLayer.Properties; + // If no setup or reset was done, handle the actual dragging action + if (_resizing) + { + var newWidth = Math.Round(x - draggingProps.X); + var newHeight = Math.Round(y - draggingProps.Y); + + // Ensure the layer doesn't leave the canvas + if (newWidth < 1 || draggingProps.X + newWidth <= 0) + newWidth = draggingProps.Width; + if (newHeight < 1 || draggingProps.Y + newHeight <= 0) + newHeight = draggingProps.Height; + + draggingProps.Width = newWidth; + draggingProps.Height = newHeight; + } + else + { + var newX = Math.Round(x - _draggingLayerOffset.Value.X); + var newY = Math.Round(y - _draggingLayerOffset.Value.Y); + + // Ensure the layer doesn't leave the canvas + if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) + newX = draggingProps.X; + if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) + newY = draggingProps.Y; + + draggingProps.X = newX; + draggingProps.Y = newY; + } + } + + private List GetLayers() + { + if (ShowAll) + return SelectedProfile.GetLayers(); + if (SelectedLayer == null) + return new List(); + + lock (SelectedLayer) + { + // Get the layers that must be drawn + if (SelectedLayer.LayerType is FolderType) + return SelectedLayer.GetLayers().ToList(); + return new List {SelectedLayer}; + } + } + + #endregion + + #region Event handles + public void DragOver(IDropInfo dropInfo) { var source = dropInfo.Data as LayerModel; @@ -226,7 +770,6 @@ namespace Artemis.ViewModels.Profiles { NotifyOfPropertyChange(() => SelectedProfileName); NotifyOfPropertyChange(() => SelectedProfile); - NotifyOfPropertyChange(() => ProfileViewModel.SelectedProfile); } /// @@ -235,461 +778,10 @@ namespace Artemis.ViewModels.Profiles private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) { NotifyOfPropertyChange(() => PreviewSettings); + NotifyOfPropertyChange(() => KeyboardImage); LoadProfiles(); } - /// - /// Loads all profiles for the current game and keyboard - /// - private void LoadProfiles() - { - Execute.OnUIThread(() => - { - ProfileNames.Clear(); - if (_moduleModel != null && _deviceManager.ActiveKeyboard != null) - ProfileNames.AddRange(ProfileProvider.GetProfileNames(_deviceManager.ActiveKeyboard, _moduleModel)); - - NotifyOfPropertyChange(() => SelectedProfile); - }); - } - - - public void EditLayerFromDoubleClick() - { - if (SelectedLayer?.LayerType is FolderType) - return; - - EditLayer(); - } - - public void EditLayer() - { - if (SelectedLayer == null) - return; - - var selectedLayer = SelectedLayer; - EditLayer(selectedLayer); - } - - /// - /// Opens a new LayerEditorView for the given layer - /// - /// The layer to open the view for - public void EditLayer(LayerModel layer) - { - if (layer == null) - return; - - IParameter[] args = - { - new ConstructorArgument("dataModel", _moduleModel.DataModel), - new ConstructorArgument("layer", layer) - }; - _windowService.ShowDialog(args); - - // If the layer was a folder, but isn't anymore, assign it's children to it's parent. - if (!(layer.LayerType is FolderType) && layer.Children.Any()) - while (layer.Children.Any()) - { - var child = layer.Children[0]; - layer.Children.Remove(child); - if (layer.Parent != null) - { - layer.Parent.Children.Add(child); - layer.Parent.FixOrder(); - } - else - { - layer.Profile.Layers.Add(child); - layer.Profile.FixOrder(); - } - } - - UpdateLayerList(layer); - } - - /// - /// Adds a new layer to the profile and selects it - /// - public LayerModel AddLayer() - { - if (SelectedProfile == null) - return null; - - var layer = SelectedProfile.AddLayer(SelectedLayer); - UpdateLayerList(layer); - - return layer; - } - - public LayerModel AddFolder() - { - var layer = AddLayer(); - if (layer == null) - return null; - - layer.Name = "New folder"; - layer.LayerType = new FolderType(); - layer.LayerType.SetupProperties(layer); - - return layer; - } - - /// - /// Removes the currently selected layer from the profile - /// - public void RemoveLayer() - { - RemoveLayer(SelectedLayer); - } - - /// - /// Removes the given layer from the profile - /// - /// - public void RemoveLayer(LayerModel layer) - { - if (layer == null) - return; - - if (layer.Parent != null) - { - var parent = layer.Parent; - layer.Parent.Children.Remove(layer); - parent.FixOrder(); - } - else if (layer.Profile != null) - { - var profile = layer.Profile; - layer.Profile.Layers.Remove(layer); - profile.FixOrder(); - } - - // Extra cleanup in case of a wonky layer that has no parent - if (SelectedProfile.Layers.Contains(layer)) - SelectedProfile.Layers.Remove(layer); - - UpdateLayerList(null); - } - - public async void RenameLayer(LayerModel layer) - { - if (layer == null) - return; - - var newName = - await - _dialogService.ShowInputDialog("Rename layer", "Please enter a name for the layer", - new MetroDialogSettings {DefaultText = layer.Name}); - // Null when the user cancelled - if (string.IsNullOrEmpty(newName)) - return; - - layer.Name = newName; - UpdateLayerList(layer); - } - - /// - /// Clones the currently selected layer and adds it to the profile, after the original - /// - public void CloneLayer() - { - if (SelectedLayer == null) - return; - - CloneLayer(SelectedLayer); - } - - /// - /// Clones the given layer and adds it to the profile, after the original - /// - /// - public void CloneLayer(LayerModel layer) - { - var clone = GeneralHelpers.Clone(layer); - layer.InsertAfter(clone); - - UpdateLayerList(clone); - } - - private void UpdateLayerList(LayerModel selectModel) - { - // Update the UI - Layers.Clear(); - SelectedLayer = null; - - if (SelectedProfile != null) - Layers.AddRange(SelectedProfile.Layers); - - if (selectModel == null) - return; - - // A small delay to allow the profile list to rebuild - Task.Factory.StartNew(() => - { - Thread.Sleep(100); - SelectedLayer = selectModel; - }); - } - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseDownKeyboardPreview(e); - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on - /// if the used clicked on an empty spot, deselects the current layer - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - ProfileViewModel.MouseUpKeyboardPreview(e); - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - ProfileViewModel.MouseMoveKeyboardPreview(e); - } - - /// - /// Adds a new profile to the current game and keyboard - /// - public async void AddProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot add profile.", - "To add a profile, please select a keyboard in the options menu first."); - return; - } - - var name = await _dialogService.ShowInputDialog("Add new profile", - "Please provide a profile name unique to this game and keyboard."); - - // Null when the user cancelled - if (name == null) - return; - - if (name.Length < 2) - { - _dialogService.ShowMessageBox("Invalid profile name", "Please provide a valid profile name"); - return; - } - - var profile = new ProfileModel - { - Name = name, - KeyboardSlug = _deviceManager.ActiveKeyboard.Slug, - Width = _deviceManager.ActiveKeyboard.Width, - Height = _deviceManager.ActiveKeyboard.Height, - GameName = _moduleModel.Name - }; - - if (!ProfileProvider.IsProfileUnique(profile)) - { - var overwrite = await _dialogService.ShowQuestionMessageBox("Overwrite existing profile", - "A profile with this name already exists for this game. Would you like to overwrite it?"); - if (!overwrite.Value) - return; - } - - ProfileProvider.AddOrUpdate(profile); - - LoadProfiles(); - } - - public async void RenameProfile() - { - if (SelectedProfile == null) - return; - - var oldName = SelectedProfile.Name; - var name = await _dialogService.ShowInputDialog("Rename profile", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - return; - - SelectedProfile.Name = name; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(SelectedProfile)) - { - name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique new profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(name) || name.Length < 2) - { - SelectedProfile.Name = oldName; - return; - } - SelectedProfile.Name = name; - } - - var profile = SelectedProfile; - ProfileProvider.RenameProfile(profile, name); - - LoadProfiles(); - } - - public async void DuplicateProfile() - { - if (SelectedProfile == null) - return; - - var newProfile = GeneralHelpers.Clone(SelectedProfile); - newProfile.Name = await _dialogService - .ShowInputDialog("Duplicate profile", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(newProfile)) - { - newProfile.Name = await _dialogService - .ShowInputDialog("Name already in use", "Please enter a unique profile name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(newProfile.Name)) - return; - } - - newProfile.IsDefault = false; - ProfileProvider.AddOrUpdate(newProfile); - LoadProfiles(); - } - - public async void DeleteProfile() - { - if (SelectedProfile == null) - return; - - var confirm = await - _dialogService.ShowQuestionMessageBox("Delete profile", - $"Are you sure you want to delete the profile named: {SelectedProfile.Name}?\n\n" + - "This cannot be undone."); - if (!confirm.Value) - return; - - var defaultProfile = ProfileProvider.GetProfile(_deviceManager.ActiveKeyboard, _moduleModel, "Default"); - var deleteProfile = SelectedProfile; - - _moduleModel.ChangeProfile(defaultProfile); - ProfileProvider.DeleteProfile(deleteProfile); - - LoadProfiles(); - } - - public async void ImportProfile() - { - if (_deviceManager.ActiveKeyboard == null) - { - _dialogService.ShowMessageBox("Cannot import profile.", - "To import a profile, please select a keyboard in the options menu first."); - return; - } - var dialog = new OpenFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - var profile = ProfileProvider.LoadProfileIfValid(dialog.FileName); - if (profile == null) - { - _dialogService.ShowErrorMessageBox("Oh noes, the profile you provided is invalid. " + - "If this keeps happening, please make an issue on GitHub and provide the profile."); - return; - } - - // Verify the game - if (profile.GameName != _moduleModel.Name) - { - _dialogService.ShowErrorMessageBox( - $"Oh oops! This profile is ment for {profile.GameName}, not {_moduleModel.Name} :c"); - return; - } - - // Verify the keyboard - var deviceManager = _deviceManager; - if (profile.KeyboardSlug != deviceManager.ActiveKeyboard.Slug) - { - var adjustKeyboard = await _dialogService.ShowQuestionMessageBox( - "Profile not inteded for this keyboard", - $"Watch out, this profile wasn't ment for this keyboard, but for the {profile.KeyboardSlug}. " + - "You can still import it but you'll probably have to do some adjusting\n\n" + - "Continue?"); - if (!adjustKeyboard.Value) - return; - - // Resize layers that are on the full keyboard width - profile.ResizeLayers(deviceManager.ActiveKeyboard); - // Put layers back into the canvas if they fell outside it - profile.FixBoundaries(deviceManager.ActiveKeyboard.KeyboardRectangle(1)); - - // Setup profile metadata to match the new keyboard - profile.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; - profile.Width = deviceManager.ActiveKeyboard.Width; - profile.Height = deviceManager.ActiveKeyboard.Height; - } - - profile.IsDefault = false; - - // Verify the name - while (!ProfileProvider.IsProfileUnique(profile)) - { - profile.Name = await _dialogService.ShowInputDialog("Rename imported profile", - "A profile with this name already exists for this game. Please enter a new name"); - - // Null when the user cancelled - if (string.IsNullOrEmpty(profile.Name)) - return; - } - - ProfileProvider.AddOrUpdate(profile); - LoadProfiles(); - } - - public void ExportProfile() - { - if (SelectedProfile == null) - return; - - var dialog = new SaveFileDialog {Filter = "Artemis profile (*.json)|*.json"}; - var result = dialog.ShowDialog(); - if (result != DialogResult.OK) - return; - - ProfileProvider.ExportProfile(SelectedProfile, dialog.FileName); - } - - public void EditLua() - { - if (SelectedProfile == null) - return; - try - { - OpenEditor(); - } - catch (Exception e) - { - _dialogService.ShowMessageBox("Couldn't open LUA file", - "Please make sure you have a text editor associated with the .lua extension.\n\n" + - "Windows error message: \n" + e.Message); - } - } - private void EditorStateHandler(object sender, PropertyChangedEventArgs e) { if (e.PropertyName != "SelectedProfile") @@ -706,90 +798,6 @@ namespace Artemis.ViewModels.Profiles NotifyOfPropertyChange(() => ProfileSelected); } - public void SaveSelectedProfile() - { - if (_saving || SelectedProfile == null || _deviceManager.ChangingKeyboard) - return; - - _saving = true; - try - { - ProfileProvider.AddOrUpdate(SelectedProfile); - } - catch (Exception) - { - // ignored - } - _saving = false; - } - - #region LUA Editor - - public void OpenEditor() - { - if (SelectedProfile == null) - return; - - // Create a temp file - var fileName = Guid.NewGuid() + ".lua"; - var file = File.Create(Path.GetTempPath() + fileName); - file.Dispose(); - - // Add instructions to LUA script if it's a new file - if (string.IsNullOrEmpty(SelectedProfile.LuaScript)) - SelectedProfile.LuaScript = Encoding.UTF8.GetString(Resources.lua_placeholder); - File.WriteAllText(Path.GetTempPath() + fileName, SelectedProfile.LuaScript); - - // Watch the file for changes - SetupWatcher(Path.GetTempPath(), fileName); - - // Open the temp file with the default editor - System.Diagnostics.Process.Start(Path.GetTempPath() + fileName); - } - - private void SetupWatcher(string path, string fileName) - { - if (_watcher == null) - { - _watcher = new FileSystemWatcher(Path.GetTempPath(), fileName); - _watcher.Changed += LuaFileChanged; - _watcher.EnableRaisingEvents = true; - } - - _watcher.Path = path; - _watcher.Filter = fileName; - } - - private void LuaFileChanged(object sender, FileSystemEventArgs args) - { - if (args.ChangeType != WatcherChangeTypes.Changed) - return; - - if (SelectedProfile == null) - return; - - lock (SelectedProfile) - { - using (var fs = new FileStream(args.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - using (var sr = new StreamReader(fs)) - { - SelectedProfile.LuaScript = sr.ReadToEnd(); - } - } - - ProfileProvider.AddOrUpdate(SelectedProfile); - _luaManager.SetupLua(SelectedProfile); - } - } - - public void Dispose() - { - SaveSelectedProfile(); - ProfileViewModel.Dispose(); - _watcher?.Dispose(); - } - #endregion } } \ No newline at end of file diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs deleted file mode 100644 index 9487b026a..000000000 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileViewModel.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using Artemis.Events; -using Artemis.Managers; -using Artemis.Modules.Abstract; -using Artemis.Profiles; -using Artemis.Profiles.Layers.Interfaces; -using Artemis.Profiles.Layers.Models; -using Artemis.Profiles.Layers.Types.Folder; -using Artemis.Properties; -using Artemis.Utilities; -using Caliburn.Micro; -using Castle.Components.DictionaryAdapter; -using MahApps.Metro; - -namespace Artemis.ViewModels.Profiles -{ - public class ProfileViewModel : PropertyChangedBase, IDisposable - { - private readonly DeviceManager _deviceManager; - private readonly LoopManager _loopManager; - private double _blurProgress; - private double _blurRadius; - private DateTime _downTime; - private LayerModel _draggingLayer; - private Point? _draggingLayerOffset; - private DrawingImage _keyboardPreview; - private Cursor _keyboardPreviewCursor; - private bool _resizing; - private bool _showAll; - - public ProfileViewModel(DeviceManager deviceManager, LoopManager loopManager) - { - _deviceManager = deviceManager; - _loopManager = loopManager; - - ShowAll = false; - - _loopManager.RenderCompleted += LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged += DeviceManagerOnOnKeyboardChanged; - } - - public ModuleModel ModuleModel { get; set; } - public LayerModel SelectedLayer { get; set; } - - public ProfileModel SelectedProfile => ModuleModel?.ProfileModel; - - public DrawingImage KeyboardPreview - { - get { return _keyboardPreview; } - set - { - if (Equals(value, _keyboardPreview)) return; - _keyboardPreview = value; - NotifyOfPropertyChange(() => KeyboardPreview); - } - } - - public double BlurRadius - { - get { return _blurRadius; } - set - { - if (value.Equals(_blurRadius)) return; - _blurRadius = value; - NotifyOfPropertyChange(() => BlurRadius); - } - } - - public bool ShowAll - { - get { return _showAll; } - set - { - if (value == _showAll) return; - _showAll = value; - NotifyOfPropertyChange(() => ShowAll); - } - } - - public ImageSource KeyboardImage => ImageUtilities - .BitmapToBitmapImage(_deviceManager.ActiveKeyboard?.PreviewSettings.Image ?? Resources.none); - - private void LoopManagerOnRenderCompleted(object sender, EventArgs eventArgs) - { - // Update the glowing effect around the keyboard - if (_blurProgress > 2) - _blurProgress = 0; - _blurProgress = _blurProgress + 0.025; - BlurRadius = (Math.Sin(_blurProgress * Math.PI) + 1) * 10 + 10; - - // Besides the usual checks, also check if the ActiveKeyboard isn't the NoneKeyboard - if (SelectedProfile == null || _deviceManager.ActiveKeyboard == null || _deviceManager.ActiveKeyboard.Slug == "none") - { - KeyboardPreview = null; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = new List(); - - return; - } - - var renderLayers = GetRenderLayers(); - // Draw the current frame to the preview - var keyboardRect = _deviceManager.ActiveKeyboard.KeyboardRectangle(); - var visual = new DrawingVisual(); - using (var drawingContext = visual.RenderOpen()) - { - // Setup the DrawingVisual's size - drawingContext.PushClip(new RectangleGeometry(keyboardRect)); - drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect); - - // Draw the layers - foreach (var layer in renderLayers) - { - layer.Update(null, true, false); - if (layer.LayerType.ShowInEdtor) - layer.Draw(null, drawingContext, true, false); - } - - // Get the selection color - var accentColor = ThemeManager.DetectAppStyle(Application.Current)?.Item2?.Resources["AccentColor"]; - if (accentColor == null) - { - var preview = new DrawingImage(); - preview.Freeze(); - KeyboardPreview = preview; - return; - } - - var pen = new Pen(new SolidColorBrush((Color) accentColor), 0.4); - - // Draw the selection outline and resize indicator - if (SelectedLayer != null && SelectedLayer.MustDraw()) - { - var layerRect = SelectedLayer.Properties.PropertiesRect(); - // Deflate the rect so that the border is drawn on the inside - layerRect.Inflate(-0.2, -0.2); - - // Draw an outline around the selected layer - drawingContext.DrawRectangle(null, pen, layerRect); - // Draw a resize indicator in the bottom-right - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 1, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 1.2, layerRect.BottomRight.Y - 0.7)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 1), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 1.2)); - drawingContext.DrawLine(pen, - new Point(layerRect.BottomRight.X - 0.5, layerRect.BottomRight.Y - 0.5), - new Point(layerRect.BottomRight.X - 0.7, layerRect.BottomRight.Y - 0.7)); - } - - SelectedProfile?.RaiseDeviceDrawnEvent(new ProfileDeviceEventsArg(DrawType.Preview, null, true, drawingContext)); - - // Remove the clip - drawingContext.Pop(); - } - var drawnPreview = new DrawingImage(visual.Drawing); - drawnPreview.Freeze(); - KeyboardPreview = drawnPreview; - - // Setup layers for the next frame - if (ModuleModel.IsInitialized && ActiveWindowHelper.MainWindowActive) - ModuleModel.PreviewLayers = renderLayers; - } - - private void DeviceManagerOnOnKeyboardChanged(object sender, KeyboardChangedEventArgs e) - { - NotifyOfPropertyChange(() => KeyboardImage); - } - - #region Processing - - /// - /// Handler for clicking - /// - /// - public void MouseDownKeyboardPreview(MouseButtonEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - _downTime = DateTime.Now; - } - - /// - /// Second handler for clicking, selects a the layer the user clicked on. - /// - /// - public void MouseUpKeyboardPreview(MouseButtonEventArgs e) - { - if (SelectedProfile == null || SelectedProfile.IsDefault) - return; - - var timeSinceDown = DateTime.Now - _downTime; - if (!(timeSinceDown.TotalMilliseconds < 500)) - return; - if (_draggingLayer != null) - return; - - var keyboard = _deviceManager.ActiveKeyboard; - var pos = e.GetPosition((Image) e.OriginalSource); - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - if (hoverLayer != null) - SelectedLayer = hoverLayer; - } - - /// - /// Handler for resizing and moving the currently selected layer - /// - /// - public void MouseMoveKeyboardPreview(MouseEventArgs e) - { - if (SelectedProfile == null) - return; - - var pos = e.GetPosition((Image) e.OriginalSource); - var keyboard = _deviceManager.ActiveKeyboard; - var x = pos.X / ((double) keyboard.PreviewSettings.Width / keyboard.Width); - var y = pos.Y / ((double) keyboard.PreviewSettings.Height / keyboard.Height); - var hoverLayer = GetLayers().Where(l => l.MustDraw()) - .FirstOrDefault(l => l.Properties.PropertiesRect(1).Contains(x, y)); - - HandleDragging(e, x, y, hoverLayer); - - if (hoverLayer == null) - { - KeyboardPreviewCursor = Cursors.Arrow; - return; - } - - // Turn the mouse pointer into a hand if hovering over an active layer - if (hoverLayer == SelectedLayer) - { - var rect = hoverLayer.Properties.PropertiesRect(1); - KeyboardPreviewCursor = - Math.Sqrt(Math.Pow(x - rect.BottomRight.X, 2) + Math.Pow(y - rect.BottomRight.Y, 2)) < 0.6 - ? Cursors.SizeNWSE - : Cursors.SizeAll; - } - else - { - KeyboardPreviewCursor = Cursors.Hand; - } - } - - public Cursor KeyboardPreviewCursor - { - get { return _keyboardPreviewCursor; } - set - { - if (Equals(value, _keyboardPreviewCursor)) return; - _keyboardPreviewCursor = value; - NotifyOfPropertyChange(() => KeyboardPreviewCursor); - } - } - - /// - /// Handles dragging the given layer - /// - /// - /// - /// - /// - private void HandleDragging(MouseEventArgs e, double x, double y, LayerModel hoverLayer) - { - // Reset the dragging state on mouse release - if (e.LeftButton == MouseButtonState.Released || - _draggingLayer != null && SelectedLayer != _draggingLayer) - { - _draggingLayerOffset = null; - _draggingLayer = null; - return; - } - - if (SelectedLayer == null || SelectedLayer.LayerType != null && !SelectedLayer.LayerType.ShowInEdtor) - return; - - // Setup the dragging state on mouse press - if (_draggingLayerOffset == null && hoverLayer != null && e.LeftButton == MouseButtonState.Pressed) - { - var layerRect = hoverLayer.Properties.PropertiesRect(1); - - _draggingLayerOffset = new Point(x - SelectedLayer.Properties.X, y - SelectedLayer.Properties.Y); - _draggingLayer = hoverLayer; - // Detect dragging if cursor is in the bottom right - _resizing = Math.Sqrt(Math.Pow(x - layerRect.BottomRight.X, 2) + - Math.Pow(y - layerRect.BottomRight.Y, 2)) < 0.6; - } - - if (_draggingLayerOffset == null || _draggingLayer == null || _draggingLayer != SelectedLayer) - return; - - var draggingProps = _draggingLayer.Properties; - // If no setup or reset was done, handle the actual dragging action - if (_resizing) - { - var newWidth = Math.Round(x - draggingProps.X); - var newHeight = Math.Round(y - draggingProps.Y); - - // Ensure the layer doesn't leave the canvas - if (newWidth < 1 || draggingProps.X + newWidth <= 0) - newWidth = draggingProps.Width; - if (newHeight < 1 || draggingProps.Y + newHeight <= 0) - newHeight = draggingProps.Height; - - draggingProps.Width = newWidth; - draggingProps.Height = newHeight; - } - else - { - var newX = Math.Round(x - _draggingLayerOffset.Value.X); - var newY = Math.Round(y - _draggingLayerOffset.Value.Y); - - // Ensure the layer doesn't leave the canvas - if (newX >= SelectedProfile.Width || newX + draggingProps.Width <= 0) - newX = draggingProps.X; - if (newY >= SelectedProfile.Height || newY + draggingProps.Height <= 0) - newY = draggingProps.Y; - - draggingProps.X = newX; - draggingProps.Y = newY; - } - } - - public List GetRenderLayers() - { - // Get the layers that must be drawn - List drawLayers; - if (ShowAll) - return SelectedProfile.GetRenderLayers(null, false, true); - - if (SelectedLayer == null || !SelectedLayer.Enabled) - return new EditableList(); - - if (SelectedLayer.LayerType is FolderType) - drawLayers = SelectedLayer.GetRenderLayers(null, false, true); - else - drawLayers = new List {SelectedLayer}; - - return drawLayers; - } - - - private List GetLayers() - { - if (ShowAll) - return SelectedProfile.GetLayers(); - if (SelectedLayer == null) - return new List(); - - lock (SelectedLayer) - { - // Get the layers that must be drawn - if (SelectedLayer.LayerType is FolderType) - return SelectedLayer.GetLayers().ToList(); - return new List {SelectedLayer}; - } - } - - public void Dispose() - { - Disposed = true; - _loopManager.RenderCompleted -= LoopManagerOnRenderCompleted; - _deviceManager.OnKeyboardChanged -= DeviceManagerOnOnKeyboardChanged; - } - - public bool Disposed { get; set; } - - #endregion - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml b/Artemis/Artemis/Views/LayerEditorView.xaml similarity index 99% rename from Artemis/Artemis/Views/Profiles/LayerEditorView.xaml rename to Artemis/Artemis/Views/LayerEditorView.xaml index dabe59270..8c7adca13 100644 --- a/Artemis/Artemis/Views/Profiles/LayerEditorView.xaml +++ b/Artemis/Artemis/Views/LayerEditorView.xaml @@ -1,4 +1,4 @@ - /// Interaction logic for LayerEditorView.xaml diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml b/Artemis/Artemis/Views/ProfileEditorView.xaml similarity index 93% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml rename to Artemis/Artemis/Views/ProfileEditorView.xaml index 4a3c9e8b7..7c138d830 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml @@ -1,288 +1,297 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs similarity index 89% rename from Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs rename to Artemis/Artemis/Views/ProfileEditorView.xaml.cs index b2abd5ee2..35d68c535 100644 --- a/Artemis/Artemis/Views/Profiles/ProfileEditorView.xaml.cs +++ b/Artemis/Artemis/Views/ProfileEditorView.xaml.cs @@ -1,6 +1,6 @@ using System.Windows.Controls; -namespace Artemis.Views.Profiles +namespace Artemis.Views { /// /// Interaction logic for ProfileEditorView.xaml