diff --git a/RGB.NET.Core/Brushes/IBrush.cs b/RGB.NET.Core/Brushes/IBrush.cs index db0b9e6..b4beac8 100644 --- a/RGB.NET.Core/Brushes/IBrush.cs +++ b/RGB.NET.Core/Brushes/IBrush.cs @@ -19,12 +19,12 @@ namespace RGB.NET.Core /// /// Gets or sets the overall percentage brightness of the . /// - float Brightness { get; set; } + double Brightness { get; set; } /// /// Gets or sets the overall percentage opacity of the . /// - float Opacity { get; set; } + double Opacity { get; set; } /// /// Gets a list of used to correct the colors of the . diff --git a/RGB.NET.Core/Devices/AbstractRGBDevice.cs b/RGB.NET.Core/Devices/AbstractRGBDevice.cs index 125f0ef..1d8958e 100644 --- a/RGB.NET.Core/Devices/AbstractRGBDevice.cs +++ b/RGB.NET.Core/Devices/AbstractRGBDevice.cs @@ -38,7 +38,7 @@ namespace RGB.NET.Core Led IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.LedRectangle.Contains(location)); /// - IEnumerable IRGBDevice.this[Rectangle referenceRect, float minOverlayPercentage] + IEnumerable IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage] => LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.LedRectangle) >= minOverlayPercentage) ; diff --git a/RGB.NET.Core/Devices/IRGBDevice.cs b/RGB.NET.Core/Devices/IRGBDevice.cs index 10030da..7422dc1 100644 --- a/RGB.NET.Core/Devices/IRGBDevice.cs +++ b/RGB.NET.Core/Devices/IRGBDevice.cs @@ -43,7 +43,7 @@ namespace RGB.NET.Core /// The to check. /// The minimal percentage overlay a must have with the to be taken into the list. /// - IEnumerable this[Rectangle referenceRect, float minOverlayPercentage = 0.5f] { get; } + IEnumerable this[Rectangle referenceRect, double minOverlayPercentage = 0.5] { get; } #endregion diff --git a/RGB.NET.Core/Effects/AbstractEffectTarget.cs b/RGB.NET.Core/Effects/AbstractEffectTarget.cs new file mode 100644 index 0000000..cc65cd7 --- /dev/null +++ b/RGB.NET.Core/Effects/AbstractEffectTarget.cs @@ -0,0 +1,99 @@ +// ReSharper disable MemberCanBePrivate.Global + +using System; +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core.Exceptions; + +namespace RGB.NET.Core +{ + /// + /// Represents an generic effect-target. + /// + /// + public abstract class AbstractEffectTarget : IEffectTarget + where T : IEffectTarget + { + #region Properties & Fields + + /// + /// Gets a list of storing the attached effects. + /// + protected IList EffectTimes { get; } = new List(); + + /// + /// Gets all attached to this . + /// + protected IList> Effects => EffectTimes.Select(x => x.Effect).Cast>().ToList(); + + /// + /// Gets the strongly-typed target used for the . + /// + protected abstract T EffectTarget { get; } + + #endregion + + #region Methods + + /// + /// Updates all added to this . + /// + public virtual void UpdateEffects() + { + lock (Effects) + { + for (int i = EffectTimes.Count - 1; i >= 0; i--) + { + EffectTimeContainer effectTime = EffectTimes[i]; + long currentTicks = DateTime.Now.Ticks; + + double deltaTime; + if (effectTime.TicksAtLastUpdate < 0) + { + effectTime.TicksAtLastUpdate = currentTicks; + deltaTime = 0; + } + else + deltaTime = (currentTicks - effectTime.TicksAtLastUpdate) / 10000000.0; + + effectTime.TicksAtLastUpdate = currentTicks; + effectTime.Effect.Update(deltaTime); + + if (effectTime.Effect.IsDone) + EffectTimes.RemoveAt(i); + } + } + } + + /// + /// Adds an . + /// + /// The to add. + public virtual void AddEffect(IEffect effect) + { + if (EffectTimes.Any(x => x.Effect == effect)) return; + + if (!effect.CanBeAppliedTo(EffectTarget)) + throw new EffectException($"Failed to add effect.\r\n" + + $"The effect of type '{effect.GetType()}' can't be applied to the target of type '{EffectTarget.GetType()}'."); + + effect.OnAttach(EffectTarget); + EffectTimes.Add(new EffectTimeContainer(effect, -1)); + } + + /// + /// Removes an . + /// + /// The to remove. + public virtual void RemoveEffect(IEffect effect) + { + EffectTimeContainer effectTimeToRemove = EffectTimes.FirstOrDefault(x => x.Effect == effect); + if (effectTimeToRemove == null) return; + + effect.OnDetach(EffectTarget); + EffectTimes.Remove(effectTimeToRemove); + } + + #endregion + } +} diff --git a/RGB.NET.Core/Effects/EffectTimeContainer.cs b/RGB.NET.Core/Effects/EffectTimeContainer.cs new file mode 100644 index 0000000..35d2991 --- /dev/null +++ b/RGB.NET.Core/Effects/EffectTimeContainer.cs @@ -0,0 +1,40 @@ +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + +namespace RGB.NET.Core +{ + /// + /// Represents a wrapped effect with additional time information. + /// + public class EffectTimeContainer + { + #region Properties & Fields + + /// + /// Gets or sets the wrapped . + /// + public IEffect Effect { get; } + + /// + /// Gets or sets the tick-count from the last time the was updated. + /// + public long TicksAtLastUpdate { get; set; } + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The wrapped . + /// The tick-count from the last time the was updated. + public EffectTimeContainer(IEffect effect, long ticksAtLastUpdate) + { + this.Effect = effect; + this.TicksAtLastUpdate = ticksAtLastUpdate; + } + + #endregion + } +} diff --git a/RGB.NET.Core/Effects/IEffect.cs b/RGB.NET.Core/Effects/IEffect.cs index 17129fa..165f81f 100644 --- a/RGB.NET.Core/Effects/IEffect.cs +++ b/RGB.NET.Core/Effects/IEffect.cs @@ -24,7 +24,7 @@ namespace RGB.NET.Core /// Updates this . /// /// The elapsed time (in seconds) since the last update. - void Update(float deltaTime); + void Update(double deltaTime); #endregion } diff --git a/RGB.NET.Core/Events/LedsUpdatedEventArgs.cs b/RGB.NET.Core/Events/LedsUpdatedEventArgs.cs deleted file mode 100644 index 53a8a26..0000000 --- a/RGB.NET.Core/Events/LedsUpdatedEventArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global - -using System; -using System.Collections.Generic; - -namespace RGB.NET.Core -{ - /// - /// Represents the information supplied with an -event. - /// - public class LedsUpdatedEventArgs : EventArgs - { - #region Properties & Fields - - /// - /// Gets a list of which got updated. - /// - public IEnumerable UpdatedLeds { get; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The updated . - public LedsUpdatedEventArgs(IEnumerable updatedLeds) - { - this.UpdatedLeds = updatedLeds; - } - - #endregion - } -} diff --git a/RGB.NET.Core/Events/LedsUpdatingEventArgs.cs b/RGB.NET.Core/Events/LedsUpdatingEventArgs.cs deleted file mode 100644 index 4ddbe93..0000000 --- a/RGB.NET.Core/Events/LedsUpdatingEventArgs.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global - -using System; -using System.Collections.Generic; - -namespace RGB.NET.Core -{ - /// - /// Represents the information supplied with an -event. - /// - public class LedsUpdatingEventArgs : EventArgs - { - #region Properties & Fields - - /// - /// Gets a list of which are about to get updated. - /// - public ICollection UpdatingLeds { get; } - - #endregion - - #region Constructors - - /// - /// Initializes a new instance of the class. - /// - /// The updating . - public LedsUpdatingEventArgs(ICollection updatingLeds) - { - this.UpdatingLeds = updatingLeds; - } - - #endregion - } -} diff --git a/RGB.NET.Core/Exceptions/EffectException.cs b/RGB.NET.Core/Exceptions/EffectException.cs new file mode 100644 index 0000000..46d3280 --- /dev/null +++ b/RGB.NET.Core/Exceptions/EffectException.cs @@ -0,0 +1,23 @@ +using System; + +namespace RGB.NET.Core.Exceptions +{ + /// + /// Represents an exception thrown by an . + /// + public class EffectException : ApplicationException + { + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The message which describes the reason of throwing this exception. + /// Optional inner exception, which lead to this exception. + public EffectException(string message, Exception innerException = null) + : base(message, innerException) + { } + + #endregion + } +} diff --git a/RGB.NET.Core/Groups/AbstractLedGroup.cs b/RGB.NET.Core/Groups/AbstractLedGroup.cs new file mode 100644 index 0000000..f00cc0a --- /dev/null +++ b/RGB.NET.Core/Groups/AbstractLedGroup.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace RGB.NET.Core +{ + /// + /// Represents a generic ledgroup. + /// + public abstract class AbstractLedGroup : AbstractEffectTarget, ILedGroup + { + #region Properties & Fields + + /// + /// Gets the strongly-typed target used for the effect. + /// + protected override ILedGroup EffectTarget => this; + + /// + public IBrush Brush { get; set; } + + /// + public int ZIndex { get; set; } = 0; + + #endregion + + #region Methods + + /// + public abstract IEnumerable GetLeds(); + + #endregion + } +} diff --git a/RGB.NET.Core/Groups/ILedGroup.cs b/RGB.NET.Core/Groups/ILedGroup.cs new file mode 100644 index 0000000..70d1eaa --- /dev/null +++ b/RGB.NET.Core/Groups/ILedGroup.cs @@ -0,0 +1,29 @@ +// ReSharper disable UnusedMemberInSuper.Global +// ReSharper disable UnusedMember.Global + +using System.Collections.Generic; + +namespace RGB.NET.Core +{ + /// + /// Represents a generic ledgroup. + /// + public interface ILedGroup : IEffectTarget + { + /// + /// Gets or sets the which should be drawn over this . + /// + IBrush Brush { get; set; } + + /// + /// Gets or sets the z-index of this to allow ordering them before drawing. (lowest first) (default: 0) + /// + int ZIndex { get; set; } + + /// + /// Gets a list containing all of this . + /// + /// The list containing all of this . + IEnumerable GetLeds(); + } +} diff --git a/RGB.NET.Core/RGB.NET.Core.csproj b/RGB.NET.Core/RGB.NET.Core.csproj index 6f069ce..7bee7ae 100644 --- a/RGB.NET.Core/RGB.NET.Core.csproj +++ b/RGB.NET.Core/RGB.NET.Core.csproj @@ -50,14 +50,17 @@ + + - - + + + diff --git a/RGB.NET.Core/RGB.NET.Core.csproj.DotSettings b/RGB.NET.Core/RGB.NET.Core.csproj.DotSettings index 7ec5688..4c65387 100644 --- a/RGB.NET.Core/RGB.NET.Core.csproj.DotSettings +++ b/RGB.NET.Core/RGB.NET.Core.csproj.DotSettings @@ -5,6 +5,7 @@ True True True + True True True True diff --git a/RGB.NET.Core/Surfaces/IRGBSurface.cs b/RGB.NET.Core/Surfaces/IRGBSurface.cs index 042f796..8810b10 100644 --- a/RGB.NET.Core/Surfaces/IRGBSurface.cs +++ b/RGB.NET.Core/Surfaces/IRGBSurface.cs @@ -1,4 +1,6 @@ -namespace RGB.NET.Core +using System.Collections.Generic; + +namespace RGB.NET.Core { #region EventHandler @@ -24,27 +26,58 @@ /// The arguments provided by the event. public delegate void UpdatedEventHandler(object sender, UpdatedEventArgs args); - /// - /// Represents the event-handler of the -event. - /// - /// The sender of the event. - /// The arguments provided by the event. - public delegate void LedsUpdatingEventHandler(object sender, LedsUpdatingEventArgs args); - - /// - /// Represents the event-handler of the -event. - /// - /// The sender of the event. - /// The arguments provided by the event. - public delegate void LedsUpdatedEventHandler(object sender, LedsUpdatedEventArgs args); - #endregion /// /// Represents a generic RGB-surface. /// - public interface IRGBSurface + public interface IRGBSurface : ILedGroup { + #region Properties & Fields + + /// + /// Gets a dictionary containing the locations of all positioned on this . + /// + Dictionary Devices { get; } + + /// + /// Gets a copy of the representing this . + /// + Rectangle SurfaceRectangle { get; } + + #endregion + + #region Methods + + /// + /// Perform an update for all dirty , or all , if flushLeds is set to true. + /// + /// Specifies whether all , (including clean ones) should be updated. + void Update(bool flushLeds = false); + + /// + /// Sets the location of the given to the given . + /// + /// The to move. + /// The target . + void PositionDevice(IRGBDevice device, Point location); + + /// + /// Attaches the given . + /// + /// The to attach. + /// true if the could be attached; otherwise, false. + bool AttachLedGroup(ILedGroup ledGroup); + + /// + /// Detaches the given . + /// + /// The to detached. + /// true if the could be detached; otherwise, false. + bool DetachLedGroup(ILedGroup ledGroup); + + #endregion + #region Events // ReSharper disable EventNeverSubscribedTo.Global @@ -64,16 +97,6 @@ /// event UpdatedEventHandler Updated; - /// - /// Occurs when the starts to update the leds. - /// - event LedsUpdatingEventHandler LedsUpdating; - - /// - /// Occurs when the updated the leds. - /// - event LedsUpdatedEventHandler LedsUpdated; - // ReSharper restore EventNeverSubscribedTo.Global #endregion diff --git a/RGB.NET.Core/Surfaces/RGBSurface.cs b/RGB.NET.Core/Surfaces/RGBSurface.cs index 0b182f5..fbbf712 100644 --- a/RGB.NET.Core/Surfaces/RGBSurface.cs +++ b/RGB.NET.Core/Surfaces/RGBSurface.cs @@ -1,17 +1,26 @@ using System; using System.Collections.Generic; +using System.Linq; namespace RGB.NET.Core { /// /// Represents a generic RGB-surface. /// - public class RGBSurface : IRGBSurface + public class RGBSurface : AbstractLedGroup, IRGBSurface { #region Properties & Fields private DateTime _lastUpdate; + /// + public Dictionary Devices { get; } = new Dictionary(); + + private readonly LinkedList _ledGroups = new LinkedList(); + + /// + public Rectangle SurfaceRectangle => new Rectangle(Devices.Select(x => x.Key.DeviceRectangle)); + #endregion #region Events @@ -27,12 +36,6 @@ namespace RGB.NET.Core /// public event UpdatedEventHandler Updated; - /// - public event LedsUpdatingEventHandler LedsUpdating; - - /// - public event LedsUpdatedEventHandler LedsUpdated; - // ReSharper restore EventNeverSubscribedTo.Global #endregion @@ -49,6 +52,137 @@ namespace RGB.NET.Core #endregion + #region Methods + + /// + public void Update(bool flushLeds = false) + { + OnUpdating(); + + lock (_ledGroups) + { + // Update effects + foreach (ILedGroup ledGroup in _ledGroups) + ledGroup.UpdateEffects(); + + // Render brushes + Render(this); + foreach (ILedGroup ledGroup in _ledGroups.OrderBy(x => x.ZIndex)) + Render(ledGroup); + } + + foreach (IRGBDevice device in Devices.Keys) + device.Update(flushLeds); + + OnUpdated(); + } + + /// + /// Renders a ledgroup. + /// + /// The led group to render. + private void Render(ILedGroup ledGroup) + { + IList leds = ledGroup.GetLeds().ToList(); + IBrush brush = ledGroup.Brush; + + try + { + switch (brush.BrushCalculationMode) + { + case BrushCalculationMode.Relative: + Rectangle brushRectangle = new Rectangle(leds.Select(x => GetDeviceLedLocation(x))); + Point offset = new Point(-brushRectangle.Location.X, -brushRectangle.Location.Y); + brushRectangle.Location.X = 0; + brushRectangle.Location.Y = 0; + brush.PerformRender(brushRectangle, + leds.Select(x => new BrushRenderTarget(x, GetDeviceLedLocation(x, offset)))); + break; + case BrushCalculationMode.Absolute: + brush.PerformRender(SurfaceRectangle, leds.Select(x => new BrushRenderTarget(x, GetDeviceLedLocation(x)))); + break; + default: + throw new ArgumentException(); + } + + brush.UpdateEffects(); + brush.PerformFinalize(); + + foreach (KeyValuePair renders in brush.RenderedTargets) + renders.Key.Led.Color = renders.Value; + } + // ReSharper disable once CatchAllClause + catch (Exception ex) + { + OnException(ex); + } + } + + private Rectangle GetDeviceLedLocation(Led led, Point extraOffset = null) + { + Point deviceLocation; + if (!Devices.TryGetValue(led.Device, out deviceLocation)) + deviceLocation = new Point(); + + return extraOffset != null + ? new Rectangle(led.LedRectangle.Location + deviceLocation + extraOffset, led.LedRectangle.Size) + : new Rectangle(led.LedRectangle.Location + deviceLocation, led.LedRectangle.Size); + } + + /// + public void PositionDevice(IRGBDevice device, Point location) + { + if (device == null) return; + + lock (Devices) + Devices[device] = location ?? new Point(); + } + + /// + /// Attaches the given . + /// + /// The to attach. + /// true if the could be attached; otherwise, false. + public bool AttachLedGroup(ILedGroup ledGroup) + { + if (ledGroup is IRGBSurface) return false; + if (ledGroup == null) return false; + + lock (_ledGroups) + { + if (_ledGroups.Contains(ledGroup)) return false; + + _ledGroups.AddLast(ledGroup); + return true; + } + } + + /// + /// Detaches the given . + /// + /// The to detached. + /// true if the could be detached; otherwise, false. + public bool DetachLedGroup(ILedGroup ledGroup) + { + if (ledGroup is IRGBSurface) return false; + if (ledGroup == null) return false; + + lock (_ledGroups) + { + LinkedListNode node = _ledGroups.Find(ledGroup); + if (node == null) return false; + + _ledGroups.Remove(node); + return true; + } + } + + /// + public override IEnumerable GetLeds() + { + return Devices.SelectMany(d => d.Key); + } + #region EventCaller /// @@ -99,35 +233,7 @@ namespace RGB.NET.Core } } - /// - /// Handles the needed event-calls before the are updated. - /// - protected virtual void OnLedsUpdating(ICollection updatingLeds) - { - try - { - LedsUpdating?.Invoke(this, new LedsUpdatingEventArgs(updatingLeds)); - } - catch - { - // Well ... that's not my fault - } - } - - /// - /// Handles the needed event-calls after the are updated. - /// - protected virtual void OnLedsUpdated(IEnumerable updatedLeds) - { - try - { - LedsUpdated?.Invoke(this, new LedsUpdatedEventArgs(updatedLeds)); - } - catch - { - // Well ... that's not my fault - } - } + #endregion #endregion }