// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable UnusedMethodReturnValue.Global
// ReSharper disable VirtualMemberNeverOverridden.Global
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using CUE.NET.Brushes;
using CUE.NET.Devices.Generic.Enums;
using CUE.NET.Devices.Generic.EventArgs;
using CUE.NET.Devices.Keyboard.Enums;
using CUE.NET.Effects;
using CUE.NET.Groups;
using CUE.NET.Helper;
using CUE.NET.Native;
namespace CUE.NET.Devices.Generic
{
///
/// Represents a generic CUE-device. (keyboard, mouse, headset, ...)
///
public abstract class AbstractCueDevice : ICueDevice
{
#region Properties & Fields
private static DateTime _lastUpdate = DateTime.Now;
///
/// Gets generic information provided by CUE for the device.
///
public IDeviceInfo DeviceInfo { get; }
///
/// Gets the rectangle containing all LEDs of the device.
///
public RectangleF DeviceRectangle { get; protected set; }
///
/// Gets a dictionary containing all LEDs of the device.
///
protected Dictionary LedMapping { get; } = new Dictionary();
///
/// Gets a read-only collection containing the LEDs of the device.
///
public IEnumerable Leds => new ReadOnlyCollection(LedMapping.Values.ToList());
///
/// Gets a list of attached ledgroups.
///
protected LinkedList LedGroups { get; } = new LinkedList();
///
/// Gets or sets the background brush of the keyboard.
///
public IBrush Brush { get; set; }
///
/// Gets or sets the z-index of the background brush of the keyboard.
/// This value has absolutely no effect.
///
public int ZIndex { get; set; } = 0;
#region Indexers
///
/// Gets the with the specified ID.
///
/// The ID of the LED to get.
/// The LED with the specified ID or null if no LED is found.
public CorsairLed this[CorsairLedId ledId]
{
get
{
CorsairLed key;
return LedMapping.TryGetValue(ledId, out key) ? key : null;
}
}
///
/// Gets the at the given physical location.
///
/// The point to get the location from.
/// The LED at the given point or null if no location is found.
public CorsairLed this[PointF location] => LedMapping.Values.FirstOrDefault(x => x.LedRectangle.Contains(location));
///
/// Gets a list of inside the given rectangle.
///
/// The rectangle to check.
/// The minimal percentage overlay a location must have with the to be taken into the list.
///
public IEnumerable this[RectangleF referenceRect, float minOverlayPercentage = 0.5f] => LedMapping.Values
.Where(x => RectangleHelper.CalculateIntersectPercentage(x.LedRectangle, referenceRect) >= minOverlayPercentage);
#endregion
#endregion
#region Events
///
/// Occurs when a catched exception is thrown inside the device.
///
public event ExceptionEventHandler Exception;
///
/// Occurs when the device starts updating.
///
public event UpdatingEventHandler Updating;
///
/// Occurs when the device update is done.
///
public event UpdatedEventHandler Updated;
///
/// Occurs when the device starts to update the leds.
///
public event LedsUpdatingEventHandler LedsUpdating;
///
/// Occurs when the device updated the leds.
///
public event LedsUpdatedEventHandler LedsUpdated;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The generic information provided by CUE for the device.
protected AbstractCueDevice(IDeviceInfo info)
{
this.DeviceInfo = info;
}
#endregion
#region Methods
#region Initialize
///
/// Initializes the device.
///
public virtual void Initialize()
{
DeviceRectangle = RectangleHelper.CreateRectangleFromRectangles((this).Select(x => x.LedRectangle));
}
///
/// Initializes the LED-Object with the specified id.
///
/// The LED-Id to initialize.
/// The rectangle representing the position of the LED to initialize.
///
protected CorsairLed InitializeLed(CorsairLedId ledId, RectangleF ledRectangle)
{
if (LedMapping.ContainsKey(ledId)) return null;
CorsairLed led = new CorsairLed(this, ledId, ledRectangle);
LedMapping.Add(ledId, led);
return led;
}
///
/// Resets all loaded LEDs back to default.
///
internal void ResetLeds()
{
foreach (CorsairLed led in LedMapping.Values)
led.Reset();
}
#endregion
#region Update
///
/// Performs an update for all dirty keys, or all keys if flushLeds is set to true.
///
/// Specifies whether all keys (including clean ones) should be updated.
public void Update(bool flushLeds = false)
{
OnUpdating();
// Update effects
foreach (ILedGroup ledGroup in LedGroups)
ledGroup.UpdateEffects();
// Render brushes
Render(this);
foreach (ILedGroup ledGroup in LedGroups.OrderBy(x => x.ZIndex))
Render(ledGroup);
// Device-specific updates
DeviceUpdate();
// Send LEDs to SDK
ICollection ledsToUpdate = (flushLeds ? LedMapping : LedMapping.Where(x => x.Value.IsDirty)).Select(x => new LedUpateRequest(x.Key, x.Value.RequestedColor)).ToList();
foreach (LedUpateRequest updateRequest in ledsToUpdate)
LedMapping[updateRequest.LedId].Update();
UpdateLeds(ledsToUpdate);
OnUpdated();
}
///
/// Performs device specific updates.
///
protected virtual void DeviceUpdate()
{ }
///
/// Renders a ledgroup.
///
/// The led group to render.
// ReSharper disable once MemberCanBeMadeStatic.Local - idc
protected virtual void Render(ILedGroup ledGroup)
{
if (ledGroup == null) return;
IList leds = ledGroup.GetLeds().ToList();
IBrush brush = ledGroup.Brush;
if (brush == null) return;
try
{
switch (brush.BrushCalculationMode)
{
case BrushCalculationMode.Relative:
RectangleF brushRectangle = RectangleHelper.CreateRectangleFromRectangles(leds.Select(x => x.LedRectangle));
float offsetX = -brushRectangle.X;
float offsetY = -brushRectangle.Y;
brushRectangle.X = 0;
brushRectangle.Y = 0;
brush.PerformRender(brushRectangle, leds.Select(x => new BrushRenderTarget(x.Id, x.LedRectangle.Move(offsetX, offsetY))));
break;
case BrushCalculationMode.Absolute:
brush.PerformRender(DeviceRectangle, leds.Select(x => new BrushRenderTarget(x.Id, x.LedRectangle)));
break;
default:
throw new ArgumentException();
}
brush.UpdateEffects();
brush.PerformFinalize();
foreach (KeyValuePair renders in brush.RenderedTargets)
this[renders.Key.LedId].Color = renders.Value;
}
// ReSharper disable once CatchAllClause
catch (Exception ex) { OnException(ex); }
}
private void UpdateLeds(ICollection updateRequests)
{
updateRequests = updateRequests.Where(x => x.Color != CorsairColor.Transparent).ToList();
OnLedsUpdating(updateRequests);
if (updateRequests.Any()) // CUE seems to crash if 'CorsairSetLedsColors' is called with a zero length array
{
int structSize = Marshal.SizeOf(typeof(_CorsairLedColor));
IntPtr ptr = Marshal.AllocHGlobal(structSize * updateRequests.Count);
IntPtr addPtr = new IntPtr(ptr.ToInt64());
foreach (LedUpateRequest ledUpdateRequest in updateRequests)
{
_CorsairLedColor color = new _CorsairLedColor
{
ledId = (int)ledUpdateRequest.LedId,
r = ledUpdateRequest.Color.R,
g = ledUpdateRequest.Color.G,
b = ledUpdateRequest.Color.B
};
Marshal.StructureToPtr(color, addPtr, false);
addPtr = new IntPtr(addPtr.ToInt64() + structSize);
}
_CUESDK.CorsairSetLedsColors(updateRequests.Count, ptr);
Marshal.FreeHGlobal(ptr);
}
OnLedsUpdated(updateRequests);
}
#endregion
#region LedGroup
///
/// Attaches the given ledgroup.
///
/// The ledgroup to attach.
/// true if the ledgroup could be attached; otherwise, false.
public bool AttachLedGroup(ILedGroup ledGroup)
{
lock (LedGroups)
{
if (ledGroup == null || LedGroups.Contains(ledGroup)) return false;
LedGroups.AddLast(ledGroup);
return true;
}
}
///
/// Detaches the given ledgroup.
///
/// The ledgroup to detached.
/// true if the ledgroup could be detached; otherwise, false.
public bool DetachLedGroup(ILedGroup ledGroup)
{
lock (LedGroups)
{
if (ledGroup == null) return false;
LinkedListNode node = LedGroups.Find(ledGroup);
if (node == null) return false;
LedGroups.Remove(node);
return true;
}
}
///
/// Gets a list containing all LEDs of this group.
///
/// The list containing all LEDs of this group.
public IEnumerable GetLeds()
{
return Leds;
}
#endregion
#region Effects
///
/// Gets a list of all active effects of this target.
/// For this device this is always null.
///
public IList> Effects => null;
///
/// NOT IMPLEMENTED: Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.
///
public void UpdateEffects()
{
throw new NotSupportedException("Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.");
}
///
/// NOT IMPLEMENTED: Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.
///
/// The effect to add.
public void AddEffect(IEffect effect)
{
throw new NotSupportedException("Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.");
}
///
/// NOT IMPLEMENTED: Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.
///
/// The effect to remove.
public void RemoveEffect(IEffect effect)
{
throw new NotSupportedException("Effects can't be applied directly to the device. Add it to the Brush or create a ledgroup instead.");
}
#endregion
#region EventCaller
///
/// Handles the needed event-calls for an exception.
///
/// The exception previously thrown.
protected virtual void OnException(Exception ex)
{
try
{
Exception?.Invoke(this, new ExceptionEventArgs(ex));
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls before updating.
///
protected virtual void OnUpdating()
{
try
{
long lastUpdateTicks = _lastUpdate.Ticks;
_lastUpdate = DateTime.Now;
Updating?.Invoke(this, new UpdatingEventArgs((DateTime.Now.Ticks - lastUpdateTicks) / 10000000f));
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls after an update.
///
protected virtual void OnUpdated()
{
try
{
Updated?.Invoke(this, new UpdatedEventArgs());
}
catch
{
// Well ... that's not my fault
}
}
///
/// Handles the needed event-calls before the leds 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 leds are updated.
///
protected virtual void OnLedsUpdated(IEnumerable updatedLeds)
{
try
{
LedsUpdated?.Invoke(this, new LedsUpdatedEventArgs(updatedLeds));
}
catch
{
// Well ... that's not my fault
}
}
#endregion
#region IEnumerable
///
/// Returns an enumerator that iterates over all LEDs of the device.
///
/// An enumerator for all LEDs of the device.
public IEnumerator GetEnumerator()
{
return LedMapping.Values.GetEnumerator();
}
///
/// Returns an enumerator that iterates over all LEDs of the device.
///
/// An enumerator for all LEDs of the device.
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#endregion
}
}