diff --git a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs index a9b1d7f4b..ba2b62fc2 100644 --- a/src/Artemis.Core/Plugins/Settings/PluginSetting.cs +++ b/src/Artemis.Core/Plugins/Settings/PluginSetting.cs @@ -48,12 +48,14 @@ namespace Artemis.Core get => _value; set { - if (!Equals(_value, value)) - { - _value = value; - OnSettingChanged(); - NotifyOfPropertyChange(nameof(Value)); - } + if (Equals(_value, value)) return; + + _value = value; + OnSettingChanged(); + NotifyOfPropertyChange(nameof(Value)); + + if (AutoSave) + Save(); } } @@ -62,6 +64,12 @@ namespace Artemis.Core /// public bool HasChanged => JsonConvert.SerializeObject(Value) != _pluginSettingEntity.Value; + /// + /// Gets or sets whether changes must automatically be saved + /// Note: When set to true is always false + /// + public bool AutoSave { get; set; } + /// /// Resets the setting to the last saved value /// diff --git a/src/Artemis.UI.Shared/Bootstrapper.cs b/src/Artemis.UI.Shared/Bootstrapper.cs new file mode 100644 index 000000000..a4535542e --- /dev/null +++ b/src/Artemis.UI.Shared/Bootstrapper.cs @@ -0,0 +1,22 @@ +using Artemis.UI.Shared.Services; +using Ninject; + +namespace Artemis.UI.Shared +{ + public static class Bootstrapper + { + public static bool Initialized { get; private set; } + + public static void Initialize(IKernel kernel) + { + if (Initialized) + return; + + var colorPickerService = kernel.Get(); + GradientPicker.ColorPickerService = colorPickerService; + ColorPicker.ColorPickerService = colorPickerService; + + Initialized = true; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml index 20c03d4e3..e4ce9f773 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml @@ -94,17 +94,18 @@ StaysOpen="{Binding StaysOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" PopupAnimation="Fade" IsOpen="{Binding PopupOpen, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"> - + + - + PreviewMouseUp="Slider_OnMouseUp" /> + + + + Preview on devices + + diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs index c2ebd7b07..bf0cc3527 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs @@ -5,6 +5,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; +using Artemis.UI.Shared.Services; namespace Artemis.UI.Shared { @@ -13,6 +14,8 @@ namespace Artemis.UI.Shared /// public partial class ColorPicker : UserControl, INotifyPropertyChanged { + private static IColorPickerService _colorPickerService; + public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(nameof(Color), typeof(Color), typeof(ColorPicker), new FrameworkPropertyMetadata(default(Color), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ColorPropertyChangedCallback)); @@ -44,6 +47,21 @@ namespace Artemis.UI.Shared public ColorPicker() { InitializeComponent(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + /// + /// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception + /// + internal static IColorPickerService ColorPickerService + { + set + { + if (_colorPickerService != null) + throw new AccessViolationException("This is not for you to touch"); + _colorPickerService = value; + } } public Color Color @@ -87,6 +105,7 @@ namespace Artemis.UI.Shared colorPicker.SetCurrentValue(ColorOpacityProperty, ((Color) e.NewValue).A); colorPicker.OnPropertyChanged(nameof(Color)); + _colorPickerService.UpdateColorDisplay(colorPicker.Color); colorPicker._inCallback = false; } @@ -126,6 +145,7 @@ namespace Artemis.UI.Shared color = Color.FromArgb(opacity, color.R, color.G, color.B); colorPicker.SetCurrentValue(ColorProperty, color); colorPicker.OnPropertyChanged(nameof(ColorOpacity)); + _colorPickerService.UpdateColorDisplay(colorPicker.Color); colorPicker._inCallback = false; } @@ -135,7 +155,7 @@ namespace Artemis.UI.Shared PopupOpen = !PopupOpen; e.Handled = true; } - + private void ColorGradient_OnMouseUp(object sender, MouseButtonEventArgs e) { e.Handled = true; @@ -144,13 +164,39 @@ namespace Artemis.UI.Shared private void Slider_OnMouseDown(object sender, MouseButtonEventArgs e) { OnDragStarted(); + + if (_colorPickerService.PreviewSetting.Value) + _colorPickerService.StartColorDisplay(); } private void Slider_OnMouseUp(object sender, MouseButtonEventArgs e) { OnDragEnded(); + _colorPickerService.StopColorDisplay(); } + private void PreviewCheckBoxClick(object sender, RoutedEventArgs e) + { + _colorPickerService.PreviewSetting.Value = PreviewCheckBox.IsChecked.Value; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value; + _colorPickerService.PreviewSetting.SettingChanged += PreviewSettingOnSettingChanged; + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + _colorPickerService.PreviewSetting.SettingChanged -= PreviewSettingOnSettingChanged; + } + + private void PreviewSettingOnSettingChanged(object sender, EventArgs e) + { + PreviewCheckBox.IsChecked = _colorPickerService.PreviewSetting.Value; + } + + #region Events public event EventHandler DragStarted; @@ -167,6 +213,5 @@ namespace Artemis.UI.Shared } #endregion - } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs index c1ee8b929..78c481c1f 100644 --- a/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/GradientPicker.xaml.cs @@ -16,7 +16,7 @@ namespace Artemis.UI.Shared /// public partial class GradientPicker : UserControl, INotifyPropertyChanged { - private static IGradientPickerService _gradientPickerService; + private static IColorPickerService _colorPickerService; private bool _inCallback; public GradientPicker() @@ -27,14 +27,13 @@ namespace Artemis.UI.Shared /// /// Used by the gradient picker to load saved gradients, do not touch or it'll just throw an exception /// - public static IGradientPickerService GradientPickerService + internal static IColorPickerService ColorPickerService { - private get => _gradientPickerService; set { - if (_gradientPickerService != null) + if (_colorPickerService != null) throw new AccessViolationException("This is not for you to touch"); - _gradientPickerService = value; + _colorPickerService = value; } } @@ -93,7 +92,7 @@ namespace Artemis.UI.Shared Execute.OnUIThread(async () => { OnDialogOpened(); - await GradientPickerService.ShowGradientPicker(ColorGradient, DialogHost); + await _colorPickerService.ShowGradientPicker(ColorGradient, DialogHost); OnDialogClosed(); }); } diff --git a/src/Artemis.UI.Shared/Controls/TreeViewPopupCompatibleCheckBox.cs b/src/Artemis.UI.Shared/Controls/TreeViewPopupCompatibleCheckBox.cs new file mode 100644 index 000000000..42952c013 --- /dev/null +++ b/src/Artemis.UI.Shared/Controls/TreeViewPopupCompatibleCheckBox.cs @@ -0,0 +1,22 @@ +using System.Windows.Controls; +using System.Windows.Input; + +namespace Artemis.UI.Shared +{ + // Workaround for https://developercommunity.visualstudio.com/content/problem/190202/button-controls-hosted-in-popup-windows-do-not-wor.html + /// + public class TreeViewPopupCompatibleCheckBox : CheckBox + { + /// + protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonDown(e); + } + + /// + protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e) + { + base.OnMouseLeftButtonUp(e); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/ColorPickerService.cs b/src/Artemis.UI.Shared/Services/ColorPickerService.cs new file mode 100644 index 000000000..843cf4adb --- /dev/null +++ b/src/Artemis.UI.Shared/Services/ColorPickerService.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Windows.Media; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Shared.Screens.GradientEditor; +using SkiaSharp; + +namespace Artemis.UI.Shared.Services +{ + internal class ColorPickerService : IColorPickerService + { + private readonly ICoreService _coreService; + private readonly IDialogService _dialogService; + + private bool _mustRenderOverlay; + private SKColor _overlayColor; + private float _overlayOpacity; + + public ColorPickerService(IDialogService dialogService, ICoreService coreService, ISettingsService settingsService) + { + _dialogService = dialogService; + _coreService = coreService; + + PreviewSetting = settingsService.GetSetting("UI.PreviewColorPickerOnDevices", false); + PreviewSetting.AutoSave = true; + } + + public PluginSetting PreviewSetting { get; } + + public Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost) + { + if (!string.IsNullOrWhiteSpace(dialogHost)) + return _dialogService.ShowDialogAt(dialogHost, new Dictionary {{"colorGradient", colorGradient}}); + return _dialogService.ShowDialog(new Dictionary {{"colorGradient", colorGradient}}); + } + + public void StartColorDisplay() + { + if (_mustRenderOverlay) + return; + + _overlayOpacity = 0f; + _mustRenderOverlay = true; + + _coreService.FrameRendering += RenderColorPickerOverlay; + } + + public void StopColorDisplay() + { + _mustRenderOverlay = false; + } + + public void UpdateColorDisplay(Color color) + { + _overlayColor = new SKColor(color.R, color.G, color.B, color.A); + } + + private void RenderColorPickerOverlay(object sender, FrameRenderingEventArgs e) + { + if (_mustRenderOverlay) + _overlayOpacity += 0.2f; + else + _overlayOpacity -= 0.2f; + + if (_overlayOpacity <= 0f) + { + _coreService.FrameRendering -= RenderColorPickerOverlay; + return; + } + + if (_overlayOpacity > 1f) + _overlayOpacity = 1f; + + using var overlayPaint = new SKPaint {Color = new SKColor(0, 0, 0, (byte) (255 * _overlayOpacity))}; + overlayPaint.Color = _overlayColor.WithAlpha((byte) (_overlayColor.Alpha * _overlayOpacity)); + e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/GradientPickerService.cs b/src/Artemis.UI.Shared/Services/GradientPickerService.cs deleted file mode 100644 index 4130d2812..000000000 --- a/src/Artemis.UI.Shared/Services/GradientPickerService.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Artemis.Core; -using Artemis.UI.Shared.Screens.GradientEditor; - -namespace Artemis.UI.Shared.Services -{ - internal class GradientPickerService : IGradientPickerService - { - private readonly IDialogService _dialogService; - - public GradientPickerService(IDialogService dialogService) - { - _dialogService = dialogService; - } - - public Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost) - { - if (!string.IsNullOrWhiteSpace(dialogHost)) - return _dialogService.ShowDialogAt(dialogHost, new Dictionary {{"colorGradient", colorGradient}}); - return _dialogService.ShowDialog(new Dictionary {{"colorGradient", colorGradient}}); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs new file mode 100644 index 000000000..4258d7596 --- /dev/null +++ b/src/Artemis.UI.Shared/Services/Interfaces/IColorPickerService.cs @@ -0,0 +1,16 @@ +using System.Threading.Tasks; +using System.Windows.Media; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services +{ + internal interface IColorPickerService : IArtemisSharedUIService + { + Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost); + + PluginSetting PreviewSetting { get; } + void StartColorDisplay(); + void StopColorDisplay(); + void UpdateColorDisplay(Color color); + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs b/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs deleted file mode 100644 index c47da27bc..000000000 --- a/src/Artemis.UI.Shared/Services/Interfaces/IGradientPickerService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; -using Artemis.Core; - -namespace Artemis.UI.Shared.Services -{ - public interface IGradientPickerService : IArtemisSharedUIService - { - Task ShowGradientPicker(ColorGradient colorGradient, string dialogHost); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs index 605ee367f..e3ea62a2a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs @@ -30,21 +30,22 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio public ConditionalDataBinding ConditionalDataBinding { get; } public BindableCollection> ConditionViewModels { get; } + public bool SupportsTestValue => false; public void Update() { UpdateConditionViewModels(); } - + public object GetTestValue() { - return ConditionalDataBinding.DataBinding.Converter.GetValue(); + throw new NotSupportedException(); } public void AddCondition(string type) { var condition = ConditionalDataBinding.AddCondition(); - + // Find the VM of the new condition var viewModel = ConditionViewModels.First(c => c.DataBindingCondition == condition); viewModel.ActiveItem.AddCondition(type); @@ -58,7 +59,8 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio // Remove old VMs var toRemove = ConditionViewModels.Where(c => !ConditionalDataBinding.Conditions.Contains(c.DataBindingCondition)).ToList(); - foreach (var dataBindingConditionViewModel in toRemove) { + foreach (var dataBindingConditionViewModel in toRemove) + { ConditionViewModels.Remove(dataBindingConditionViewModel); dataBindingConditionViewModel.Dispose(); } @@ -72,7 +74,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio // Fix order ConditionViewModels.Sort(c => c.DataBindingCondition.Order); - + _updating = false; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml index 2f35ca31e..e09257740 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingView.xaml @@ -21,13 +21,13 @@ - - + + @@ -39,11 +39,10 @@ Style="{StaticResource MaterialDesignFloatingHintComboBox}" materialDesign:HintAssist.Hint="Data binding mode" MinWidth="128" - SelectedValue="{Binding SelectedDataBindingMode}" - ItemsSource="{Binding DataBindingModes}" + SelectedValue="{Binding SelectedDataBindingMode}" + ItemsSource="{Binding DataBindingModes}" SelectedValuePath="Value" - DisplayMemberPath="Description" > - + DisplayMemberPath="Description" /> - Input + Input + Grid.Column="1" + Visibility="{Binding ActiveItem.SupportsTestValue, Converter={x:Static s:BoolToVisibilityConverter.Instance}}" + s:View.Model="{Binding TestInputValue}" + Margin="0 2" + FontFamily="Consolas" + VerticalAlignment="Stretch" + HorizontalAlignment="Right" /> Output - + @@ -127,10 +127,10 @@ - + diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs index 72fbefd50..27c72d5c1 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DataBindingViewModel.cs @@ -205,17 +205,23 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings private void UpdateTestResult() { - if (Registration.DataBinding == null) + if (Registration.DataBinding == null || ActiveItem == null) { TestInputValue.UpdateValue(default); TestResultValue.UpdateValue(default); return; } - var currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); - - TestInputValue.UpdateValue(currentValue); - TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); + if (ActiveItem.SupportsTestValue) + { + var currentValue = Registration.Converter.ConvertFromObject(ActiveItem?.GetTestValue() ?? default(TProperty)); + TestInputValue.UpdateValue(currentValue); + TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(currentValue) : default); + } + else + { + TestResultValue.UpdateValue(Registration.DataBinding != null ? Registration.DataBinding.GetValue(default) : default); + } } private void EnableDataBinding() diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs index dde2ca976..72d11cd20 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/DirectDataBinding/DirectDataBindingModeViewModel.cs @@ -38,6 +38,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DirectDa public DirectDataBinding DirectDataBinding { get; } public BindableCollection> ModifierViewModels { get; } + public bool SupportsTestValue => true; public bool CanAddModifier { diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs index 7add83036..2083cc99a 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/IDataBindingModeViewModel.cs @@ -6,6 +6,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings public interface IDataBindingModeViewModel : IScreen, IDisposable { void Update(); + bool SupportsTestValue { get; } object GetTestValue(); } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/TrayViewModel.cs b/src/Artemis.UI/Screens/TrayViewModel.cs index b4f7b7c39..e5bffe1c7 100644 --- a/src/Artemis.UI/Screens/TrayViewModel.cs +++ b/src/Artemis.UI/Screens/TrayViewModel.cs @@ -3,8 +3,6 @@ using Artemis.Core.Services; using Artemis.UI.Events; using Artemis.UI.Screens.Splash; using Artemis.UI.Services.Interfaces; -using Artemis.UI.Shared; -using Artemis.UI.Shared.Services; using Ninject; using Stylet; @@ -12,15 +10,14 @@ namespace Artemis.UI.Screens { public class TrayViewModel : Screen { + private readonly IDebugService _debugService; private readonly IEventAggregator _eventAggregator; private readonly IKernel _kernel; private readonly IWindowManager _windowManager; - private readonly IDebugService _debugService; private bool _canShowRootViewModel; - private bool _setGradientPickerService; private SplashViewModel _splashViewModel; - public TrayViewModel(IKernel kernel, IWindowManager windowManager, IEventAggregator eventAggregator, ICoreService coreService, IDebugService debugService,ISettingsService settingsService) + public TrayViewModel(IKernel kernel, IWindowManager windowManager, IEventAggregator eventAggregator, ICoreService coreService, IDebugService debugService, ISettingsService settingsService) { _kernel = kernel; _windowManager = windowManager; @@ -48,13 +45,9 @@ namespace Artemis.UI.Screens if (!CanShowRootViewModel) return; - // The gradient picker must have a reference to this service to be able to load saved gradients. - // To avoid wasting resources, only set the service once and not until showing the UI. - if (!_setGradientPickerService) - { - GradientPicker.GradientPickerService = _kernel.Get(); - _setGradientPickerService = true; - } + // Initialize the shared UI when first showing the window + if (!UI.Shared.Bootstrapper.Initialized) + UI.Shared.Bootstrapper.Initialize(_kernel); CanShowRootViewModel = false; Execute.OnUIThread(() =>