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 + } +}