diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index ecab68953..36f107598 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -176,7 +176,7 @@ namespace Artemis.Core #region Rendering /// - public override void Render(SKCanvas canvas, SKPointI basePosition) + public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus) { if (Disposed) throw new ObjectDisposedException("Folder"); @@ -188,6 +188,10 @@ namespace Artemis.Core // No point rendering if all children are disabled if (!Children.Any(c => c is RenderProfileElement {Enabled: true})) return; + + // If the editor focus is on this folder, discard further focus for children to effectively focus the entire folder and all descendants + if (editorFocus == this) + editorFocus = null; SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low}; try @@ -208,7 +212,7 @@ namespace Artemis.Core // Iterate the children in reverse because the first layer must be rendered last to end up on top for (int index = Children.Count - 1; index > -1; index--) - Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top)); + Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top), editorFocus); foreach (BaseLayerEffect baseLayerEffect in LayerEffects) { diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index ca5f0d84c..7f2be3fb5 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -416,11 +416,14 @@ namespace Artemis.Core } /// - public override void Render(SKCanvas canvas, SKPointI basePosition) + public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus) { if (Disposed) throw new ObjectDisposedException("Layer"); + if (editorFocus != null && editorFocus != this) + return; + RenderLayer(canvas, basePosition); RenderCopies(canvas, basePosition); } @@ -496,7 +499,7 @@ namespace Artemis.Core private void RenderCopies(SKCanvas canvas, SKPointI basePosition) { for (int i = _renderCopies.Count - 1; i >= 0; i--) - _renderCopies[i].Render(canvas, basePosition); + _renderCopies[i].Render(canvas, basePosition, null); } /// @@ -627,20 +630,31 @@ namespace Artemis.Core if (LayerBrush == null) throw new ArtemisCoreException("The layer is not yet ready for rendering"); - using SKAutoCanvasRestore _ = new(canvas); foreach (BaseLayerEffect baseLayerEffect in LayerEffects) { if (!baseLayerEffect.Suspended) baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint); } - - canvas.ClipPath(renderPath); - LayerBrush.InternalRender(canvas, bounds, layerPaint); - - foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + + try { - if (!baseLayerEffect.Suspended) - baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint); + canvas.SaveLayer(layerPaint); + canvas.ClipPath(renderPath); + + // Restore the blend mode before doing the actual render + layerPaint.BlendMode = SKBlendMode.SrcOver; + + LayerBrush.InternalRender(canvas, bounds, layerPaint); + + foreach (BaseLayerEffect baseLayerEffect in LayerEffects) + { + if (!baseLayerEffect.Suspended) + baseLayerEffect.InternalPostProcess(canvas, bounds, layerPaint); + } + } + finally + { + canvas.Restore(); } } diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 6d2645f07..eaa4abce4 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -101,7 +101,7 @@ namespace Artemis.Core } /// - public override void Render(SKCanvas canvas, SKPointI basePosition) + public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus) { lock (_lock) { @@ -112,7 +112,7 @@ namespace Artemis.Core profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds); foreach (ProfileElement profileElement in Children) - profileElement.Render(canvas, basePosition); + profileElement.Render(canvas, basePosition, editorFocus); foreach (ProfileScript profileScript in Scripts) profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds); diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 11ba77cb6..83a131739 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -105,9 +105,12 @@ namespace Artemis.Core public abstract void Update(double deltaTime); /// - /// Renders the element + /// Renders the element /// - public abstract void Render(SKCanvas canvas, SKPointI basePosition); + /// The canvas to render upon. + /// The base position to use to translate relative positions to absolute positions. + /// An optional element to focus on while rendering (other elements will not render). + public abstract void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus); /// /// Resets the internal state of the element diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs index cc8805173..cfaeb5a48 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs @@ -1,4 +1,5 @@ -using SkiaSharp; +using System; +using SkiaSharp; namespace Artemis.Core.LayerBrushes { @@ -52,23 +53,24 @@ namespace Artemis.Core.LayerBrushes TryOrBreak(() => { + + for (int index = 0; index < Layer.Leds.Count; index++) { ArtemisLed artemisLed = Layer.Leds[index]; SKPoint renderPoint = points[index * 2 + 1]; if (!float.IsFinite(renderPoint.X) || !float.IsFinite(renderPoint.Y)) continue; - + // Let the brush determine the color - paint.Color = GetColor(artemisLed, renderPoint).WithAlpha(paint.Color.Alpha); - + paint.Color = GetColor(artemisLed, renderPoint); SKRect ledRectangle = SKRect.Create( artemisLed.AbsoluteRectangle.Left - Layer.Bounds.Left, artemisLed.AbsoluteRectangle.Top - Layer.Bounds.Top, artemisLed.AbsoluteRectangle.Width, artemisLed.AbsoluteRectangle.Height ); - + canvas.DrawRect(ledRectangle, paint); } }, "Failed to render"); diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs index d73a904e8..5e449f263 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs @@ -34,6 +34,11 @@ namespace Artemis.Core.Services /// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited /// bool RenderForEditor { get; set; } + + /// + /// Gets or sets the profile element to focus on while rendering for the editor + /// + ProfileElement? EditorFocus { get; set; } /// /// Activates the profile of the given with the currently active surface diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 12b212cd4..742a0b0ec 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -190,6 +190,7 @@ namespace Artemis.Core.Services public bool HotkeysEnabled { get; set; } public bool RenderForEditor { get; set; } + public ProfileElement? EditorFocus { get; set; } public void UpdateProfiles(double deltaTime) { @@ -247,7 +248,7 @@ namespace Artemis.Core.Services ProfileConfiguration? editedProfileConfiguration = _profileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(p => p.IsBeingEdited); if (editedProfileConfiguration != null) { - editedProfileConfiguration.Profile?.Render(canvas, SKPointI.Empty); + editedProfileConfiguration.Profile?.Render(canvas, SKPointI.Empty, RenderForEditor ? EditorFocus : null); return; } @@ -265,7 +266,7 @@ namespace Artemis.Core.Services ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; // Ensure all criteria are met before rendering if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet) - profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); + profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null); } catch (Exception e) { diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index 14fd740e3..beba258d7 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -36,6 +36,7 @@ internal class ProfileEditorService : IProfileEditorService private readonly SourceList _selectedKeyframes; private readonly IWindowService _windowService; private ProfileEditorCommandScope? _profileEditorHistoryScope; + private readonly PluginSetting _focusSelectedLayer; public ProfileEditorService(ILogger logger, IProfileService profileService, @@ -43,7 +44,8 @@ internal class ProfileEditorService : IProfileEditorService IRgbService rgbService, ILayerBrushService layerBrushService, IMainWindowService mainWindowService, - IWindowService windowService) + IWindowService windowService, + ISettingsService settingsService) { _logger = logger; _profileService = profileService; @@ -51,7 +53,8 @@ internal class ProfileEditorService : IProfileEditorService _rgbService = rgbService; _layerBrushService = layerBrushService; _windowService = windowService; - + _focusSelectedLayer = settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false); + _tools = new SourceList(); _selectedKeyframes = new SourceList(); _tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Subscribe(OnToolSelected); @@ -85,6 +88,7 @@ internal class ProfileEditorService : IProfileEditorService // When the main window closes, stop editing mainWindowService.MainWindowClosed += (_, _) => ChangeCurrentProfileConfiguration(null); + _focusSelectedLayer.SettingChanged += FocusSelectedLayerOnSettingChanged; } public IObservable ProfileConfiguration { get; } @@ -152,6 +156,7 @@ internal class ProfileEditorService : IProfileEditorService { _selectedKeyframes.Clear(); _profileElementSubject.OnNext(renderProfileElement); + _profileService.EditorFocus = _focusSelectedLayer.Value ? renderProfileElement : null; ChangeCurrentLayerProperty(null); } @@ -504,4 +509,9 @@ internal class ProfileEditorService : IProfileEditorService TickProfileElement(child, time); } } + + private void FocusSelectedLayerOnSettingChanged(object? sender, EventArgs e) + { + _profileService.EditorFocus = _focusSelectedLayer.Value ? _profileElementSubject.Value : null; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs index 4872f92bb..9a63f91a6 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/Panels/MenuBar/MenuBarViewModel.cs @@ -88,7 +88,7 @@ public class MenuBarViewModel : ActivatableViewModelBase public PluginSetting FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false); public PluginSetting ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); - public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false); + public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true); public PluginSetting AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false); public ProfileEditorHistory? History diff --git a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml index 9f57e492e..b52b38de1 100644 --- a/src/Artemis.UI/Screens/VisualScripting/CableView.axaml +++ b/src/Artemis.UI/Screens/VisualScripting/CableView.axaml @@ -18,7 +18,8 @@ - + segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y); segment.Point3 = new Point(ViewModel.ToPoint.X, ViewModel.ToPoint.Y); } + + private void OnPointerEnter(object? sender, PointerEventArgs e) + { + ViewModel?.UpdateDisplayValue(true); + } + + private void OnPointerLeave(object? sender, PointerEventArgs e) + { + ViewModel?.UpdateDisplayValue(false); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/CableViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/CableViewModel.cs index d4d8bba4c..4a3e4b5b6 100644 --- a/src/Artemis.UI/Screens/VisualScripting/CableViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/CableViewModel.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Exceptions; using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Shared; @@ -15,6 +17,9 @@ namespace Artemis.UI.Screens.VisualScripting; public class CableViewModel : ActivatableViewModelBase { + private readonly NodeScriptViewModel _nodeScriptViewModel; + private readonly IPin _from; + private readonly IPin _to; private ObservableAsPropertyHelper? _cableColor; private ObservableAsPropertyHelper? _fromPoint; private ObservableAsPropertyHelper? _toPoint; @@ -25,14 +30,19 @@ public class CableViewModel : ActivatableViewModelBase private PinViewModel? _toViewModel; private bool _displayValue; - public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to) + public CableViewModel(NodeScriptViewModel nodeScriptViewModel, IPin from, IPin to, ISettingsService settingsService) { + _nodeScriptViewModel = nodeScriptViewModel; + _from = from; + _to = to; + + AlwaysShowValues = settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true); + if (from.Direction != PinDirection.Output) throw new ArtemisUIException("Can only create cables originating from an output pin"); if (to.Direction != PinDirection.Input) throw new ArtemisUIException("Can only create cables targeted to an input pin"); - this.WhenActivated(d => { nodeScriptViewModel.PinViewModels.ToObservableChangeSet().Filter(p => ReferenceEquals(p.Pin, from)).Transform(model => FromViewModel = model).Subscribe().DisposeWith(d); @@ -65,12 +75,17 @@ public class CableViewModel : ActivatableViewModelBase .Select(tuple => tuple.Item1 != new Point(0, 0) && tuple.Item2 != new Point(0, 0)) .ToProperty(this, vm => vm.Connected) .DisposeWith(d); + + AlwaysShowValues.SettingChanged += AlwaysShowValuesOnSettingChanged; + Disposable.Create(() => AlwaysShowValues.SettingChanged -= AlwaysShowValuesOnSettingChanged).DisposeWith(d); }); - DisplayValue = !nodeScriptViewModel.IsPreview; + UpdateDisplayValue(false); } + public PluginSetting AlwaysShowValues { get; } + public PinViewModel? FromViewModel { get => _fromViewModel; @@ -86,13 +101,30 @@ public class CableViewModel : ActivatableViewModelBase public bool DisplayValue { get => _displayValue; - set => _displayValue = value; + set => RaiseAndSetIfChanged(ref _displayValue, value); } public bool Connected => _connected?.Value ?? false; + public bool IsFirst => _from.ConnectedTo[0] == _to; public Point FromPoint => _fromPoint?.Value ?? new Point(); public Point ToPoint => _toPoint?.Value ?? new Point(); public Point ValuePoint => _valuePoint?.Value ?? new Point(); public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255); + + private void AlwaysShowValuesOnSettingChanged(object? sender, EventArgs e) + { + UpdateDisplayValue(false); + } + + public void UpdateDisplayValue(bool hoveringOver) + { + if (_nodeScriptViewModel.IsPreview) + DisplayValue = false; + + if (hoveringOver) + DisplayValue = true; + else + DisplayValue = AlwaysShowValues.Value && IsFirst; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs index 3f058e74d..46169e93d 100644 --- a/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs +++ b/src/Artemis.UI/Screens/VisualScripting/NodeScriptWindowViewModel.cs @@ -78,7 +78,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase public PluginSetting ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); - public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false); + public PluginSetting AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true); private void ExecuteToggleBooleanSetting(PluginSetting setting) {