From fd662dcf341b211c24658ea77a95d84607c06529 Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 4 Jun 2018 22:36:56 +0200 Subject: [PATCH] Initial implementation --- RGBSync+/App.config | 6 + RGBSync+/App.xaml | 26 + RGBSync+/App.xaml.cs | 78 +++ RGBSync+/ApplicationManager.cs | 150 ++++++ RGBSync+/Attached/SliderValue.cs | 111 ++++ RGBSync+/Attached/SliderValueAdorner.cs | 99 ++++ RGBSync+/Brushes/SyncBrush.cs | 48 ++ .../Configuration/AbstractConfiguration.cs | 31 ++ RGBSync+/Configuration/ColorSerializer.cs | 41 ++ RGBSync+/Configuration/IConfiguration.cs | 7 + .../Legacy/ConfigurationUpdates.cs | 12 + RGBSync+/Configuration/Settings.cs | 24 + RGBSync+/Controls/BlurredDecorationWindow.cs | 87 ++++ RGBSync+/Controls/ColorSelector.cs | 483 ++++++++++++++++++ RGBSync+/Controls/Form.cs | 212 ++++++++ RGBSync+/Controls/GradientEditor.cs | 424 +++++++++++++++ RGBSync+/Controls/ImageButton.cs | 51 ++ .../Converter/BoolToVisibilityConverter.cs | 21 + RGBSync+/Converter/EqualsToBoolConverter.cs | 18 + .../Converter/NullToVisibilityConverter.cs | 20 + .../ScrollOffsetToOpacityMaskConverter.cs | 61 +++ .../ScrollOffsetToVisibilityConverter.cs | 29 ++ RGBSync+/Helper/ActionCommand.cs | 77 +++ RGBSync+/Helper/ExceptionExtension.cs | 23 + RGBSync+/Helper/MathHelper.cs | 15 + RGBSync+/Helper/RGBNetExtension.cs | 21 + RGBSync+/Model/SyncGroup.cs | 48 ++ RGBSync+/Model/SyncLed.cs | 80 +++ RGBSync+/Properties/AssemblyInfo.cs | 55 ++ RGBSync+/Properties/Resources.Designer.cs | 63 +++ RGBSync+/Properties/Resources.resx | 117 +++++ RGBSync+/Properties/Settings.Designer.cs | 26 + RGBSync+/Properties/Settings.settings | 7 + RGBSync+/RGBSync+.csproj | 236 +++++++++ RGBSync+/RGBSync+.sln | 25 + RGBSync+/Resources/RGBSync+.xaml | 42 ++ RGBSync+/Resources/argebee.ico | Bin 0 -> 40322 bytes RGBSync+/Resources/arrow_down.png | Bin 0 -> 4027 bytes RGBSync+/Resources/arrow_up.png | Bin 0 -> 3969 bytes RGBSync+/Resources/background.png | Bin 0 -> 279849 bytes RGBSync+/Resources/close.png | Bin 0 -> 4800 bytes RGBSync+/Resources/font.ttf | Bin 0 -> 56228 bytes RGBSync+/Resources/minimize.png | Bin 0 -> 1821 bytes RGBSync+/Resources/navigation.ttf | Bin 0 -> 62448 bytes RGBSync+/Styles/BlurredDecorationWindow.xaml | 126 +++++ RGBSync+/Styles/Button.xaml | 41 ++ RGBSync+/Styles/CachedResourceDictionary.cs | 56 ++ RGBSync+/Styles/ColorSelector.xaml | 305 +++++++++++ RGBSync+/Styles/ComboBox.xaml | 144 ++++++ RGBSync+/Styles/Form.xaml | 99 ++++ RGBSync+/Styles/FrameworkElement.xaml | 13 + RGBSync+/Styles/GradientEditor.xaml | 93 ++++ RGBSync+/Styles/GroupBox.xaml | 35 ++ RGBSync+/Styles/ImageButton.xaml | 119 +++++ RGBSync+/Styles/ListBox.xaml | 164 ++++++ RGBSync+/Styles/Navigation.xaml | 181 +++++++ RGBSync+/Styles/Slider.xaml | 31 ++ RGBSync+/Styles/TextBox.xaml | 45 ++ RGBSync+/Styles/Theme.xaml | 75 +++ RGBSync+/Styles/ToolTip.xaml | 31 ++ RGBSync+/UI/ConfigurationViewModel.cs | 183 +++++++ RGBSync+/UI/ConfigurationWindow.xaml | 250 +++++++++ RGBSync+/UI/ConfigurationWindow.xaml.cs | 16 + 63 files changed, 4881 insertions(+) create mode 100644 RGBSync+/App.config create mode 100644 RGBSync+/App.xaml create mode 100644 RGBSync+/App.xaml.cs create mode 100644 RGBSync+/ApplicationManager.cs create mode 100644 RGBSync+/Attached/SliderValue.cs create mode 100644 RGBSync+/Attached/SliderValueAdorner.cs create mode 100644 RGBSync+/Brushes/SyncBrush.cs create mode 100644 RGBSync+/Configuration/AbstractConfiguration.cs create mode 100644 RGBSync+/Configuration/ColorSerializer.cs create mode 100644 RGBSync+/Configuration/IConfiguration.cs create mode 100644 RGBSync+/Configuration/Legacy/ConfigurationUpdates.cs create mode 100644 RGBSync+/Configuration/Settings.cs create mode 100644 RGBSync+/Controls/BlurredDecorationWindow.cs create mode 100644 RGBSync+/Controls/ColorSelector.cs create mode 100644 RGBSync+/Controls/Form.cs create mode 100644 RGBSync+/Controls/GradientEditor.cs create mode 100644 RGBSync+/Controls/ImageButton.cs create mode 100644 RGBSync+/Converter/BoolToVisibilityConverter.cs create mode 100644 RGBSync+/Converter/EqualsToBoolConverter.cs create mode 100644 RGBSync+/Converter/NullToVisibilityConverter.cs create mode 100644 RGBSync+/Converter/ScrollOffsetToOpacityMaskConverter.cs create mode 100644 RGBSync+/Converter/ScrollOffsetToVisibilityConverter.cs create mode 100644 RGBSync+/Helper/ActionCommand.cs create mode 100644 RGBSync+/Helper/ExceptionExtension.cs create mode 100644 RGBSync+/Helper/MathHelper.cs create mode 100644 RGBSync+/Helper/RGBNetExtension.cs create mode 100644 RGBSync+/Model/SyncGroup.cs create mode 100644 RGBSync+/Model/SyncLed.cs create mode 100644 RGBSync+/Properties/AssemblyInfo.cs create mode 100644 RGBSync+/Properties/Resources.Designer.cs create mode 100644 RGBSync+/Properties/Resources.resx create mode 100644 RGBSync+/Properties/Settings.Designer.cs create mode 100644 RGBSync+/Properties/Settings.settings create mode 100644 RGBSync+/RGBSync+.csproj create mode 100644 RGBSync+/RGBSync+.sln create mode 100644 RGBSync+/Resources/RGBSync+.xaml create mode 100644 RGBSync+/Resources/argebee.ico create mode 100644 RGBSync+/Resources/arrow_down.png create mode 100644 RGBSync+/Resources/arrow_up.png create mode 100644 RGBSync+/Resources/background.png create mode 100644 RGBSync+/Resources/close.png create mode 100644 RGBSync+/Resources/font.ttf create mode 100644 RGBSync+/Resources/minimize.png create mode 100644 RGBSync+/Resources/navigation.ttf create mode 100644 RGBSync+/Styles/BlurredDecorationWindow.xaml create mode 100644 RGBSync+/Styles/Button.xaml create mode 100644 RGBSync+/Styles/CachedResourceDictionary.cs create mode 100644 RGBSync+/Styles/ColorSelector.xaml create mode 100644 RGBSync+/Styles/ComboBox.xaml create mode 100644 RGBSync+/Styles/Form.xaml create mode 100644 RGBSync+/Styles/FrameworkElement.xaml create mode 100644 RGBSync+/Styles/GradientEditor.xaml create mode 100644 RGBSync+/Styles/GroupBox.xaml create mode 100644 RGBSync+/Styles/ImageButton.xaml create mode 100644 RGBSync+/Styles/ListBox.xaml create mode 100644 RGBSync+/Styles/Navigation.xaml create mode 100644 RGBSync+/Styles/Slider.xaml create mode 100644 RGBSync+/Styles/TextBox.xaml create mode 100644 RGBSync+/Styles/Theme.xaml create mode 100644 RGBSync+/Styles/ToolTip.xaml create mode 100644 RGBSync+/UI/ConfigurationViewModel.cs create mode 100644 RGBSync+/UI/ConfigurationWindow.xaml create mode 100644 RGBSync+/UI/ConfigurationWindow.xaml.cs diff --git a/RGBSync+/App.config b/RGBSync+/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/RGBSync+/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/RGBSync+/App.xaml b/RGBSync+/App.xaml new file mode 100644 index 0000000..b8e1030 --- /dev/null +++ b/RGBSync+/App.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + diff --git a/RGBSync+/App.xaml.cs b/RGBSync+/App.xaml.cs new file mode 100644 index 0000000..cb651cb --- /dev/null +++ b/RGBSync+/App.xaml.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using Hardcodet.Wpf.TaskbarNotification; +using Newtonsoft.Json; +using RGBSyncPlus.Configuration; +using RGBSyncPlus.Configuration.Legacy; +using RGBSyncPlus.Helper; + +namespace RGBSyncPlus +{ + public partial class App : Application + { + #region Constants + + private const string PATH_SETTINGS = "Settings.json"; + + #endregion + + #region Properties & Fields + + private TaskbarIcon _taskbarIcon; + + #endregion + + #region Methods + + protected override void OnStartup(StartupEventArgs e) + { + base.OnStartup(e); + + try + { + ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(int.MaxValue)); + + _taskbarIcon = (TaskbarIcon)FindResource("TaskbarIcon"); + _taskbarIcon.DoubleClickCommand = ApplicationManager.Instance.OpenConfigurationCommand; + + Settings settings = null; + try { settings = JsonConvert.DeserializeObject(File.ReadAllText(PATH_SETTINGS), new ColorSerializer()); } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + /* File doesn't exist or is corrupt - just create a new one. */ + } + + if (settings == null) + { + settings = new Settings { Version = Settings.CURRENT_VERSION }; + _taskbarIcon.ShowBalloonTip("RGBSync+ is starting in the tray!", "Click on the icon to open the configuration.", BalloonIcon.Info); + } + else if (settings.Version != Settings.CURRENT_VERSION) + ConfigurationUpdates.PerformOn(settings); + + ApplicationManager.Instance.Settings = settings; + ApplicationManager.Instance.Initialize(); + } + catch (Exception ex) + { + File.WriteAllText("error.log", $"[{DateTime.Now:G}] Exception!\r\n\r\nMessage:\r\n{ex.GetFullMessage()}\r\n\r\nStackTrace:\r\n{ex.StackTrace}\r\n\r\n"); + MessageBox.Show("An error occured while starting RGBSync+.\r\nMore information can be found in the error.log file in the application directory.", "Can't start RGBSync+."); + + try { ApplicationManager.Instance.ExitCommand.Execute(null); } + catch { Environment.Exit(0); } + } + } + + protected override void OnExit(ExitEventArgs e) + { + base.OnExit(e); + + File.WriteAllText(PATH_SETTINGS, JsonConvert.SerializeObject(ApplicationManager.Instance.Settings, new ColorSerializer())); + } + + #endregion + } +} diff --git a/RGBSync+/ApplicationManager.cs b/RGBSync+/ApplicationManager.cs new file mode 100644 index 0000000..c5fb75d --- /dev/null +++ b/RGBSync+/ApplicationManager.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Windows; +using RGB.NET.Core; +using RGB.NET.Groups; +using RGBSyncPlus.Brushes; +using RGBSyncPlus.Configuration; +using RGBSyncPlus.Helper; +using RGBSyncPlus.Model; +using RGBSyncPlus.UI; + +namespace RGBSyncPlus +{ + public class ApplicationManager + { + #region Constants + + private const string DEVICEPROVIDER_DIRECTORY = "DeviceProvider"; + + #endregion + + #region Properties & Fields + + public static ApplicationManager Instance { get; } = new ApplicationManager(); + + private ConfigurationWindow _configurationWindow; + + public Settings Settings { get; set; } + public TimerUpdateTrigger UpdateTrigger { get; private set; } + + #endregion + + #region Commands + + private ActionCommand _openConfiguration; + public ActionCommand OpenConfigurationCommand => _openConfiguration ?? (_openConfiguration = new ActionCommand(OpenConfiguration)); + + private ActionCommand _exitCommand; + public ActionCommand ExitCommand => _exitCommand ?? (_exitCommand = new ActionCommand(Exit)); + + #endregion + + #region Constructors + + private ApplicationManager() { } + + #endregion + + #region Methods + + public void Initialize() + { + RGBSurface surface = RGBSurface.Instance; + LoadDeviceProviders(); + surface.AlignDevices(); + + foreach (IRGBDevice device in surface.Devices) + device.UpdateMode = DeviceUpdateMode.Sync | DeviceUpdateMode.SyncBack; + + UpdateTrigger = new TimerUpdateTrigger { UpdateFrequency = 1.0 / MathHelper.Clamp(Settings.UpdateRate, 1, 100) }; + surface.RegisterUpdateTrigger(UpdateTrigger); + UpdateTrigger.Start(); + + foreach (SyncGroup syncGroup in Settings.SyncGroups) + RegisterSyncGroup(syncGroup); + } + + private void LoadDeviceProviders() + { + string deviceProvierDir = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) ?? string.Empty, DEVICEPROVIDER_DIRECTORY); + if (!Directory.Exists(deviceProvierDir)) return; + + foreach (string file in Directory.GetFiles(deviceProvierDir, "*.dll")) + { + try + { + Assembly assembly = Assembly.LoadFrom(file); + foreach (Type loaderType in assembly.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && t.IsClass + && typeof(IRGBDeviceProviderLoader).IsAssignableFrom(t))) + { + if (Activator.CreateInstance(loaderType) is IRGBDeviceProviderLoader deviceProviderLoader) + { + //TODO DarthAffe 03.06.2018: Support Initialization + if (deviceProviderLoader.RequiresInitialization) continue; + + RGBSurface.Instance.LoadDevices(deviceProviderLoader); + } + } + } + catch { /* #sadprogrammer */ } + } + } + + public void AddSyncGroup(SyncGroup syncGroup) + { + Settings.SyncGroups.Add(syncGroup); + RegisterSyncGroup(syncGroup); + } + + private void RegisterSyncGroup(SyncGroup syncGroup) + { + syncGroup.LedGroup = new ListLedGroup(syncGroup.Leds.GetLeds()) { Brush = new SyncBrush(syncGroup) }; + syncGroup.LedsChangedEventHandler = (sender, args) => UpdateLedGroup(syncGroup.LedGroup, args); + syncGroup.Leds.CollectionChanged += syncGroup.LedsChangedEventHandler; + } + + public void RemoveSyncGroup(SyncGroup syncGroup) + { + Settings.SyncGroups.Remove(syncGroup); + syncGroup.Leds.CollectionChanged -= syncGroup.LedsChangedEventHandler; + syncGroup.LedGroup.Detach(); + syncGroup.LedGroup = null; + } + + private void UpdateLedGroup(ListLedGroup group, NotifyCollectionChangedEventArgs args) + { + if (args.Action == NotifyCollectionChangedAction.Reset) + { + List leds = group.GetLeds().ToList(); + group.RemoveLeds(leds); + } + else + { + if (args.NewItems != null) + group.AddLeds(args.NewItems.Cast().GetLeds()); + + if (args.OldItems != null) + group.RemoveLeds(args.OldItems.Cast().GetLeds()); + } + } + + private void OpenConfiguration() + { + if (_configurationWindow == null) _configurationWindow = new ConfigurationWindow(); + _configurationWindow.Show(); + } + + private void Exit() + { + try { RGBSurface.Instance?.Dispose(); } catch { /* well, we're shuting down anyway ... */ } + Application.Current.Shutdown(); + } + + #endregion + } +} diff --git a/RGBSync+/Attached/SliderValue.cs b/RGBSync+/Attached/SliderValue.cs new file mode 100644 index 0000000..ab43c1d --- /dev/null +++ b/RGBSync+/Attached/SliderValue.cs @@ -0,0 +1,111 @@ +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; + +namespace RGBSyncPlus.Attached +{ + public static class SliderValue + { + #region Properties & Fields + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty UnitProperty = DependencyProperty.RegisterAttached( + "Unit", typeof(string), typeof(SliderValue), new PropertyMetadata(default(string))); + + public static void SetUnit(DependencyObject element, string value) => element.SetValue(UnitProperty, value); + public static string GetUnit(DependencyObject element) => (string)element.GetValue(UnitProperty); + + public static readonly DependencyProperty IsShownProperty = DependencyProperty.RegisterAttached( + "IsShown", typeof(bool), typeof(SliderValue), new PropertyMetadata(default(bool), IsShownChanged)); + + public static void SetIsShown(DependencyObject element, bool value) => element.SetValue(IsShownProperty, value); + public static bool GetIsShown(DependencyObject element) => (bool)element.GetValue(IsShownProperty); + + public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.RegisterAttached( + "BorderBrush", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetBorderBrush(DependencyObject element, Brush value) => element.SetValue(BorderBrushProperty, value); + public static Brush GetBorderBrush(DependencyObject element) => (Brush)element.GetValue(BorderBrushProperty); + + public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached( + "Background", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetBackground(DependencyObject element, Brush value) => element.SetValue(BackgroundProperty, value); + public static Brush GetBackground(DependencyObject element) => (Brush)element.GetValue(BackgroundProperty); + + public static readonly DependencyProperty ForegroundProperty = DependencyProperty.RegisterAttached( + "Foreground", typeof(Brush), typeof(SliderValue), new PropertyMetadata(default(Brush))); + + public static void SetForeground(DependencyObject element, Brush value) => element.SetValue(ForegroundProperty, value); + public static Brush GetForeground(DependencyObject element) => (Brush)element.GetValue(ForegroundProperty); + + public static readonly DependencyProperty FontProperty = DependencyProperty.RegisterAttached( + "Font", typeof(FontFamily), typeof(SliderValue), new PropertyMetadata(default(FontFamily))); + + public static void SetFont(DependencyObject element, FontFamily value) => element.SetValue(FontProperty, value); + public static FontFamily GetFont(DependencyObject element) => (FontFamily)element.GetValue(FontProperty); + + public static readonly DependencyProperty FontSizeProperty = DependencyProperty.RegisterAttached( + "FontSize", typeof(double), typeof(SliderValue), new PropertyMetadata(default(double))); + + public static void SetFontSize(DependencyObject element, double value) => element.SetValue(FontSizeProperty, value); + public static double GetFontSize(DependencyObject element) => (double)element.GetValue(FontSizeProperty); + + // ReSharper enable InconsistentNaming + #endregion + + #region Methods + + private static void IsShownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + if (!(dependencyObject is Slider slider)) return; + + if (dependencyPropertyChangedEventArgs.NewValue as bool? == true) + { + slider.MouseEnter += SliderOnMouseEnter; + slider.MouseLeave += SliderOnMouseLeave; + } + else + { + slider.MouseEnter -= SliderOnMouseEnter; + slider.MouseLeave -= SliderOnMouseLeave; + RemoveAdorner(slider); + } + } + + private static void SliderOnMouseEnter(object sender, MouseEventArgs mouseEventArgs) + { + if (!(sender is Slider slider)) return; + AdornerLayer.GetAdornerLayer(slider)?.Add(new SliderValueAdorner(slider, GetUnit(slider)) + { + BorderBrush = GetBorderBrush(slider), + Background = GetBackground(slider), + Foreground = GetForeground(slider), + Font = GetFont(slider), + FontSize = GetFontSize(slider) + }); + } + + private static void SliderOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) + { + if (!(sender is Slider slider)) return; + RemoveAdorner(slider); + } + + private static void RemoveAdorner(Slider slider) + { + AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(slider); + Adorner adorner = adornerLayer?.GetAdorners(slider)?.FirstOrDefault(x => x is SliderValueAdorner); + if (adorner != null) + { + adornerLayer.Remove(adorner); + (adorner as SliderValueAdorner)?.Cleanup(); + } + } + + #endregion + } +} diff --git a/RGBSync+/Attached/SliderValueAdorner.cs b/RGBSync+/Attached/SliderValueAdorner.cs new file mode 100644 index 0000000..bc44983 --- /dev/null +++ b/RGBSync+/Attached/SliderValueAdorner.cs @@ -0,0 +1,99 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using Point = System.Windows.Point; + +namespace RGBSyncPlus.Attached +{ + public class SliderValueAdorner : System.Windows.Documents.Adorner + { + #region Properties & Fields + + private readonly string _unit; + private readonly Slider _slider; + private readonly Thumb _thumb; + private readonly RepeatButton _decreaseRepeatButton; + + public Brush BorderBrush { get; set; } = System.Windows.Media.Brushes.Black; + public Brush Background { get; set; } = System.Windows.Media.Brushes.Black; + public Brush Foreground { get; set; } = System.Windows.Media.Brushes.White; + public FontFamily Font { get; set; } = new FontFamily("Verdana"); + public double FontSize { get; set; } = 14; + + #endregion + + #region Constructors + + public SliderValueAdorner(UIElement adornedElement, string unit) + : base(adornedElement) + { + this._unit = unit; + + _slider = (Slider)adornedElement; + Track track = (Track)_slider.Template.FindName("PART_Track", _slider); + + _thumb = track.Thumb; + _decreaseRepeatButton = track.DecreaseRepeatButton; + _decreaseRepeatButton.SizeChanged += OnButtonSizeChanged; + } + + #endregion + + #region Methods + + public void Cleanup() + { + _decreaseRepeatButton.SizeChanged -= OnButtonSizeChanged; + } + + private void OnButtonSizeChanged(object sender, SizeChangedEventArgs sizeChangedEventArgs) => InvalidateVisual(); + + protected override void OnRender(DrawingContext drawingContext) + { + double offset = _decreaseRepeatButton.ActualWidth + (_thumb.ActualWidth / 2.0); + + FormattedText text = new FormattedText(GetText(), CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface(Font, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal), FontSize, Foreground); + Geometry border = CreateBorder(offset, text.Width, text.Height); + + drawingContext.DrawGeometry(Background, new Pen(BorderBrush, 1), border); + drawingContext.DrawText(text, new Point(offset - (text.Width / 2.0), -26)); + } + + private string GetText() + { + string valueText = _slider.Value.ToString(); + if (!string.IsNullOrWhiteSpace(_unit)) + valueText += " " + _unit; + + return valueText; + } + + private Geometry CreateBorder(double offset, double width, double height) + { + double halfWidth = width / 2.0; + + PathGeometry borderGeometry = new PathGeometry(); + PathFigure border = new PathFigure + { + StartPoint = new Point(offset, 0), + IsClosed = true, + IsFilled = true + }; + + border.Segments.Add(new LineSegment(new Point(offset + 4, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset + 4 + halfWidth, -10 - height), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -10 - height), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4 - halfWidth, -6), true)); + border.Segments.Add(new LineSegment(new Point(offset - 4, -6), true)); + + borderGeometry.Figures.Add(border); + + return borderGeometry; + } + + #endregion + } +} diff --git a/RGBSync+/Brushes/SyncBrush.cs b/RGBSync+/Brushes/SyncBrush.cs new file mode 100644 index 0000000..9f64245 --- /dev/null +++ b/RGBSync+/Brushes/SyncBrush.cs @@ -0,0 +1,48 @@ +using System.ComponentModel; +using RGB.NET.Core; +using RGBSyncPlus.Helper; +using RGBSyncPlus.Model; + +namespace RGBSyncPlus.Brushes +{ + public class SyncBrush : AbstractBrush + { + #region Properties & Fields + + private readonly SyncGroup _syncGroup; + + private Led _syncLed; + + #endregion + + #region Constructors + + public SyncBrush(SyncGroup syncGroup) + { + this._syncGroup = syncGroup; + + syncGroup.PropertyChanged += SyncGroupOnPropertyChanged; + _syncLed = syncGroup.SyncLed?.GetLed(); + } + + #endregion + + #region Methods + + private void SyncGroupOnPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(SyncGroup.SyncLed)) + _syncLed = _syncGroup.SyncLed?.GetLed(); + } + + protected override Color GetColorAtPoint(Rectangle rectangle, BrushRenderTarget renderTarget) + { + if (renderTarget.Led == _syncLed) + return Color.Transparent; + + return _syncLed?.Color ?? Color.Transparent; + } + + #endregion + } +} diff --git a/RGBSync+/Configuration/AbstractConfiguration.cs b/RGBSync+/Configuration/AbstractConfiguration.cs new file mode 100644 index 0000000..69f897f --- /dev/null +++ b/RGBSync+/Configuration/AbstractConfiguration.cs @@ -0,0 +1,31 @@ +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using RGB.NET.Core; + +namespace RGBSyncPlus.Configuration +{ + public class AbstractConfiguration : AbstractBindable, IConfiguration, INotifyPropertyChanged + { + #region Methods + + protected override bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if ((typeof(T) == typeof(double)) || (typeof(T) == typeof(float))) + { + if (Math.Abs((double)(object)storage - (double)(object)value) < 0.000001) return false; + } + else + { + if (Equals(storage, value)) return false; + } + + storage = value; + // ReSharper disable once ExplicitCallerInfoArgument + OnPropertyChanged(propertyName); + return true; + } + + #endregion + } +} diff --git a/RGBSync+/Configuration/ColorSerializer.cs b/RGBSync+/Configuration/ColorSerializer.cs new file mode 100644 index 0000000..7f2bae3 --- /dev/null +++ b/RGBSync+/Configuration/ColorSerializer.cs @@ -0,0 +1,41 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using RGB.NET.Core; + +namespace RGBSyncPlus.Configuration +{ + public class ColorSerializer : JsonConverter + { + #region Methods + + public override bool CanConvert(Type objectType) => objectType == typeof(Color); + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (!(value is Color color)) return; + + writer.WriteStartObject(); + writer.WritePropertyName("A"); + writer.WriteValue(color.A); + writer.WritePropertyName("R"); + writer.WriteValue(color.R); + writer.WritePropertyName("G"); + writer.WriteValue(color.G); + writer.WritePropertyName("B"); + writer.WriteValue(color.B); + writer.WriteEndObject(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + JObject jsonObject = JObject.Load(reader); + return new Color(jsonObject.Property("A").Value.ToObject(), + jsonObject.Property("R").Value.ToObject(), + jsonObject.Property("G").Value.ToObject(), + jsonObject.Property("B").Value.ToObject()); + } + + #endregion + } +} diff --git a/RGBSync+/Configuration/IConfiguration.cs b/RGBSync+/Configuration/IConfiguration.cs new file mode 100644 index 0000000..65361f7 --- /dev/null +++ b/RGBSync+/Configuration/IConfiguration.cs @@ -0,0 +1,7 @@ +using System.ComponentModel; + +namespace RGBSyncPlus.Configuration +{ + public interface IConfiguration : INotifyPropertyChanged + { } +} diff --git a/RGBSync+/Configuration/Legacy/ConfigurationUpdates.cs b/RGBSync+/Configuration/Legacy/ConfigurationUpdates.cs new file mode 100644 index 0000000..34d52b4 --- /dev/null +++ b/RGBSync+/Configuration/Legacy/ConfigurationUpdates.cs @@ -0,0 +1,12 @@ +namespace RGBSyncPlus.Configuration.Legacy +{ + public static class ConfigurationUpdates + { + #region Methods + + public static void PerformOn(Settings settings) + { } + + #endregion + } +} diff --git a/RGBSync+/Configuration/Settings.cs b/RGBSync+/Configuration/Settings.cs new file mode 100644 index 0000000..c9f4981 --- /dev/null +++ b/RGBSync+/Configuration/Settings.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using RGBSyncPlus.Model; + +namespace RGBSyncPlus.Configuration +{ + public class Settings + { + #region Constants + + public const int CURRENT_VERSION = 1; + + #endregion + + #region Properties & Fields + + public int Version { get; set; } = 0; + + public double UpdateRate { get; set; } = 30.0; + + public List SyncGroups { get; set; } = new List(); + + #endregion + } +} diff --git a/RGBSync+/Controls/BlurredDecorationWindow.cs b/RGBSync+/Controls/BlurredDecorationWindow.cs new file mode 100644 index 0000000..ec0efc8 --- /dev/null +++ b/RGBSync+/Controls/BlurredDecorationWindow.cs @@ -0,0 +1,87 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace RGBSyncPlus.Controls +{ + [TemplatePart(Name = "PART_Decoration", Type = typeof(FrameworkElement))] + [TemplatePart(Name = "PART_Content", Type = typeof(FrameworkElement))] + [TemplatePart(Name = "PART_CloseButton", Type = typeof(Button))] + [TemplatePart(Name = "PART_MinimizeButton", Type = typeof(Button))] + [TemplatePart(Name = "PART_IconButton", Type = typeof(Button))] + public class BlurredDecorationWindow : Window + { + #region DependencyProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register( + "BackgroundImage", typeof(ImageSource), typeof(BlurredDecorationWindow), new PropertyMetadata(default(ImageSource))); + + public ImageSource BackgroundImage + { + get => (ImageSource)GetValue(BackgroundImageProperty); + set => SetValue(BackgroundImageProperty, value); + } + + public static readonly DependencyProperty DecorationHeightProperty = DependencyProperty.Register( + "DecorationHeight", typeof(double), typeof(BlurredDecorationWindow), new PropertyMetadata(20.0)); + + public double DecorationHeight + { + get => (double)GetValue(DecorationHeightProperty); + set => SetValue(DecorationHeightProperty, value); + } + + public static readonly DependencyProperty IconToolTipProperty = DependencyProperty.Register( + "IconToolTip", typeof(string), typeof(BlurredDecorationWindow), new PropertyMetadata(default(string))); + + public string IconToolTip + { + get => (string)GetValue(IconToolTipProperty); + set => SetValue(IconToolTipProperty, value); + } + + public static readonly DependencyProperty IconCommandProperty = DependencyProperty.Register( + "IconCommand", typeof(ICommand), typeof(BlurredDecorationWindow), new PropertyMetadata(default(ICommand))); + + public ICommand IconCommand + { + get => (ICommand)GetValue(IconCommandProperty); + set => SetValue(IconCommandProperty, value); + } + + // ReSharper restore InconsistentNaming + #endregion + + #region Constructors + + static BlurredDecorationWindow() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(BlurredDecorationWindow), new FrameworkPropertyMetadata(typeof(BlurredDecorationWindow))); + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (GetTemplateChild("PART_Decoration") is FrameworkElement decoration) + decoration.MouseLeftButtonDown += (sender, args) => DragMove(); + + if (GetTemplateChild("PART_CloseButton") is Button closeButton) + closeButton.Click += (sender, args) => ApplicationManager.Instance.ExitCommand.Execute(null); + + if (GetTemplateChild("PART_MinimizeButton") is Button minimizeButton) + minimizeButton.Click += (sender, args) => Hide(); + + if (GetTemplateChild("PART_IconButton") is Button iconButton) + iconButton.Click += (sender, args) => IconCommand?.Execute(null); + } + + #endregion + } +} diff --git a/RGBSync+/Controls/ColorSelector.cs b/RGBSync+/Controls/ColorSelector.cs new file mode 100644 index 0000000..1b7a635 --- /dev/null +++ b/RGBSync+/Controls/ColorSelector.cs @@ -0,0 +1,483 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using RGB.NET.Core; +using Color = RGB.NET.Core.Color; +using Point = System.Windows.Point; +using Rectangle = System.Windows.Shapes.Rectangle; +using WpfColor = System.Windows.Media.Color; + +namespace RGBSyncPlus.Controls +{ + [TemplatePart(Name = "PART_Selector", Type = typeof(Panel))] + [TemplatePart(Name = "PART_SliderAlpha", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderRed", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderGreen", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderBlue", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderHue", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderSaturation", Type = typeof(Slider))] + [TemplatePart(Name = "PART_SliderValue", Type = typeof(Slider))] + [TemplatePart(Name = "PART_Preview", Type = typeof(Rectangle))] + public class ColorSelector : Control + { + #region Properties & Fields + + private bool _ignorePropertyChanged; + private bool _dragSelector; + + private byte _a; + private byte _r; + private byte _g; + private byte _b; + private double _hue; + private double _saturation; + private double _value; + + private Panel _selector; + private Rectangle _selectorColor; + private Grid _selectorGrip; + private Slider _sliderAlpha; + private Slider _sliderRed; + private Slider _sliderGreen; + private Slider _sliderBlue; + private Slider _sliderHue; + private Slider _sliderSaturation; + private Slider _sliderValue; + private Rectangle _preview; + + private SolidColorBrush _previewBrush; + private SolidColorBrush _selectorBrush; + private LinearGradientBrush _alphaBrush; + private LinearGradientBrush _redBrush; + private LinearGradientBrush _greenBrush; + private LinearGradientBrush _blueBrush; + private LinearGradientBrush _hueBrush; + private LinearGradientBrush _saturationBrush; + private LinearGradientBrush _valueBrush; + + #endregion + + #region DependencyProperties + + public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register( + "SelectedColor", typeof(Color), typeof(ColorSelector), new FrameworkPropertyMetadata(new Color(255, 0, 0), + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + SelectedColorChanged)); + + public Color SelectedColor + { + get => (Color)GetValue(SelectedColorProperty); + set => SetValue(SelectedColorProperty, value); + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + if ((_selector = GetTemplateChild("PART_Selector") as Panel) != null) + { + _selectorBrush = new SolidColorBrush(); + _selectorColor = new Rectangle + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + SnapsToDevicePixels = true, + StrokeThickness = 0, + Fill = _selectorBrush + }; + _selector.Children.Add(_selectorColor); + + Rectangle selectorWhite = new Rectangle + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + SnapsToDevicePixels = true, + StrokeThickness = 0, + Fill = new LinearGradientBrush(WpfColor.FromRgb(255, 255, 255), WpfColor.FromArgb(0, 255, 255, 255), new Point(0, 0.5), new Point(1, 0.5)) + }; + _selector.Children.Add(selectorWhite); + + Rectangle selectorBlack = new Rectangle + { + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Stretch, + SnapsToDevicePixels = true, + StrokeThickness = 0, + Fill = new LinearGradientBrush(WpfColor.FromRgb(0, 0, 0), WpfColor.FromArgb(0, 0, 0, 0), new Point(0.5, 1), new Point(0.5, 0)) + }; + _selector.Children.Add(selectorBlack); + + _selectorGrip = new Grid + { + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + SnapsToDevicePixels = true + }; + _selectorGrip.Children.Add(new Ellipse + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + SnapsToDevicePixels = true, + Stroke = new SolidColorBrush(WpfColor.FromRgb(0, 0, 0)), + StrokeThickness = 2, + Fill = null, + Width = 12, + Height = 12 + }); + _selectorGrip.Children.Add(new Ellipse + { + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center, + SnapsToDevicePixels = true, + Stroke = new SolidColorBrush(WpfColor.FromRgb(255, 255, 255)), + StrokeThickness = 1, + Fill = null, + Width = 10, + Height = 10 + }); + _selector.Children.Add(_selectorGrip); + + _selector.SizeChanged += (sender, args) => UpdateSelector(); + _selector.MouseLeftButtonDown += (sender, args) => + { + _dragSelector = true; + UpdateSelectorValue(args.GetPosition(_selector)); + }; + _selector.MouseEnter += (sender, args) => + { + if (args.LeftButton == MouseButtonState.Pressed) + { + _dragSelector = true; + UpdateSelectorValue(args.GetPosition(_selector)); + } + }; + _selector.MouseLeftButtonUp += (sender, args) => _dragSelector = false; + _selector.MouseLeave += (sender, args) => _dragSelector = false; + _selector.MouseMove += (sender, args) => UpdateSelectorValue(args.GetPosition(_selector)); + _selector.ClipToBounds = true; + } + + if ((_sliderAlpha = GetTemplateChild("PART_SliderAlpha") as Slider) != null) + { + _alphaBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderAlpha.Background = _alphaBrush; + _sliderAlpha.ValueChanged += AChanged; + } + + if ((_sliderRed = GetTemplateChild("PART_SliderRed") as Slider) != null) + { + _redBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderRed.Background = _redBrush; + _sliderRed.ValueChanged += RChanged; + } + + if ((_sliderGreen = GetTemplateChild("PART_SliderGreen") as Slider) != null) + { + _greenBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderGreen.Background = _greenBrush; + _sliderGreen.ValueChanged += GChanged; + } + + if ((_sliderBlue = GetTemplateChild("PART_SliderBlue") as Slider) != null) + { + _blueBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderBlue.Background = _blueBrush; + _sliderBlue.ValueChanged += BChanged; + } + + if ((_sliderHue = GetTemplateChild("PART_SliderHue") as Slider) != null) + { + _hueBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1.0 / 6.0), + new GradientStop(new WpfColor(), 2.0 / 6.0), + new GradientStop(new WpfColor(), 3.0 / 6.0), + new GradientStop(new WpfColor(), 4.0 / 6.0), + new GradientStop(new WpfColor(), 5.0 / 6.0), + new GradientStop(new WpfColor(), 1) })); + _sliderHue.Background = _hueBrush; + _sliderHue.ValueChanged += HueChanged; + } + + if ((_sliderSaturation = GetTemplateChild("PART_SliderSaturation") as Slider) != null) + { + _saturationBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderSaturation.Background = _saturationBrush; + _sliderSaturation.ValueChanged += SaturationChanged; + } + + if ((_sliderValue = GetTemplateChild("PART_SliderValue") as Slider) != null) + { + _valueBrush = new LinearGradientBrush(new GradientStopCollection(new[] { new GradientStop(new WpfColor(), 0), + new GradientStop(new WpfColor(), 1) })); + _sliderValue.Background = _valueBrush; + _sliderValue.ValueChanged += ValueChanged; + } + + if ((_preview = GetTemplateChild("PART_Preview") as Rectangle) != null) + { + _previewBrush = new SolidColorBrush(); + _preview.Fill = _previewBrush; + } + + SetColor(SelectedColor); + } + + private static void SelectedColorChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + if (!(dependencyObject is ColorSelector cs) || !(dependencyPropertyChangedEventArgs.NewValue is Color color)) return; + cs.SetColor(color); + } + + private void SetColor(Color color) + { + if (_ignorePropertyChanged) return; + + SetA(color); + SetRGB(color); + SetHSV(color); + + UpdateSelector(); + UpdateUIColors(); + } + + private void AChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _a = (byte)routedPropertyChangedEventArgs.NewValue.Clamp(0, byte.MaxValue); + Color color = new Color(_a, _r, _g, _b); + UpdateSelectedColor(color); + UpdateUIColors(); + UpdateSelector(); + } + + private void RChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _r = (byte)routedPropertyChangedEventArgs.NewValue.Clamp(0, byte.MaxValue); + RGBChanged(); + } + + private void GChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _g = (byte)routedPropertyChangedEventArgs.NewValue.Clamp(0, byte.MaxValue); + RGBChanged(); + } + + private void BChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _b = (byte)routedPropertyChangedEventArgs.NewValue.Clamp(0, byte.MaxValue); + RGBChanged(); + } + + private void RGBChanged() + { + Color color = new Color(_a, _r, _g, _b); + UpdateSelectedColor(color); + SetHSV(color); + UpdateUIColors(); + UpdateSelector(); + } + + private void HueChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _hue = routedPropertyChangedEventArgs.NewValue.Clamp(0, 360); + HSVChanged(); + } + + private void SaturationChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _saturation = routedPropertyChangedEventArgs.NewValue.Clamp(0, 1); + HSVChanged(); + } + + private void ValueChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + if (_ignorePropertyChanged) return; + + _value = routedPropertyChangedEventArgs.NewValue.Clamp(0, 1); + HSVChanged(); + } + + private void HSVChanged() + { + Color color = Color.FromHSV(_a, _hue, _saturation, _value); + UpdateSelectedColor(color); + SetRGB(color); + UpdateUIColors(); + UpdateSelector(); + } + + private void SetA(Color color) + { + _ignorePropertyChanged = true; + + _a = color.A; + if (_sliderAlpha != null) + _sliderAlpha.Value = _a; + + _ignorePropertyChanged = false; + } + + private void SetRGB(Color color) + { + _ignorePropertyChanged = true; + + _r = color.R; + if (_sliderRed != null) + _sliderRed.Value = _r; + + _g = color.G; + if (_sliderGreen != null) + _sliderGreen.Value = _g; + + _b = color.B; + if (_sliderBlue != null) + _sliderBlue.Value = _b; + + _ignorePropertyChanged = false; + } + + private void SetHSV(Color color) + { + _ignorePropertyChanged = true; + + _hue = color.Hue; + if (_sliderHue != null) + _sliderHue.Value = _hue; + + _saturation = color.Saturation; + if (_sliderSaturation != null) + _sliderSaturation.Value = _saturation; + + _value = color.Value; + if (_sliderValue != null) + _sliderValue.Value = _value; + + _ignorePropertyChanged = false; + } + + private void UpdateSelectedColor(Color color) + { + _ignorePropertyChanged = true; + + SelectedColor = color; + + _ignorePropertyChanged = false; + } + + private void UpdateSelector() + { + if (_selector == null) return; + + double selectorX = (_selector.ActualWidth * _saturation) - (_selectorGrip.ActualWidth / 2); + double selectorY = (_selector.ActualHeight * _value) - (_selectorGrip.ActualHeight / 2); + if (!double.IsNaN(selectorX) && !double.IsNaN(selectorY)) + _selectorGrip.Margin = new Thickness(selectorX, 0, 0, selectorY); + } + + private void UpdateSelectorValue(Point mouseLocation) + { + if (!_dragSelector) return; + + double saturation = mouseLocation.X / _selector.ActualWidth; + double value = 1 - (mouseLocation.Y / _selector.ActualHeight); + if (!double.IsNaN(saturation) && !double.IsNaN(value)) + { + _saturation = saturation; + _value = value; + HSVChanged(); + } + } + + private void UpdateUIColors() + { + Color hueColor = Color.FromHSV(_hue, 1, 1); + + if (_previewBrush != null) + _previewBrush.Color = WpfColor.FromArgb(_a, _r, _g, _b); + + if (_selectorBrush != null) + _selectorBrush.Color = WpfColor.FromRgb(hueColor.R, hueColor.G, hueColor.B); + + if (_alphaBrush != null) + { + _alphaBrush.GradientStops[0].Color = WpfColor.FromArgb(0, _r, _g, _b); + _alphaBrush.GradientStops[1].Color = WpfColor.FromArgb(255, _r, _g, _b); + } + + if (_redBrush != null) + { + _redBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, 0, _g, _b); + _redBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, 255, _g, _b); + } + + if (_greenBrush != null) + { + _greenBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, _r, 0, _b); + _greenBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, _r, 255, _b); + } + + if (_blueBrush != null) + { + _blueBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, _r, _g, 0); + _blueBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, _r, _g, 255); + } + + if (_hueBrush != null) + { + Color referenceColor1 = Color.FromHSV(0, _saturation, _value); + Color referenceColor2 = Color.FromHSV(60, _saturation, _value); + Color referenceColor3 = Color.FromHSV(120, _saturation, _value); + Color referenceColor4 = Color.FromHSV(180, _saturation, _value); + Color referenceColor5 = Color.FromHSV(240, _saturation, _value); + Color referenceColor6 = Color.FromHSV(300, _saturation, _value); + + _hueBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, referenceColor1.R, referenceColor1.G, referenceColor1.B); + _hueBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, referenceColor2.R, referenceColor2.G, referenceColor2.B); + _hueBrush.GradientStops[2].Color = WpfColor.FromArgb(_a, referenceColor3.R, referenceColor3.G, referenceColor3.B); + _hueBrush.GradientStops[3].Color = WpfColor.FromArgb(_a, referenceColor4.R, referenceColor4.G, referenceColor4.B); + _hueBrush.GradientStops[4].Color = WpfColor.FromArgb(_a, referenceColor5.R, referenceColor5.G, referenceColor5.B); + _hueBrush.GradientStops[5].Color = WpfColor.FromArgb(_a, referenceColor6.R, referenceColor6.G, referenceColor6.B); + _hueBrush.GradientStops[6].Color = WpfColor.FromArgb(_a, referenceColor1.R, referenceColor1.G, referenceColor1.B); + } + + if (_saturationBrush != null) + { + Color referenceColor = Color.FromHSV(_hue, 1, _value); + + _saturationBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, 255, 255, 255); + _saturationBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, referenceColor.R, referenceColor.G, referenceColor.B); + } + + if (_valueBrush != null) + { + Color referenceColor = Color.FromHSV(_hue, _saturation, 1); + + _valueBrush.GradientStops[0].Color = WpfColor.FromArgb(_a, 0, 0, 0); + _valueBrush.GradientStops[1].Color = WpfColor.FromArgb(_a, referenceColor.R, referenceColor.G, referenceColor.B); + } + } + + #endregion + } +} diff --git a/RGBSync+/Controls/Form.cs b/RGBSync+/Controls/Form.cs new file mode 100644 index 0000000..2d8e939 --- /dev/null +++ b/RGBSync+/Controls/Form.cs @@ -0,0 +1,212 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace RGBSyncPlus.Controls +{ + public class Form : Panel + { + #region DependencyProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty RowHeightProperty = DependencyProperty.Register("RowHeight", typeof(double), typeof(Form), + new FrameworkPropertyMetadata(24.0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double RowHeight + { + get => (double)GetValue(RowHeightProperty); + set + { + if (value < 0) throw new ArgumentOutOfRangeException(nameof(RowHeight), "Row height can't be negative"); + SetValue(RowHeightProperty, value); + } + } + + public static readonly DependencyProperty LabelWidthProperty = DependencyProperty.Register("LabelWidth", typeof(double), typeof(Form), + new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double LabelWidth + { + get => (double)GetValue(LabelWidthProperty); + set + { + if (value < 0) throw new ArgumentOutOfRangeException(nameof(RowHeight), "Label width can't be negative"); + SetValue(LabelWidthProperty, value); + } + } + + public static readonly DependencyProperty ElementSpacingProperty = DependencyProperty.Register("ElementSpacing", typeof(double), typeof(Form), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double ElementSpacing + { + get => (double)GetValue(ElementSpacingProperty); + set => SetValue(ElementSpacingProperty, value); + } + + public static readonly DependencyProperty RowSpacingProperty = DependencyProperty.Register("RowSpacing", typeof(double), typeof(Form), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double RowSpacing + { + get => (double)GetValue(RowSpacingProperty); + set => SetValue(RowSpacingProperty, value); + } + + // ReSharper restore InconsistentNaming + #endregion + + #region AttachedProperties + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty IsLabelProperty = DependencyProperty.RegisterAttached("IsLabel", typeof(bool), typeof(Form), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetIsLabel(UIElement element, bool value) => element.SetValue(IsLabelProperty, value); + public static bool GetIsLabel(UIElement element) => (bool)element.GetValue(IsLabelProperty); + + public static readonly DependencyProperty LineBreaksProperty = DependencyProperty.RegisterAttached("LineBreaks", typeof(int), typeof(Form), + new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetLineBreaks(UIElement element, int value) => element.SetValue(LineBreaksProperty, value); + public static int GetLineBreaks(UIElement element) => (int)element.GetValue(LineBreaksProperty); + + public static readonly DependencyProperty RowSpanProperty = DependencyProperty.RegisterAttached("RowSpan", typeof(int), typeof(Form), + new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetRowSpan(DependencyObject element, int value) => element.SetValue(RowSpanProperty, value); + public static int GetRowSpan(DependencyObject element) => (int)element.GetValue(RowSpanProperty); + + public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached("Fill", typeof(bool), typeof(Form), + new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public static void SetFill(DependencyObject element, bool value) => element.SetValue(FillProperty, value); + public static bool GetFill(DependencyObject element) => (bool)element.GetValue(FillProperty); + + // ReSharper restore InconsistentNaming + #endregion + + #region Methods + + protected override Size MeasureOverride(Size availableSize) + { + if (InternalChildren.Count == 0) return new Size(0, 0); + + FormLayout layout = new FormLayout(RowHeight, LabelWidth, ElementSpacing, RowSpacing); + + foreach (UIElement child in InternalChildren) + { + child.Measure(availableSize); + layout.AddElement(child, 0); + } + + return new Size(layout.Width, layout.Height); + } + + protected override Size ArrangeOverride(Size finalSize) + { + if (InternalChildren.Count == 0) return new Size(0, 0); + + FormLayout layout = new FormLayout(RowHeight, LabelWidth, ElementSpacing, RowSpacing); + + foreach (UIElement child in InternalChildren) + child.Arrange(layout.AddElement(child, finalSize.Width)); + + return new Size(finalSize.Width, layout.Height); + } + + #endregion + + #region Data + + private class FormLayout + { + #region Properties & Fields + + private readonly double _rowHeight; + private readonly double _labelWidth; + private readonly double _elementSpacing; + private readonly double _rowSpacing; + + private double _currentRowWidth; + + private int _newRows = 0; + private int _rows = -1; + private double _currentMaxWidth; + public double Width => Math.Max((Math.Max(_currentMaxWidth, _currentRowWidth) - _elementSpacing), 0); + public double Height => ((_rows + 1) * _rowHeight) + (_rows * _rowSpacing); + + #endregion + + #region Constructors + + public FormLayout(double rowHeight, double labelWidth, double elementSpacing, double rowSpacing) + { + this._rowHeight = rowHeight; + this._labelWidth = labelWidth; + this._elementSpacing = elementSpacing; + this._rowSpacing = rowSpacing; + } + + #endregion + + #region Methods + + public Rect AddElement(UIElement element, double targetWidth) + { + bool isLabel = GetIsLabel(element); + int lineBreaks = GetLineBreaks(element); + int rowSpan = GetRowSpan(element); + + double elementWidth = isLabel ? _labelWidth : element.DesiredSize.Width; + double height = _rowHeight; + + if (_newRows > 0) + { + AddLineBreaks(_newRows); + _newRows = 0; + } + + if (lineBreaks > 0) AddLineBreaks(lineBreaks); + else if (isLabel) AddLineBreaks(1); + else if (_rows < 0) _rows = 0; + + if (!isLabel && (_currentRowWidth < _labelWidth)) + _currentRowWidth = _labelWidth + _elementSpacing; + + if (rowSpan > 1) + { + height = (rowSpan * _rowHeight) + ((rowSpan - 1) * _rowSpacing); + _newRows = Math.Max(_newRows, rowSpan - 1); + } + + if (element is FrameworkElement fe) + fe.MaxHeight = height; + + double width = elementWidth; + if ((targetWidth >= 1) && GetFill(element)) + width = targetWidth - _currentRowWidth; + + Rect rect = new Rect(new Point(_currentRowWidth, (_rows * _rowHeight) + (_rows * _rowSpacing)), new Size(width, height)); + + _currentRowWidth += width + _elementSpacing; + + return rect; + } + + private void AddLineBreaks(int count) + { + if (count <= 0) return; + + _currentMaxWidth = Math.Max(_currentMaxWidth, _currentRowWidth); + + _currentRowWidth = 0; + _rows += count; + } + + #endregion + } + + #endregion + } +} diff --git a/RGBSync+/Controls/GradientEditor.cs b/RGBSync+/Controls/GradientEditor.cs new file mode 100644 index 0000000..854250d --- /dev/null +++ b/RGBSync+/Controls/GradientEditor.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using RGB.NET.Brushes.Gradients; +using RGB.NET.Core; +using Color = System.Windows.Media.Color; +using Point = System.Windows.Point; +using Size = System.Windows.Size; +using Rectangle = System.Windows.Shapes.Rectangle; +using GradientStop = RGB.NET.Brushes.Gradients.GradientStop; + +namespace RGBSyncPlus.Controls +{ + [TemplatePart(Name = "PART_Gradient", Type = typeof(Canvas))] + [TemplatePart(Name = "PART_Stops", Type = typeof(Canvas))] + public class GradientEditor : Control + { + #region Properties & Fields + + private Canvas _gradientContainer; + private Canvas _stopContainer; + private readonly List _previewRectangles = new List(); + private readonly Dictionary _stops = new Dictionary(); + private ContentControl _draggingStop; + private AdornerLayer _adornerLayer; + private ColorPickerAdorner _adorner; + private Window _window; + + #endregion + + #region DepdencyProperties + + public static readonly DependencyProperty GradientProperty = DependencyProperty.Register( + "Gradient", typeof(LinearGradient), typeof(GradientEditor), new FrameworkPropertyMetadata(null, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnGradientChanged)); + + public LinearGradient Gradient + { + get => (LinearGradient)GetValue(GradientProperty); + set => SetValue(GradientProperty, value); + } + + public static readonly DependencyProperty GradientStopStyleProperty = DependencyProperty.Register( + "GradientStopStyle", typeof(Style), typeof(GradientEditor), new PropertyMetadata(default(Style))); + + public Style GradientStopStyle + { + get => (Style)GetValue(GradientStopStyleProperty); + set => SetValue(GradientStopStyleProperty, value); + } + + public static readonly DependencyProperty SelectedStopProperty = DependencyProperty.Register( + "SelectedStop", typeof(GradientStop), typeof(GradientEditor), new PropertyMetadata(default(GradientStop), SelectedStopChanged)); + + public GradientStop SelectedStop + { + get => (GradientStop)GetValue(SelectedStopProperty); + set => SetValue(SelectedStopProperty, value); + } + + public static readonly DependencyProperty ColorSelectorTemplateProperty = DependencyProperty.Register( + "ColorSelectorTemplate", typeof(DataTemplate), typeof(GradientEditor), new PropertyMetadata(default(DataTemplate))); + + public DataTemplate ColorSelectorTemplate + { + get => (DataTemplate)GetValue(ColorSelectorTemplateProperty); + set => SetValue(ColorSelectorTemplateProperty, value); + } + + public static readonly DependencyProperty CanAddOrDeleteStopsProperty = DependencyProperty.Register( + "CanAddOrDeleteStops", typeof(bool), typeof(GradientEditor), new PropertyMetadata(true)); + + public bool CanAddOrDeleteStops + { + get => (bool)GetValue(CanAddOrDeleteStopsProperty); + set => SetValue(CanAddOrDeleteStopsProperty, value); + } + + #endregion + + #region AttachedProperties + + public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached( + "IsSelected", typeof(bool), typeof(GradientEditor), new PropertyMetadata(default(bool))); + + public static void SetIsSelected(DependencyObject element, bool value) => element.SetValue(IsSelectedProperty, value); + public static bool GetIsSelected(DependencyObject element) => (bool)element.GetValue(IsSelectedProperty); + + #endregion + + #region Constructors + + public GradientEditor() + { + if (Gradient == null) + Gradient = new LinearGradient(); + } + + #endregion + + #region Methods + + public override void OnApplyTemplate() + { + if ((_gradientContainer = GetTemplateChild("PART_Gradient") as Canvas) != null) + { + _gradientContainer.SizeChanged += (sender, args) => UpdateGradientPreview(); + _gradientContainer.MouseDown += GradientContainerOnMouseDown; + } + + if ((_stopContainer = GetTemplateChild("PART_Stops") as Canvas) != null) + _stopContainer.SizeChanged += (sender, args) => UpdateGradientStops(); + + _adornerLayer = AdornerLayer.GetAdornerLayer(this); + _window = Window.GetWindow(this); + if (_window != null) + { + _window.PreviewMouseDown += WindowMouseDown; + _window.PreviewKeyDown += (sender, args) => + { + if (args.Key == Key.Escape) + SelectedStop = null; + }; + } + + UpdateGradientPreview(); + UpdateGradientStops(); + } + + private void UpdateGradientPreview() + { + if ((_gradientContainer == null) || (Gradient == null)) return; + + List gradientStops = Gradient.GradientStops.OrderBy(x => x.Offset).ToList(); + if (gradientStops.Count == 0) + UpdatePreviewRectangleCount(gradientStops.Count); + else if (gradientStops.Count == 1) + { + UpdatePreviewRectangleCount(gradientStops.Count); + GradientStop firstStop = gradientStops[0]; + UpdatePreviewRectangle(_previewRectangles[0], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, 0, 1, firstStop.Color, firstStop.Color); + } + else + { + UpdatePreviewRectangleCount(gradientStops.Count + 1); + + GradientStop firstStop = gradientStops[0]; + UpdatePreviewRectangle(_previewRectangles[0], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, 0, firstStop.Offset, firstStop.Color, firstStop.Color); + for (int i = 0; i < (gradientStops.Count - 1); i++) + { + GradientStop stop = gradientStops[i]; + GradientStop nextStop = gradientStops[i + 1]; + Rectangle rect = _previewRectangles[i + 1]; + UpdatePreviewRectangle(rect, _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, stop.Offset, nextStop.Offset, stop.Color, nextStop.Color); + } + GradientStop lastStop = gradientStops[gradientStops.Count - 1]; + UpdatePreviewRectangle(_previewRectangles[_previewRectangles.Count - 1], _gradientContainer.ActualWidth, _gradientContainer.ActualHeight, lastStop.Offset, 1, lastStop.Color, lastStop.Color); + } + } + + private void UpdatePreviewRectangle(Rectangle rect, double referenceWidth, double referenceHeight, double from, double to, + RGB.NET.Core.Color startColor, RGB.NET.Core.Color endColor) + { + rect.Fill = new LinearGradientBrush(Color.FromArgb(startColor.A, startColor.R, startColor.G, startColor.B), + Color.FromArgb(endColor.A, endColor.R, endColor.G, endColor.B), + new Point(0, 0.5), new Point(1, 0.5)); + + //DarthAffe 09.02.2018: Forced rounding to prevent render issues on resize + Canvas.SetLeft(rect, Math.Floor(referenceWidth * from.Clamp(0, 1))); + rect.Width = Math.Ceiling(referenceWidth * (to.Clamp(0, 1) - from.Clamp(0, 1))); + + Canvas.SetTop(rect, 0); + rect.Height = referenceHeight; + } + + private void UpdatePreviewRectangleCount(int gradientCount) + { + int countDiff = gradientCount - _previewRectangles.Count; + if (countDiff > 0) + for (int i = 0; i < countDiff; i++) + { + Rectangle rect = new Rectangle { VerticalAlignment = VerticalAlignment.Stretch }; + _previewRectangles.Add(rect); + _gradientContainer.Children.Add(rect); + } + + if (countDiff < 0) + for (int i = 0; i < Math.Abs(countDiff); i++) + { + int index = _previewRectangles.Count - i - 1; + Rectangle rect = _previewRectangles[index]; + _previewRectangles.RemoveAt(index); + _gradientContainer.Children.Remove(rect); + } + } + + private void UpdateGradientStops() + { + if (Gradient == null) return; + + List gradientStops = Gradient.GradientStops.OrderBy(x => x.Offset).ToList(); + UpdateGradientStopsCount(gradientStops); + foreach (GradientStop stop in gradientStops) + UpdateGradientStop(_stops[stop], _stopContainer.ActualWidth, _stopContainer.ActualHeight, stop); + } + + private void UpdateGradientStop(ContentControl control, double referenceWidth, double referenceHeight, GradientStop stop) + { + control.Background = new SolidColorBrush(Color.FromArgb(stop.Color.A, stop.Color.R, stop.Color.G, stop.Color.B)); + + Canvas.SetLeft(control, (referenceWidth * stop.Offset.Clamp(0, 1)) - (control.Width / 2.0)); + + Canvas.SetTop(control, 0); + control.Height = referenceHeight; + } + + private void UpdateGradientStopsCount(List gradientStops) + { + foreach (GradientStop stop in gradientStops) + { + if (!_stops.ContainsKey(stop)) + { + ContentControl control = new ContentControl + { + VerticalAlignment = VerticalAlignment.Stretch, + Style = GradientStopStyle, + Content = stop + }; + control.MouseDown += GradientStopOnMouseDown; + _stops.Add(stop, control); + _stopContainer.Children.Add(control); + } + } + + List stopsToRemove = new List(); + foreach (KeyValuePair stopPair in _stops) + if (!gradientStops.Contains(stopPair.Key)) + { + ContentControl control = stopPair.Value; + control.MouseDown -= GradientStopOnMouseDown; + stopsToRemove.Add(stopPair.Key); + _stopContainer.Children.Remove(control); + } + + foreach (GradientStop stop in stopsToRemove) + _stops.Remove(stop); + } + + private void AttachGradient(AbstractGradient gradient) => gradient.GradientChanged += GradientChanged; + private void DetachGradient(AbstractGradient gradient) => gradient.GradientChanged -= GradientChanged; + + private void GradientChanged(object o, EventArgs eventArgs) + { + UpdateGradientPreview(); + UpdateGradientStops(); + } + + private static void OnGradientChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + if (!(dependencyObject is GradientEditor ge)) return; + + if (dependencyPropertyChangedEventArgs.OldValue is AbstractGradient oldGradient) + ge.DetachGradient(oldGradient); + + if (dependencyPropertyChangedEventArgs.NewValue is AbstractGradient newGradient) + ge.AttachGradient(newGradient); + } + + private void GradientContainerOnMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs) + { + if ((mouseButtonEventArgs.ChangedButton != MouseButton.Left) || (Gradient == null) || !CanAddOrDeleteStops) return; + + double offset = mouseButtonEventArgs.GetPosition(_gradientContainer).X / _gradientContainer.ActualWidth; + RGB.NET.Core.Color color = Gradient.GetColor(offset); + GradientStop newStop = new GradientStop(offset, color); + Gradient.GradientStops.Add(newStop); + SelectedStop = newStop; + } + + private void GradientStopOnMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs) + { + if (!((o as ContentControl)?.Content is GradientStop stop) || (Gradient == null)) return; + + if (mouseButtonEventArgs.ChangedButton == MouseButton.Right) + { + if (CanAddOrDeleteStops) + Gradient.GradientStops.Remove(stop); + } + else if (mouseButtonEventArgs.ChangedButton == MouseButton.Left) + { + SelectedStop = stop; + _draggingStop = (ContentControl)o; + } + } + + protected override void OnMouseMove(MouseEventArgs e) + { + base.OnMouseMove(e); + + if (_draggingStop?.Content is GradientStop stop) + { + double location = e.GetPosition(_gradientContainer).X; + stop.Offset = (location / _gradientContainer.ActualWidth).Clamp(0, 1); + } + } + + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + + _draggingStop = null; + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + + _draggingStop = null; + } + + private static void SelectedStopChanged(DependencyObject dependencyObject, + DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + if (!(dependencyObject is GradientEditor gradientEditor)) return; + + if (gradientEditor._adorner != null) + gradientEditor._adornerLayer.Remove(gradientEditor._adorner); + + if (dependencyPropertyChangedEventArgs.OldValue is GradientStop oldStop) + { + if (gradientEditor._stops.TryGetValue(oldStop, out ContentControl oldcontrol)) + SetIsSelected(oldcontrol, false); + } + + if (dependencyPropertyChangedEventArgs.NewValue is GradientStop stop) + { + ContentControl stopContainer = gradientEditor._stops[stop]; + SetIsSelected(stopContainer, true); + + if (gradientEditor._adornerLayer != null) + { + ContentControl contentControl = new ContentControl + { + ContentTemplate = gradientEditor.ColorSelectorTemplate, + Content = stop + }; + + ColorPickerAdorner adorner = new ColorPickerAdorner(stopContainer, contentControl); + gradientEditor._adorner = adorner; + gradientEditor._adornerLayer.Add(adorner); + } + } + } + + private void WindowMouseDown(object o, MouseButtonEventArgs mouseButtonEventArgs) + { + if ((_adorner != null) && (VisualTreeHelper.HitTest(_adorner, mouseButtonEventArgs.GetPosition(_adorner)) == null)) + SelectedStop = null; + } + + #endregion + } + + public class ColorPickerAdorner : Adorner + { + #region Properties & Fields + + private readonly VisualCollection _visualChildren; + private readonly FrameworkElement _colorSelector; + protected override int VisualChildrenCount => 1; + protected override Visual GetVisualChild(int index) => _colorSelector; + + #endregion + + #region Constructors + + public ColorPickerAdorner(UIElement adornedElement, FrameworkElement colorSelector) + : base(adornedElement) + { + this._colorSelector = colorSelector; + + _visualChildren = new VisualCollection(this) { colorSelector }; + } + + #endregion + + #region Methods + + protected override Size ArrangeOverride(Size finalSize) + { + Window referenceWindow = Window.GetWindow(AdornedElement); + Point referenceLocation = AdornedElement.TranslatePoint(new Point(0, 0), referenceWindow); + + double referenceWidth = ((FrameworkElement)AdornedElement).ActualWidth / 2.0; + double referenceHeight = ((FrameworkElement)AdornedElement).Height; + double referenceX = referenceLocation.X + referenceWidth; + double halfWidth = finalSize.Width / 2.0; + double maxOffset = referenceWindow.Width - halfWidth; + + double offset = (referenceX < halfWidth ? referenceX + : (((referenceX + (referenceWidth * 2)) > maxOffset) + ? halfWidth - ((maxOffset - referenceX) - (referenceWidth * 2)) + : halfWidth)); + + _colorSelector.Arrange(new Rect(new Point(referenceWidth - offset, referenceHeight), finalSize)); + return _colorSelector.RenderSize; + } + + protected override Size MeasureOverride(Size constraint) + { + _colorSelector.Measure(constraint); + return _colorSelector.DesiredSize; + } + + #endregion + } +} diff --git a/RGBSync+/Controls/ImageButton.cs b/RGBSync+/Controls/ImageButton.cs new file mode 100644 index 0000000..2bd0f4e --- /dev/null +++ b/RGBSync+/Controls/ImageButton.cs @@ -0,0 +1,51 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace RGBSyncPlus.Controls +{ + public class ImageButton : Button + { + #region Properties & Fields + // ReSharper disable InconsistentNaming + + public static readonly DependencyProperty ImageProperty = DependencyProperty.Register( + "Image", typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(default(ImageSource))); + + public ImageSource Image + { + get => (ImageSource)GetValue(ImageProperty); + set => SetValue(ImageProperty, value); + } + + public static readonly DependencyProperty HoverImageProperty = DependencyProperty.Register( + "HoverImage", typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(default(ImageSource))); + + public ImageSource HoverImage + { + get => (ImageSource)GetValue(HoverImageProperty); + set => SetValue(HoverImageProperty, value); + } + + public static readonly DependencyProperty PressedImageProperty = DependencyProperty.Register( + "PressedImage", typeof(ImageSource), typeof(ImageButton), new PropertyMetadata(default(ImageSource))); + + public ImageSource PressedImage + { + get => (ImageSource)GetValue(PressedImageProperty); + set => SetValue(PressedImageProperty, value); + } + + // ReSharper restore InconsistentNaming + #endregion + + #region Constructors + + static ImageButton() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(ImageButton), new FrameworkPropertyMetadata(typeof(ImageButton))); + } + + #endregion + } +} diff --git a/RGBSync+/Converter/BoolToVisibilityConverter.cs b/RGBSync+/Converter/BoolToVisibilityConverter.cs new file mode 100644 index 0000000..04f4f2b --- /dev/null +++ b/RGBSync+/Converter/BoolToVisibilityConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RGBSyncPlus.Converter +{ + [ValueConversion(typeof(bool), typeof(Visibility))] + public class BoolToVisibilityConverter : IValueConverter + { + #region Methods + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => (value as bool?) == true ? Visibility.Visible + : (string.Equals(parameter?.ToString(), "true", StringComparison.OrdinalIgnoreCase) ? Visibility.Hidden : Visibility.Collapsed); + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value as Visibility? == Visibility.Visible; + + #endregion + } +} diff --git a/RGBSync+/Converter/EqualsToBoolConverter.cs b/RGBSync+/Converter/EqualsToBoolConverter.cs new file mode 100644 index 0000000..32373c9 --- /dev/null +++ b/RGBSync+/Converter/EqualsToBoolConverter.cs @@ -0,0 +1,18 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace RGBSyncPlus.Converter +{ + [ValueConversion(typeof(object), typeof(bool))] + public class EqualsToBoolConverter : IValueConverter + { + #region Methods + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => Equals(value, parameter); + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException(); + + #endregion + } +} diff --git a/RGBSync+/Converter/NullToVisibilityConverter.cs b/RGBSync+/Converter/NullToVisibilityConverter.cs new file mode 100644 index 0000000..6fc33f9 --- /dev/null +++ b/RGBSync+/Converter/NullToVisibilityConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RGBSyncPlus.Converter +{ + [ValueConversion(typeof(object), typeof(Visibility))] + public class NullToVisibilityConverter : IValueConverter + { + #region Methods + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => (value == null) == (string.Equals(parameter?.ToString(), "true", StringComparison.OrdinalIgnoreCase)) ? Visibility.Visible : Visibility.Hidden; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException(); + + #endregion + } +} diff --git a/RGBSync+/Converter/ScrollOffsetToOpacityMaskConverter.cs b/RGBSync+/Converter/ScrollOffsetToOpacityMaskConverter.cs new file mode 100644 index 0000000..cc0252f --- /dev/null +++ b/RGBSync+/Converter/ScrollOffsetToOpacityMaskConverter.cs @@ -0,0 +1,61 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; +using System.Windows.Media; + +namespace RGBSyncPlus.Converter +{ + public class ScrollOffsetToOpacityMaskConverter : IMultiValueConverter + { + #region Constants + + private static readonly Color TRANSPARENT = Color.FromArgb(0, 0, 0, 0); + private static readonly Color OPAQUE = Color.FromArgb(255, 0, 0, 0); + + #endregion + + #region Methods + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + double offset = double.Parse(values[0].ToString()); + double maxHeight = double.Parse(values[1].ToString()); + double height = double.Parse(values[2].ToString()); + + double transparencyHeight = double.Parse(parameter.ToString()); + double transparencyFactor = (transparencyHeight - 6) / height; + double transparencyFadeFactor = (transparencyHeight + 4) / height; + + bool top = !(Math.Abs(offset) < float.Epsilon); + bool bot = !(Math.Abs(offset - maxHeight) < float.Epsilon); + + if (!top && !bot) return new SolidColorBrush(OPAQUE); + + GradientStopCollection gradientStops = new GradientStopCollection(); + if (top) + { + gradientStops.Add(new GradientStop(TRANSPARENT, 0.0)); + gradientStops.Add(new GradientStop(TRANSPARENT, transparencyFactor)); + gradientStops.Add(new GradientStop(OPAQUE, transparencyFadeFactor)); + } + else + gradientStops.Add(new GradientStop(OPAQUE, 0.0)); + + if (bot) + { + gradientStops.Add(new GradientStop(OPAQUE, 1.0 - transparencyFadeFactor)); + gradientStops.Add(new GradientStop(TRANSPARENT, 1.0 - transparencyFactor)); + gradientStops.Add(new GradientStop(TRANSPARENT, 1.0)); + } + else + gradientStops.Add(new GradientStop(OPAQUE, 1.0)); + + return new LinearGradientBrush(gradientStops, new Point(0.5, 0.0), new Point(0.5, 1.0)); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); + + #endregion + } +} diff --git a/RGBSync+/Converter/ScrollOffsetToVisibilityConverter.cs b/RGBSync+/Converter/ScrollOffsetToVisibilityConverter.cs new file mode 100644 index 0000000..0eb286f --- /dev/null +++ b/RGBSync+/Converter/ScrollOffsetToVisibilityConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace RGBSyncPlus.Converter +{ + // Based on: http://stackoverflow.com/a/28679767 + public class ScrollOffsetToVisibilityConverter : IMultiValueConverter + { + #region Methods + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + bool top = "top".Equals(parameter?.ToString(), StringComparison.OrdinalIgnoreCase); + + double offset = double.Parse(values[0].ToString()); + double maxHeight = double.Parse(values[1].ToString()); + + return (top && Math.Abs(offset) < float.Epsilon) || (!top && Math.Abs(offset - maxHeight) < float.Epsilon) + ? Visibility.Collapsed + : Visibility.Visible; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => throw new NotSupportedException(); + + #endregion + } +} diff --git a/RGBSync+/Helper/ActionCommand.cs b/RGBSync+/Helper/ActionCommand.cs new file mode 100644 index 0000000..537357b --- /dev/null +++ b/RGBSync+/Helper/ActionCommand.cs @@ -0,0 +1,77 @@ +using System; +using System.Windows.Input; + +namespace RGBSyncPlus.Helper +{ + public class ActionCommand : ICommand + { + #region Properties & Fields + + private readonly Func _canExecute; + private readonly Action _command; + + #endregion + + #region Events + + public event EventHandler CanExecuteChanged; + + #endregion + + #region Constructors + + public ActionCommand(Action command, Func canExecute = null) + { + this._command = command; + this._canExecute = canExecute; + } + + #endregion + + #region Methods + + public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true; + + public void Execute(object parameter) => _command?.Invoke(); + + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs()); + + #endregion + } + + public class ActionCommand : ICommand + { + #region Properties & Fields + + private readonly Func _canExecute; + private readonly Action _command; + + #endregion + + #region Events + + public event EventHandler CanExecuteChanged; + + #endregion + + #region Constructors + + public ActionCommand(Action command, Func canExecute = null) + { + this._command = command; + this._canExecute = canExecute; + } + + #endregion + + #region Methods + + public bool CanExecute(object parameter) => _canExecute?.Invoke((T)parameter) ?? true; + + public void Execute(object parameter) => _command?.Invoke((T)parameter); + + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs()); + + #endregion + } +} diff --git a/RGBSync+/Helper/ExceptionExtension.cs b/RGBSync+/Helper/ExceptionExtension.cs new file mode 100644 index 0000000..e2d566f --- /dev/null +++ b/RGBSync+/Helper/ExceptionExtension.cs @@ -0,0 +1,23 @@ +using System; + +namespace RGBSyncPlus.Helper +{ + public static class ExceptionExtension + { + #region Methods + + public static string GetFullMessage(this Exception ex, string message = "") + { + if (ex == null) return string.Empty; + + message += ex.Message; + + if (ex.InnerException != null) + message += "\r\nInnerException: " + GetFullMessage(ex.InnerException); + + return message; + } + + #endregion + } +} diff --git a/RGBSync+/Helper/MathHelper.cs b/RGBSync+/Helper/MathHelper.cs new file mode 100644 index 0000000..6ef4415 --- /dev/null +++ b/RGBSync+/Helper/MathHelper.cs @@ -0,0 +1,15 @@ +using System; + +namespace RGBSyncPlus.Helper +{ + public static class MathHelper + { + #region Methods + + public static double Clamp(double value, double min, double max) => Math.Max(min, Math.Min(max, value)); + public static float Clamp(float value, float min, float max) => (float)Clamp((double)value, min, max); + public static int Clamp(int value, int min, int max) => Math.Max(min, Math.Min(max, value)); + + #endregion + } +} diff --git a/RGBSync+/Helper/RGBNetExtension.cs b/RGBSync+/Helper/RGBNetExtension.cs new file mode 100644 index 0000000..1a5ea8c --- /dev/null +++ b/RGBSync+/Helper/RGBNetExtension.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; +using RGBSyncPlus.Model; + +namespace RGBSyncPlus.Helper +{ + public static class RGBNetExtension + { + public static string GetDeviceName(this IRGBDevice device) => $"{device.DeviceInfo.Manufacturer} {device.DeviceInfo.Model} ({device.DeviceInfo.DeviceType})"; + + public static IEnumerable GetLeds(this IEnumerable syncLeds) + => syncLeds.Select(GetLed).Where(led => led != null); + + public static Led GetLed(this SyncLed syncLed) + { + if (syncLed == null) return null; + return RGBSurface.Instance.Leds.FirstOrDefault(l => (l.Id == syncLed.LedId) && (l.Device.GetDeviceName() == syncLed.Device)); + } + } +} diff --git a/RGBSync+/Model/SyncGroup.cs b/RGBSync+/Model/SyncGroup.cs new file mode 100644 index 0000000..827772d --- /dev/null +++ b/RGBSync+/Model/SyncGroup.cs @@ -0,0 +1,48 @@ +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Newtonsoft.Json; +using RGB.NET.Core; +using RGB.NET.Groups; + +namespace RGBSyncPlus.Model +{ + public class SyncGroup : AbstractBindable + { + #region Properties & Fields + + public string DisplayName => string.IsNullOrWhiteSpace(Name) ? "(unnamed)" : Name; + + private string _name; + public string Name + { + get => _name; + set + { + if (SetProperty(ref _name, value)) + OnPropertyChanged(nameof(DisplayName)); + } + } + + private SyncLed _syncLed; + public SyncLed SyncLed + { + get => _syncLed; + set => SetProperty(ref _syncLed, value); + } + + private ObservableCollection _leds = new ObservableCollection(); + public ObservableCollection Leds + { + get => _leds; + set => SetProperty(ref _leds, value); + } + + [JsonIgnore] + public ListLedGroup LedGroup { get; set; } + + [JsonIgnore] + public NotifyCollectionChangedEventHandler LedsChangedEventHandler { get; set; } + + #endregion + } +} diff --git a/RGBSync+/Model/SyncLed.cs b/RGBSync+/Model/SyncLed.cs new file mode 100644 index 0000000..fdd5dee --- /dev/null +++ b/RGBSync+/Model/SyncLed.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using RGB.NET.Core; +using RGBSyncPlus.Helper; + +namespace RGBSyncPlus.Model +{ + public class SyncLed : AbstractBindable + { + #region Properties & Fields + + private string _device; + public string Device + { + get => _device; + set => SetProperty(ref _device, value); + } + + private LedId _ledId; + public LedId LedId + { + get => _ledId; + set => SetProperty(ref _ledId, value); + } + + private Led _led; + [JsonIgnore] + public Led Led + { + get => _led; + set => SetProperty(ref _led, value); + } + + #endregion + + #region Constructors + + public SyncLed() + { } + + public SyncLed(string device, LedId ledId) + { + this.Device = device; + this.LedId = ledId; + } + + public SyncLed(Led led) + { + this.Device = led.Device.GetDeviceName(); + this.LedId = led.Id; + this.Led = led; + } + + #endregion + + #region Methods + + protected bool Equals(SyncLed other) => string.Equals(_device, other._device) && (_ledId == other._ledId); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((SyncLed)obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((_device != null ? _device.GetHashCode() : 0) * 397) ^ (int)_ledId; + } + } + + public static bool operator ==(SyncLed left, SyncLed right) => Equals(left, right); + public static bool operator !=(SyncLed left, SyncLed right) => !Equals(left, right); + + #endregion + } +} diff --git a/RGBSync+/Properties/AssemblyInfo.cs b/RGBSync+/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c1f69b1 --- /dev/null +++ b/RGBSync+/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RGBSync+")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RGBSync+")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/RGBSync+/Properties/Resources.Designer.cs b/RGBSync+/Properties/Resources.Designer.cs new file mode 100644 index 0000000..03689b5 --- /dev/null +++ b/RGBSync+/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RGBSyncPlus.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RGBSyncPlus.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/RGBSync+/Properties/Resources.resx b/RGBSync+/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/RGBSync+/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/RGBSync+/Properties/Settings.Designer.cs b/RGBSync+/Properties/Settings.Designer.cs new file mode 100644 index 0000000..5ca9da0 --- /dev/null +++ b/RGBSync+/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace RGBSyncPlus.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/RGBSync+/Properties/Settings.settings b/RGBSync+/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/RGBSync+/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/RGBSync+/RGBSync+.csproj b/RGBSync+/RGBSync+.csproj new file mode 100644 index 0000000..b1c4716 --- /dev/null +++ b/RGBSync+/RGBSync+.csproj @@ -0,0 +1,236 @@ + + + + + Debug + AnyCPU + {C6BF4357-07D9-496B-9630-A26568D30723} + WinExe + RGBSyncPlus + RGBSync+ + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + x86 + true + full + false + ..\bin\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + ..\bin\ + TRACE + prompt + 4 + + + RGBSyncPlus.App + + + Resources\argebee.ico + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + + + + + + + + + + + + + + + + + ConfigurationWindow.xaml + + + + + + + + + + + App.xaml + Code + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + 1.1.0 + + + 1.0.8 + + + 11.0.2 + + + 0.0.1.54 + + + 0.0.1.54 + + + 0.0.1.54 + + + 0.0.1.54 + + + 4.5.0 + + + 1.0.0 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/RGBSync+.sln b/RGBSync+/RGBSync+.sln new file mode 100644 index 0000000..78c7140 --- /dev/null +++ b/RGBSync+/RGBSync+.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2018 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RGBSync+", "RGBSync+.csproj", "{C6BF4357-07D9-496B-9630-A26568D30723}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C6BF4357-07D9-496B-9630-A26568D30723}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6BF4357-07D9-496B-9630-A26568D30723}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6BF4357-07D9-496B-9630-A26568D30723}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6BF4357-07D9-496B-9630-A26568D30723}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3AD1163D-9F77-43AB-A3BA-38C1B9CCBA87} + EndGlobalSection +EndGlobal diff --git a/RGBSync+/Resources/RGBSync+.xaml b/RGBSync+/Resources/RGBSync+.xaml new file mode 100644 index 0000000..22b2895 --- /dev/null +++ b/RGBSync+/Resources/RGBSync+.xaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/Button.xaml b/RGBSync+/Styles/Button.xaml new file mode 100644 index 0000000..4393b6c --- /dev/null +++ b/RGBSync+/Styles/Button.xaml @@ -0,0 +1,41 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/CachedResourceDictionary.cs b/RGBSync+/Styles/CachedResourceDictionary.cs new file mode 100644 index 0000000..c35a946 --- /dev/null +++ b/RGBSync+/Styles/CachedResourceDictionary.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Windows; + +namespace RGBSyncPlus.Styles +{ + public class CachedResourceDictionary : ResourceDictionary + { + #region Properties & Fields + + // ReSharper disable InconsistentNaming + private static readonly List _cachedDictionaries = new List(); + private static readonly ResourceDictionary _innerDictionary = new ResourceDictionary(); + // ReSharper restore + + public new Uri Source + { + get => null; + set + { + lock (_innerDictionary) + { + UpdateCache(value); + + MergedDictionaries.Clear(); + MergedDictionaries.Add(_innerDictionary); + } + } + } + + #endregion + + #region Methods + + private static void UpdateCache(Uri source) + { + string uriPath = source.OriginalString; + if (_cachedDictionaries.Contains(uriPath)) return; + + _cachedDictionaries.Add(uriPath); + + ResourceDictionary newDictionary = new ResourceDictionary { Source = new Uri(uriPath, source.IsAbsoluteUri ? UriKind.Absolute : UriKind.Relative) }; + CopyDictionaryEntries(newDictionary, _innerDictionary); + } + + private static void CopyDictionaryEntries(IDictionary source, IDictionary target) + { + foreach (object key in source.Keys) + if (!target.Contains(key)) + target.Add(key, source[key]); + } + + #endregion + } +} diff --git a/RGBSync+/Styles/ColorSelector.xaml b/RGBSync+/Styles/ColorSelector.xaml new file mode 100644 index 0000000..1175f00 --- /dev/null +++ b/RGBSync+/Styles/ColorSelector.xaml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/Form.xaml b/RGBSync+/Styles/Form.xaml new file mode 100644 index 0000000..5a47e2e --- /dev/null +++ b/RGBSync+/Styles/Form.xaml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/FrameworkElement.xaml b/RGBSync+/Styles/FrameworkElement.xaml new file mode 100644 index 0000000..107ef2e --- /dev/null +++ b/RGBSync+/Styles/FrameworkElement.xaml @@ -0,0 +1,13 @@ + + + + + + diff --git a/RGBSync+/Styles/GradientEditor.xaml b/RGBSync+/Styles/GradientEditor.xaml new file mode 100644 index 0000000..bc28fb6 --- /dev/null +++ b/RGBSync+/Styles/GradientEditor.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/ImageButton.xaml b/RGBSync+/Styles/ImageButton.xaml new file mode 100644 index 0000000..68edb06 --- /dev/null +++ b/RGBSync+/Styles/ImageButton.xaml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/ListBox.xaml b/RGBSync+/Styles/ListBox.xaml new file mode 100644 index 0000000..16d8bef --- /dev/null +++ b/RGBSync+/Styles/ListBox.xaml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/Navigation.xaml b/RGBSync+/Styles/Navigation.xaml new file mode 100644 index 0000000..94339c0 --- /dev/null +++ b/RGBSync+/Styles/Navigation.xaml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/TextBox.xaml b/RGBSync+/Styles/TextBox.xaml new file mode 100644 index 0000000..02b7861 --- /dev/null +++ b/RGBSync+/Styles/TextBox.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/Styles/Theme.xaml b/RGBSync+/Styles/Theme.xaml new file mode 100644 index 0000000..520c7f9 --- /dev/null +++ b/RGBSync+/Styles/Theme.xaml @@ -0,0 +1,75 @@ + + + #FFDCDCDC + #FF2A2A2A + #B82A2A2A + #111111 + #B8111111 + #60111111 + #50000000 + #FFE135 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #60111111 + #B8111111 + + + + + + + + + + + + + + + 40 + + + + 14 + 14 + 22 + diff --git a/RGBSync+/Styles/ToolTip.xaml b/RGBSync+/Styles/ToolTip.xaml new file mode 100644 index 0000000..2b823da --- /dev/null +++ b/RGBSync+/Styles/ToolTip.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/RGBSync+/UI/ConfigurationViewModel.cs b/RGBSync+/UI/ConfigurationViewModel.cs new file mode 100644 index 0000000..3109938 --- /dev/null +++ b/RGBSync+/UI/ConfigurationViewModel.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Windows; +using System.Windows.Data; +using GongSolutions.Wpf.DragDrop; +using RGB.NET.Core; +using RGBSyncPlus.Helper; +using RGBSyncPlus.Model; + +namespace RGBSyncPlus.UI +{ + public sealed class ConfigurationViewModel : AbstractBindable, IDropTarget + { + #region Properties & Fields + + public Version Version => Assembly.GetEntryAssembly().GetName().Version; + + public double UpdateRate + { + get => 1.0 / ApplicationManager.Instance.UpdateTrigger.UpdateFrequency; + set + { + double val = MathHelper.Clamp(value, 1, 100); + ApplicationManager.Instance.Settings.UpdateRate = val; + ApplicationManager.Instance.UpdateTrigger.UpdateFrequency = 1.0 / val; + OnPropertyChanged(); + } + } + + private ObservableCollection _syncGroups; + public ObservableCollection SyncGroups + { + get => _syncGroups; + set => SetProperty(ref _syncGroups, value); + } + + private SyncGroup _selectedSyncGroup; + public SyncGroup SelectedSyncGroup + { + get => _selectedSyncGroup; + set + { + if (SetProperty(ref _selectedSyncGroup, value)) + UpdateLedLists(); + } + } + + private ListCollectionView _availableSyncLeds; + public ListCollectionView AvailableSyncLeds + { + get => _availableSyncLeds; + set => SetProperty(ref _availableSyncLeds, value); + } + + private ListCollectionView _availableLeds; + public ListCollectionView AvailableLeds + { + get => _availableLeds; + set => SetProperty(ref _availableLeds, value); + } + + private ListCollectionView _synchronizedLeds; + public ListCollectionView SynchronizedLeds + { + get => _synchronizedLeds; + set => SetProperty(ref _synchronizedLeds, value); + } + + #endregion + + #region Commands + + private ActionCommand _openHomepageCommand; + public ActionCommand OpenHomepageCommand => _openHomepageCommand ?? (_openHomepageCommand = new ActionCommand(OpenHomepage)); + + private ActionCommand _addSyncGroupCommand; + public ActionCommand AddSyncGroupCommand => _addSyncGroupCommand ?? (_addSyncGroupCommand = new ActionCommand(AddSyncGroup)); + + private ActionCommand _removeSyncGroupCommand; + public ActionCommand RemoveSyncGroupCommand => _removeSyncGroupCommand ?? (_removeSyncGroupCommand = new ActionCommand(RemoveSyncGroup)); + + #endregion + + #region Constructors + + public ConfigurationViewModel() + { + SyncGroups = new ObservableCollection(ApplicationManager.Instance.Settings.SyncGroups); + + AvailableSyncLeds = GetGroupedLedList(RGBSurface.Instance.Leds.Where(x => x.Device.DeviceInfo.SupportsSyncBack)); + OnPropertyChanged(nameof(AvailableSyncLeds)); + } + + #endregion + + #region Methods + + private ListCollectionView GetGroupedLedList(IEnumerable leds) => GetGroupedLedList(leds.Select(led => new SyncLed(led)).ToList()); + + private ListCollectionView GetGroupedLedList(IList syncLeds) + { + ListCollectionView collectionView = new ListCollectionView(syncLeds); + collectionView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(SyncLed.Device))); + collectionView.SortDescriptions.Add(new SortDescription(nameof(SyncLed.Device), ListSortDirection.Ascending)); + collectionView.SortDescriptions.Add(new SortDescription(nameof(SyncLed.LedId), ListSortDirection.Ascending)); + collectionView.Refresh(); + return collectionView; + } + + private void UpdateLedLists() + { + SynchronizedLeds = GetGroupedLedList(SelectedSyncGroup.Leds); + OnPropertyChanged(nameof(SynchronizedLeds)); + + AvailableLeds = GetGroupedLedList(RGBSurface.Instance.Leds.Where(led => !SelectedSyncGroup.Leds.Any(sc => (sc.LedId == led.Id) && (sc.Device == led.Device.GetDeviceName())))); + OnPropertyChanged(nameof(AvailableLeds)); + } + + private void OpenHomepage() => Process.Start("https://github.com/DarthAffe/RGBSyncPlus"); + + private void AddSyncGroup() + { + SyncGroup syncGroup = new SyncGroup(); + SyncGroups.Add(syncGroup); + ApplicationManager.Instance.AddSyncGroup(syncGroup); + } + + private void RemoveSyncGroup(SyncGroup syncGroup) + { + if (syncGroup == null) return; + + if (MessageBox.Show($"Are you sure that you want to delete the group '{syncGroup.DisplayName}'", "Remove Sync-Group", MessageBoxButton.YesNo) == MessageBoxResult.No) + return; + + SyncGroups.Remove(syncGroup); + ApplicationManager.Instance.RemoveSyncGroup(syncGroup); + } + + void IDropTarget.DragOver(IDropInfo dropInfo) + { + if ((dropInfo.Data is SyncLed || dropInfo.Data is IEnumerable) && (dropInfo.TargetCollection is ListCollectionView)) + { + dropInfo.DropTargetAdorner = DropTargetAdorners.Highlight; + dropInfo.Effects = DragDropEffects.Copy; + } + } + + void IDropTarget.Drop(IDropInfo dropInfo) + { + if (!(dropInfo.TargetCollection is ListCollectionView targetList)) return; + + //HACK DarthAffe 04.06.2018: Super ugly hack - I've no idea how to do this correctly ... + ListCollectionView sourceList = targetList == AvailableLeds ? SynchronizedLeds : AvailableLeds; + + if (dropInfo.Data is SyncLed syncLed) + { + targetList.AddNewItem(syncLed); + sourceList.Remove(syncLed); + + targetList.CommitNew(); + sourceList.CommitEdit(); + } + else if (dropInfo.Data is IEnumerable syncLeds) + { + foreach (SyncLed led in syncLeds) + { + targetList.AddNewItem(led); + sourceList.Remove(led); + } + targetList.CommitNew(); + sourceList.CommitEdit(); + } + } + + #endregion + } +} diff --git a/RGBSync+/UI/ConfigurationWindow.xaml b/RGBSync+/UI/ConfigurationWindow.xaml new file mode 100644 index 0000000..819e8bd --- /dev/null +++ b/RGBSync+/UI/ConfigurationWindow.xaml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +