// ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Linq; namespace RGB.NET.Core { /// /// /// /// Represents a RGB-surface containing multiple devices. /// public class RGBSurface : AbstractBindable, IDisposable { #region Properties & Fields private Stopwatch _deltaTimeCounter; private IList _deviceProvider = new List(); private IList _devices = new List(); private IList _updateTriggers = new List(); // ReSharper disable InconsistentNaming private readonly LinkedList _ledGroups = new LinkedList(); // ReSharper restore InconsistentNaming /// /// Gets a readonly list containing all loaded . /// public IEnumerable Devices { get { lock (_devices) return new ReadOnlyCollection(_devices); } } /// /// Gets a readonly list containing all registered . /// public IEnumerable UpdateTriggers => new ReadOnlyCollection(_updateTriggers); /// /// Gets a copy of the representing this . /// public Rectangle SurfaceRectangle { get; private set; } /// /// Gets a list of all on this . /// public IEnumerable Leds { get { lock (_devices) return _devices.SelectMany(x => x); } } #endregion #region EventHandler /// /// Represents the event-handler of the -event. /// /// The arguments provided by the event. public delegate void ExceptionEventHandler(ExceptionEventArgs args); /// /// Represents the event-handler of the -event. /// /// The arguments provided by the event. public delegate void UpdatingEventHandler(UpdatingEventArgs args); /// /// Represents the event-handler of the -event. /// /// The arguments provided by the event. public delegate void UpdatedEventHandler(UpdatedEventArgs args); /// /// Represents the event-handler of the -event. /// /// public delegate void SurfaceLayoutChangedEventHandler(SurfaceLayoutChangedEventArgs args); #endregion #region Events // ReSharper disable EventNeverSubscribedTo.Global /// /// Occurs when a catched exception is thrown inside the . /// public event ExceptionEventHandler Exception; /// /// Occurs when the starts updating. /// public event UpdatingEventHandler Updating; /// /// Occurs when the update is done. /// public event UpdatedEventHandler Updated; /// /// Occurs when the layout of this changed. /// public event SurfaceLayoutChangedEventHandler SurfaceLayoutChanged; // ReSharper restore EventNeverSubscribedTo.Global #endregion #region Constructors /// /// Initializes a new instance of the class. /// public RGBSurface() { _deltaTimeCounter = Stopwatch.StartNew(); } #endregion #region Methods /// /// Perform a full update for all devices. Updates only dirty by default, or all , if flushLeds is set to true. /// /// Specifies whether all , (including clean ones) should be updated. public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData(("flushLeds", flushLeds))); private void Update(object updateTrigger, CustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData); private void Update(IUpdateTrigger updateTrigger, CustomUpdateData customData) { if (customData == null) customData = new CustomUpdateData(); try { bool flushLeds = customData["flushLeds"] as bool? ?? false; bool render = customData["render"] as bool? ?? true; bool updateDevices = customData["updateDevices"] as bool? ?? true; lock (_updateTriggers) lock (_devices) { OnUpdating(updateTrigger, customData); if (render) lock (_ledGroups) { // Render brushes foreach (ILedGroup ledGroup in _ledGroups.OrderBy(x => x.ZIndex)) try { Render(ledGroup); } catch (Exception ex) { OnException(ex); } } if (updateDevices) foreach (IRGBDevice device in _devices) if (!device.UpdateMode.HasFlag(DeviceUpdateMode.NoUpdate)) try { device.Update(flushLeds); } catch (Exception ex) { OnException(ex); } OnUpdated(); } } catch (Exception ex) { OnException(ex); } } /// public void Dispose() { lock (_devices) foreach (IRGBDevice device in _devices) try { device.Dispose(); } catch { /* We do what we can */} lock (_deviceProvider) foreach (IRGBDeviceProvider deviceProvider in _deviceProvider) try { deviceProvider.Dispose(); } catch { /* We do what we can */} foreach (IUpdateTrigger updateTrigger in _updateTriggers) try { updateTrigger.Dispose(); } catch { /* We do what we can */} _ledGroups.Clear(); _devices = null; _deviceProvider = null; } /// /// Renders a ledgroup. /// /// The led group to render. private void Render(ILedGroup ledGroup) { IList leds = ledGroup.GetLeds().ToList(); IBrush brush = ledGroup.Brush; if ((brush == null) || !brush.IsEnabled) return; switch (brush.BrushCalculationMode) { case BrushCalculationMode.Relative: Rectangle brushRectangle = new Rectangle(leds.Select(led => led.AbsoluteLedRectangle)); Point offset = new Point(-brushRectangle.Location.X, -brushRectangle.Location.Y); brushRectangle = brushRectangle.SetLocation(new Point(0, 0)); brush.PerformRender(brushRectangle, leds.Select(led => new BrushRenderTarget(led, led.AbsoluteLedRectangle.Translate(offset)))); break; case BrushCalculationMode.Absolute: brush.PerformRender(SurfaceRectangle, leds.Select(led => new BrushRenderTarget(led, led.AbsoluteLedRectangle))); break; default: throw new ArgumentException(); } //brush.UpdateEffects(); brush.PerformFinalize(); foreach (KeyValuePair renders in brush.RenderedTargets) renders.Key.Led.Color = renders.Value; } /// /// Attaches the given . /// /// The to attach. /// true if the could be attached; otherwise, false. public bool AttachLedGroup(ILedGroup ledGroup) { if (ledGroup == null) return false; lock (_ledGroups) { if (_ledGroups.Contains(ledGroup)) return false; _ledGroups.AddLast(ledGroup); ledGroup.OnAttach(this); return true; } } /// /// Detaches the given . /// /// The to detached. /// true if the could be detached; otherwise, false. public bool DetachLedGroup(ILedGroup ledGroup) { if (ledGroup == null) return false; lock (_ledGroups) { LinkedListNode node = _ledGroups.Find(ledGroup); if (node == null) return false; _ledGroups.Remove(node); node.Value.OnDetach(this); return true; } } // ReSharper disable UnusedMember.Global /// /// Loads all devices the given by the provided by the give . /// /// The which provides the to load the devices from. /// Specifies which types of devices to load. /// Specifies whether the application should request exclusive access of possible or not. /// Specifies whether exception during the initialization sequence should be thrown or not. public void LoadDevices(IRGBDeviceProviderLoader deviceProviderLoader, RGBDeviceType loadFilter = RGBDeviceType.All, bool exclusiveAccessIfPossible = false, bool throwExceptions = false) => LoadDevices(deviceProviderLoader.GetDeviceProvider(), loadFilter, exclusiveAccessIfPossible, throwExceptions); /// /// Loads all devices the given is able to provide. /// /// The to load the devices from. /// Specifies which types of devices to load. /// Specifies whether the application should request exclusive access of possible or not. /// Specifies whether exception during the initialization sequence should be thrown or not. public void LoadDevices(IRGBDeviceProvider deviceProvider, RGBDeviceType loadFilter = RGBDeviceType.All, bool exclusiveAccessIfPossible = false, bool throwExceptions = false) { lock (_deviceProvider) { if (_deviceProvider.Contains(deviceProvider) || _deviceProvider.Any(x => x.GetType() == deviceProvider.GetType())) return; List addedDevices = new List(); if (deviceProvider.IsInitialized || deviceProvider.Initialize(loadFilter, exclusiveAccessIfPossible, throwExceptions)) { _deviceProvider.Add(deviceProvider); lock (_devices) foreach (IRGBDevice device in deviceProvider.Devices) { if (_devices.Contains(device)) continue; addedDevices.Add(device); device.PropertyChanged += DeviceOnPropertyChanged; _devices.Add(device); } } if (addedDevices.Any()) { UpdateSurfaceRectangle(); SurfaceLayoutChanged?.Invoke(new SurfaceLayoutChangedEventArgs(addedDevices, true, false)); } } } /// /// Automatically aligns all devices to prevent overlaps. /// public void AlignDevices() { double posX = 0; foreach (IRGBDevice device in Devices) { device.Location += new Point(posX, 0); posX += device.ActualSize.Width + 1; } } // ReSharper restore UnusedMember.Global private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs) { UpdateSurfaceRectangle(); SurfaceLayoutChanged?.Invoke(new SurfaceLayoutChangedEventArgs(new[] { sender as IRGBDevice }, false, true)); } private void UpdateSurfaceRectangle() { lock (_devices) { Rectangle devicesRectangle = new Rectangle(_devices.Select(d => d.DeviceRectangle)); SurfaceRectangle = SurfaceRectangle.SetSize(new Size(devicesRectangle.Location.X + devicesRectangle.Size.Width, devicesRectangle.Location.Y + devicesRectangle.Size.Height)); } } /// /// Gets all devices of a specific type. /// /// The type of devices to get. /// A list of devices with the specified type. public IList GetDevices() where T : class { lock (_devices) return new ReadOnlyCollection(_devices.Select(x => x as T).Where(x => x != null).ToList()); } /// /// Gets all devices of the specified . /// /// The of the devices to get. /// a list of devices matching the specified . public IList GetDevices(RGBDeviceType deviceType) { lock (_devices) return new ReadOnlyCollection(_devices.Where(d => deviceType.HasFlag(d.DeviceInfo.DeviceType)).ToList()); } /// /// Registers the provided . /// /// The to register. public void RegisterUpdateTrigger(IUpdateTrigger updateTrigger) { if (!_updateTriggers.Contains(updateTrigger)) { _updateTriggers.Add(updateTrigger); updateTrigger.Update += Update; } } /// /// Unregisters the provided . /// /// The to unregister. public void UnregisterUpdateTrigger(IUpdateTrigger updateTrigger) { if (_updateTriggers.Remove(updateTrigger)) updateTrigger.Update -= Update; } /// /// Handles the needed event-calls for an exception. /// /// The exception previously thrown. private void OnException(Exception ex) { try { Exception?.Invoke(new ExceptionEventArgs(ex)); } catch { /* Well ... that's not my fault */ } } /// /// Handles the needed event-calls before updating. /// private void OnUpdating(IUpdateTrigger trigger, CustomUpdateData customData) { try { double deltaTime = _deltaTimeCounter.Elapsed.TotalSeconds; _deltaTimeCounter.Restart(); Updating?.Invoke(new UpdatingEventArgs(deltaTime, trigger, customData)); } catch { /* Well ... that's not my fault */ } } /// /// Handles the needed event-calls after an update. /// private void OnUpdated() { try { Updated?.Invoke(new UpdatedEventArgs()); } catch { /* Well ... that's not my fault */ } } #endregion } }