diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj
index c18676f4b..46f1173c5 100644
--- a/Artemis/Artemis/Artemis.csproj
+++ b/Artemis/Artemis/Artemis.csproj
@@ -56,6 +56,7 @@
prompt
4
false
+ true
x64
@@ -65,6 +66,7 @@
TRACE
prompt
4
+ true
EAC088BE27A2DE790AE6F37A020409F4A1B5EC0E
@@ -491,6 +493,13 @@
+
+
+ AngularBrushPropertiesView.xaml
+
+
+
+
@@ -862,6 +871,10 @@
MSBuild:Compile
Designer
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/Artemis/Artemis/Managers/LoopManager.cs b/Artemis/Artemis/Managers/LoopManager.cs
index cb1493b10..06256803a 100644
--- a/Artemis/Artemis/Managers/LoopManager.cs
+++ b/Artemis/Artemis/Managers/LoopManager.cs
@@ -3,13 +3,10 @@ using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using System.Timers;
-using System.Windows.Media;
using Artemis.DeviceProviders;
using Artemis.ViewModels;
using Ninject.Extensions.Logging;
using Color = System.Drawing.Color;
-using Timer = System.Timers.Timer;
namespace Artemis.Managers
{
@@ -21,7 +18,8 @@ namespace Artemis.Managers
private readonly DebugViewModel _debugViewModel;
private readonly DeviceManager _deviceManager;
private readonly ILogger _logger;
- private readonly Timer _loopTimer;
+ //private readonly Timer _loopTimer;
+ private readonly Task _loopTask;
private readonly ModuleManager _moduleManager;
public LoopManager(ILogger logger, ModuleManager moduleManager, DeviceManager deviceManager,
@@ -33,10 +31,10 @@ namespace Artemis.Managers
_debugViewModel = debugViewModel;
// Setup timers
- _loopTimer = new Timer(40);
- _loopTimer.Elapsed += LoopTimerOnElapsed;
- _loopTimer.Start();
-
+ //_loopTimer = new Timer(40);
+ //_loopTimer.Elapsed += LoopTimerOnElapsed;
+ //_loopTimer.Start();
+ _loopTask = Task.Factory.StartNew(ProcessLoop);
_logger.Info("Intialized LoopManager");
}
@@ -49,22 +47,45 @@ namespace Artemis.Managers
public void Dispose()
{
- _loopTimer.Stop();
- _loopTimer.Dispose();
+ _loopTask.Dispose();
+ //_loopTimer.Stop();
+ //_loopTimer.Dispose();
}
- private void LoopTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
+ private void ProcessLoop()
{
- try
+ //TODO DarthAffe 14.01.2017: A stop-condition and a real cleanup instead of just aborting might be better
+ while (true)
{
- Render();
- }
- catch (Exception e)
- {
- _logger.Warn(e, "Exception in render loop");
+ try
+ {
+ long preUpdateTicks = DateTime.Now.Ticks;
+
+ Render();
+
+ int sleep = (int)(40f - ((DateTime.Now.Ticks - preUpdateTicks) / 10000f));
+ if (sleep > 0)
+ Thread.Sleep(sleep);
+ }
+ catch (Exception e)
+ {
+ _logger.Warn(e, "Exception in render loop");
+ }
}
}
+ //private void LoopTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
+ //{
+ // try
+ // {
+ // Render();
+ // }
+ // catch (Exception e)
+ // {
+ // _logger.Warn(e, "Exception in render loop");
+ // }
+ //}
+
public Task StartAsync()
{
return Task.Run(() => Start());
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs
new file mode 100644
index 000000000..58fdce801
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesModel.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Media;
+using Artemis.Profiles.Layers.Models;
+
+namespace Artemis.Profiles.Layers.Types.AngularBrush
+{
+ public class AngularBrushPropertiesModel : LayerPropertiesModel
+ {
+ #region Properties & Fields
+
+ public IList> GradientStops { get; set; }
+
+ #endregion
+
+ #region Constructors
+
+ public AngularBrushPropertiesModel(LayerPropertiesModel properties = null)
+ : base(properties)
+ { }
+
+ #endregion
+
+ #region Methods
+
+ #endregion
+ }
+}
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml
new file mode 100644
index 000000000..b93018706
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs
new file mode 100644
index 000000000..e5edeea15
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesView.xaml.cs
@@ -0,0 +1,12 @@
+using System.Windows.Controls;
+
+namespace Artemis.Profiles.Layers.Types.AngularBrush
+{
+ public partial class AngularBrushPropertiesView : UserControl
+ {
+ public AngularBrushPropertiesView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs
new file mode 100644
index 000000000..e964b72fc
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushPropertiesViewModel.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Linq;
+using System.Windows.Media;
+using Artemis.Profiles.Layers.Abstract;
+using Artemis.Profiles.Layers.Interfaces;
+using Artemis.Utilities;
+using Artemis.ViewModels;
+using Artemis.ViewModels.Profiles;
+using Caliburn.Micro;
+
+namespace Artemis.Profiles.Layers.Types.AngularBrush
+{
+ public class AngularBrushPropertiesViewModel : LayerPropertiesViewModel
+ {
+ #region Properties & Fields
+
+ private ILayerAnimation _selectedLayerAnimation;
+
+ public BindableCollection DataModelProps { get; set; }
+ public BindableCollection LayerAnimations { get; set; }
+ public LayerDynamicPropertiesViewModel HeightProperties { get; set; }
+ public LayerDynamicPropertiesViewModel WidthProperties { get; set; }
+ public LayerDynamicPropertiesViewModel OpacityProperties { get; set; }
+ public LayerTweenViewModel LayerTweenViewModel { get; set; }
+
+ public ILayerAnimation SelectedLayerAnimation
+ {
+ get { return _selectedLayerAnimation; }
+ set
+ {
+ if (Equals(value, _selectedLayerAnimation)) return;
+ _selectedLayerAnimation = value;
+ NotifyOfPropertyChange(() => SelectedLayerAnimation);
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public AngularBrushPropertiesViewModel(LayerEditorViewModel editorVm)
+ : base(editorVm)
+ {
+ LayerAnimations = new BindableCollection(editorVm.LayerAnimations);
+
+ HeightProperties = new LayerDynamicPropertiesViewModel("Height", editorVm);
+ WidthProperties = new LayerDynamicPropertiesViewModel("Width", editorVm);
+ OpacityProperties = new LayerDynamicPropertiesViewModel("Opacity", editorVm);
+ LayerTweenViewModel = new LayerTweenViewModel(editorVm);
+
+ SelectedLayerAnimation =
+ LayerAnimations.FirstOrDefault(l => l.Name == editorVm.ProposedLayer.LayerAnimation?.Name) ??
+ LayerAnimations.First(l => l.Name == "None");
+ }
+
+ #endregion
+
+ #region Methods
+
+ public override void ApplyProperties()
+ {
+ HeightProperties.Apply(LayerModel);
+ WidthProperties.Apply(LayerModel);
+ OpacityProperties.Apply(LayerModel);
+
+ LayerModel.Properties.Brush = Brush;
+ LayerModel.LayerAnimation = SelectedLayerAnimation;
+ }
+
+ #endregion
+ }
+}
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs
new file mode 100644
index 000000000..0bdf2775e
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/AngularBrushType.cs
@@ -0,0 +1,130 @@
+using System;
+using System.Linq;
+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.AngularBrush.Drawing;
+using Artemis.ViewModels;
+
+namespace Artemis.Profiles.Layers.Types.AngularBrush
+{
+ public class AngularBrushType : ILayerType
+ {
+ #region Properties & Fields
+
+ private GradientDrawer _gradientDrawer;
+
+ public string Name => "Angular Brush";
+ public bool ShowInEdtor => true;
+ public DrawType DrawType => DrawType.Keyboard;
+
+ #endregion
+
+ public AngularBrushType()
+ {
+ _gradientDrawer = new GradientDrawer();
+ }
+
+ #region Methods
+
+ public ImageSource DrawThumbnail(LayerModel layer)
+ {
+ //TODO DarthAffe 14.01.2017: This could be replaced with the real brush but it complaints about the thread too
+ Rect thumbnailRect = new Rect(0, 0, 18, 18);
+ DrawingVisual visual = new DrawingVisual();
+ using (DrawingContext c = visual.RenderOpen())
+ if (layer.Properties.Brush != null)
+ c.DrawRectangle(layer.Properties.Brush,
+ new Pen(new SolidColorBrush(Colors.White), 1),
+ thumbnailRect);
+
+ DrawingImage image = new DrawingImage(visual.Drawing);
+ return image;
+ }
+
+ public void Draw(LayerModel layerModel, DrawingContext c)
+ {
+ AngularBrushPropertiesModel properties = layerModel.Properties as AngularBrushPropertiesModel;
+ if (properties == null) return;
+
+ Brush origBrush = layerModel.Brush;
+
+ //TODO DarthAffe 14.01.2017: Check if an update is needed
+ _gradientDrawer.GradientStops = GetGradientStops(layerModel.Brush).Select(x => new Tuple(x.Offset, x.Color)).ToList();
+ _gradientDrawer.Update();
+ layerModel.Brush = _gradientDrawer.Brush.Clone();
+
+ // 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
+ Rect rect = layerModel.Properties.Contain
+ ? layerModel.LayerRect()
+ : new Rect(layerModel.Properties.X * 4, layerModel.Properties.Y * 4,
+ layerModel.Properties.Width * 4, layerModel.Properties.Height * 4);
+
+ Rect clip = layerModel.LayerRect();
+
+ // Can't meddle with the original brush because it's frozen.
+ Brush brush = layerModel.Brush.Clone();
+ brush.Opacity = layerModel.Opacity;
+
+ c.PushClip(new RectangleGeometry(clip));
+ c.DrawRectangle(brush, null, rect);
+ c.Pop();
+
+ layerModel.Brush = origBrush;
+ }
+
+ public void Update(LayerModel layerModel, ModuleDataModel dataModel, bool isPreview = false)
+ {
+ layerModel.ApplyProperties(true);
+ if (isPreview || dataModel == null)
+ return;
+
+ // If not previewing, apply dynamic properties according to datamodel
+ foreach (DynamicPropertiesModel dynamicProperty in layerModel.Properties.DynamicProperties)
+ dynamicProperty.ApplyProperty(dataModel, layerModel);
+ }
+
+ public void SetupProperties(LayerModel layerModel)
+ {
+ if (layerModel.Properties is AngularBrushPropertiesModel)
+ return;
+
+ layerModel.Properties = new AngularBrushPropertiesModel(layerModel.Properties);
+ }
+
+ public LayerPropertiesViewModel SetupViewModel(LayerEditorViewModel layerEditorViewModel, LayerPropertiesViewModel layerPropertiesViewModel)
+ {
+ return (layerPropertiesViewModel as AngularBrushPropertiesViewModel) ?? new AngularBrushPropertiesViewModel(layerEditorViewModel);
+ }
+
+ private GradientStopCollection GetGradientStops(Brush brush)
+ {
+ LinearGradientBrush linearBrush = brush as LinearGradientBrush;
+ if (linearBrush != null)
+ return linearBrush.GradientStops;
+
+ RadialGradientBrush radialBrush = brush as RadialGradientBrush;
+ if (radialBrush != null)
+ return radialBrush.GradientStops;
+
+ SolidColorBrush solidBrush = brush as SolidColorBrush;
+ if (solidBrush != null)
+ return new GradientStopCollection(new[] { new GradientStop(solidBrush.Color, 0), new GradientStop(solidBrush.Color, 1) });
+
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs
new file mode 100644
index 000000000..fd3d7e8c2
--- /dev/null
+++ b/Artemis/Artemis/Profiles/Layers/Types/AngularBrush/Drawing/GradientDrawer.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace Artemis.Profiles.Layers.Types.AngularBrush.Drawing
+{
+ public class GradientDrawer
+ {
+ #region Constants
+
+ private static readonly double ORIGIN = Math.Atan2(-1, 0);
+
+ #endregion
+
+ #region Properties & Fields
+
+ private WriteableBitmap _bitmap;
+
+ private IList> _gradientStops;
+ public IList> GradientStops
+ {
+ set { _gradientStops = FixGradientStops(value); }
+ }
+
+ public Brush Brush { get; private set; }
+
+ #endregion
+
+ #region Methods
+
+ public void Update()
+ {
+ if (_bitmap == null)
+ CreateBrush();
+
+ unsafe
+ {
+ _bitmap.Lock();
+ byte* buffer = (byte*)_bitmap.BackBuffer.ToPointer();
+
+ int width = _bitmap.PixelWidth;
+ double widthHalf = width / 2.0;
+
+ int height = _bitmap.PixelHeight;
+ double heightHalf = height / 2.0;
+
+ for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++)
+ {
+ int offset = (((y * width) + x) * 4);
+
+ double gradientOffset = CalculateGradientOffset(x, y, widthHalf, heightHalf);
+ GetColor(_gradientStops, gradientOffset,
+ ref buffer[offset + 3], ref buffer[offset + 2],
+ ref buffer[offset + 1], ref buffer[offset]);
+ }
+
+ _bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
+ _bitmap.Unlock();
+ }
+ }
+
+ private void CreateBrush()
+ {
+ _bitmap = new WriteableBitmap(100, 100, 96, 96, PixelFormats.Bgra32, null);
+ Brush = new ImageBrush(_bitmap) { Stretch = Stretch.UniformToFill };
+ }
+
+ private double CalculateGradientOffset(double x, double y, double centerX, double centerY)
+ {
+ double angle = Math.Atan2(y - centerY, x - centerX) - ORIGIN;
+ if (angle < 0) angle += Math.PI * 2;
+ return angle / (Math.PI * 2);
+ }
+
+ private static void GetColor(IList> gradientStops, double offset, ref byte colA, ref byte colR, ref byte colG, ref byte colB)
+ {
+ if (gradientStops.Count == 0)
+ {
+ colA = 0;
+ colR = 0;
+ colG = 0;
+ colB = 0;
+ return;
+ }
+ if (gradientStops.Count == 1)
+ {
+ Color color = gradientStops.First().Item2;
+ colA = color.A;
+ colR = color.R;
+ colG = color.G;
+ colB = color.B;
+ return;
+ }
+
+ Tuple beforeStop = null;
+ double afterOffset = -1;
+ Color afterColor = default(Color);
+
+ for (int i = 0; i < gradientStops.Count; i++)
+ {
+ Tuple gradientStop = gradientStops[i];
+ double o = gradientStop.Item1;
+ if (o <= offset)
+ beforeStop = gradientStop;
+
+ if (o >= offset)
+ {
+ afterOffset = gradientStop.Item1;
+ afterColor = gradientStop.Item2;
+ break;
+ }
+ }
+ double beforeOffset = beforeStop.Item1;
+ Color beforeColor = beforeStop.Item2;
+
+ double blendFactor = 0f;
+ if (beforeOffset != afterOffset)
+ blendFactor = ((offset - beforeOffset) / (afterOffset - beforeOffset));
+
+ colA = (byte)((afterColor.A - beforeColor.A) * blendFactor + beforeColor.A);
+ colR = (byte)((afterColor.R - beforeColor.R) * blendFactor + beforeColor.R);
+ colG = (byte)((afterColor.G - beforeColor.G) * blendFactor + beforeColor.G);
+ colB = (byte)((afterColor.B - beforeColor.B) * blendFactor + beforeColor.B);
+ }
+
+ private IList> FixGradientStops(IList> gradientStops)
+ {
+ if (gradientStops == null) return new List>();
+
+ List> stops = gradientStops.OrderBy(x => x.Item1).ToList();
+
+ Tuple firstStop = stops.First();
+ if (firstStop.Item1 > 0)
+ stops.Insert(0, new Tuple(0, firstStop.Item2));
+
+ Tuple lastStop = stops.Last();
+ if (lastStop.Item1 < 1)
+ stops.Add(new Tuple(1, lastStop.Item2));
+
+ return stops;
+ }
+
+ #endregion
+ }
+}