1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile - Blending mode fixes

Nodes editor - Adhere to cable display settings
Profile - Added focus layer option
This commit is contained in:
Robert 2022-08-14 10:18:17 +02:00
parent e401fdf964
commit 3f52279305
13 changed files with 115 additions and 32 deletions

View File

@ -176,7 +176,7 @@ namespace Artemis.Core
#region Rendering #region Rendering
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPointI basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Folder"); throw new ObjectDisposedException("Folder");
@ -188,6 +188,10 @@ namespace Artemis.Core
// No point rendering if all children are disabled // No point rendering if all children are disabled
if (!Children.Any(c => c is RenderProfileElement {Enabled: true})) if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
return; 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}; SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try 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 // 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--) 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) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{ {

View File

@ -416,11 +416,14 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPointI basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
{ {
if (Disposed) if (Disposed)
throw new ObjectDisposedException("Layer"); throw new ObjectDisposedException("Layer");
if (editorFocus != null && editorFocus != this)
return;
RenderLayer(canvas, basePosition); RenderLayer(canvas, basePosition);
RenderCopies(canvas, basePosition); RenderCopies(canvas, basePosition);
} }
@ -496,7 +499,7 @@ namespace Artemis.Core
private void RenderCopies(SKCanvas canvas, SKPointI basePosition) private void RenderCopies(SKCanvas canvas, SKPointI basePosition)
{ {
for (int i = _renderCopies.Count - 1; i >= 0; i--) for (int i = _renderCopies.Count - 1; i >= 0; i--)
_renderCopies[i].Render(canvas, basePosition); _renderCopies[i].Render(canvas, basePosition, null);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -627,20 +630,31 @@ namespace Artemis.Core
if (LayerBrush == null) if (LayerBrush == null)
throw new ArtemisCoreException("The layer is not yet ready for rendering"); throw new ArtemisCoreException("The layer is not yet ready for rendering");
using SKAutoCanvasRestore _ = new(canvas);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects) foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{ {
if (!baseLayerEffect.Suspended) if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint); baseLayerEffect.InternalPreProcess(canvas, bounds, layerPaint);
} }
canvas.ClipPath(renderPath); try
LayerBrush.InternalRender(canvas, bounds, layerPaint);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{ {
if (!baseLayerEffect.Suspended) canvas.SaveLayer(layerPaint);
baseLayerEffect.InternalPostProcess(canvas, bounds, 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();
} }
} }

View File

@ -101,7 +101,7 @@ namespace Artemis.Core
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Render(SKCanvas canvas, SKPointI basePosition) public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
{ {
lock (_lock) lock (_lock)
{ {
@ -112,7 +112,7 @@ namespace Artemis.Core
profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds); profileScript.OnProfileRendering(canvas, canvas.LocalClipBounds);
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Render(canvas, basePosition); profileElement.Render(canvas, basePosition, editorFocus);
foreach (ProfileScript profileScript in Scripts) foreach (ProfileScript profileScript in Scripts)
profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds); profileScript.OnProfileRendered(canvas, canvas.LocalClipBounds);

View File

@ -105,9 +105,12 @@ namespace Artemis.Core
public abstract void Update(double deltaTime); public abstract void Update(double deltaTime);
/// <summary> /// <summary>
/// Renders the element /// Renders the element
/// </summary> /// </summary>
public abstract void Render(SKCanvas canvas, SKPointI basePosition); /// <param name="canvas">The canvas to render upon.</param>
/// <param name="basePosition">The base position to use to translate relative positions to absolute positions.</param>
/// <param name="editorFocus">An optional element to focus on while rendering (other elements will not render).</param>
public abstract void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus);
/// <summary> /// <summary>
/// Resets the internal state of the element /// Resets the internal state of the element

View File

@ -1,4 +1,5 @@
using SkiaSharp; using System;
using SkiaSharp;
namespace Artemis.Core.LayerBrushes namespace Artemis.Core.LayerBrushes
{ {
@ -52,23 +53,24 @@ namespace Artemis.Core.LayerBrushes
TryOrBreak(() => TryOrBreak(() =>
{ {
for (int index = 0; index < Layer.Leds.Count; index++) for (int index = 0; index < Layer.Leds.Count; index++)
{ {
ArtemisLed artemisLed = Layer.Leds[index]; ArtemisLed artemisLed = Layer.Leds[index];
SKPoint renderPoint = points[index * 2 + 1]; SKPoint renderPoint = points[index * 2 + 1];
if (!float.IsFinite(renderPoint.X) || !float.IsFinite(renderPoint.Y)) if (!float.IsFinite(renderPoint.X) || !float.IsFinite(renderPoint.Y))
continue; continue;
// Let the brush determine the color // Let the brush determine the color
paint.Color = GetColor(artemisLed, renderPoint).WithAlpha(paint.Color.Alpha); paint.Color = GetColor(artemisLed, renderPoint);
SKRect ledRectangle = SKRect.Create( SKRect ledRectangle = SKRect.Create(
artemisLed.AbsoluteRectangle.Left - Layer.Bounds.Left, artemisLed.AbsoluteRectangle.Left - Layer.Bounds.Left,
artemisLed.AbsoluteRectangle.Top - Layer.Bounds.Top, artemisLed.AbsoluteRectangle.Top - Layer.Bounds.Top,
artemisLed.AbsoluteRectangle.Width, artemisLed.AbsoluteRectangle.Width,
artemisLed.AbsoluteRectangle.Height artemisLed.AbsoluteRectangle.Height
); );
canvas.DrawRect(ledRectangle, paint); canvas.DrawRect(ledRectangle, paint);
} }
}, "Failed to render"); }, "Failed to render");

View File

@ -34,6 +34,11 @@ namespace Artemis.Core.Services
/// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited /// Gets or sets a boolean indicating whether rendering should only be done for profiles being edited
/// </summary> /// </summary>
bool RenderForEditor { get; set; } bool RenderForEditor { get; set; }
/// <summary>
/// Gets or sets the profile element to focus on while rendering for the editor
/// </summary>
ProfileElement? EditorFocus { get; set; }
/// <summary> /// <summary>
/// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface /// Activates the profile of the given <see cref="ProfileConfiguration" /> with the currently active surface

View File

@ -190,6 +190,7 @@ namespace Artemis.Core.Services
public bool HotkeysEnabled { get; set; } public bool HotkeysEnabled { get; set; }
public bool RenderForEditor { get; set; } public bool RenderForEditor { get; set; }
public ProfileElement? EditorFocus { get; set; }
public void UpdateProfiles(double deltaTime) public void UpdateProfiles(double deltaTime)
{ {
@ -247,7 +248,7 @@ namespace Artemis.Core.Services
ProfileConfiguration? editedProfileConfiguration = _profileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(p => p.IsBeingEdited); ProfileConfiguration? editedProfileConfiguration = _profileCategories.SelectMany(c => c.ProfileConfigurations).FirstOrDefault(p => p.IsBeingEdited);
if (editedProfileConfiguration != null) if (editedProfileConfiguration != null)
{ {
editedProfileConfiguration.Profile?.Render(canvas, SKPointI.Empty); editedProfileConfiguration.Profile?.Render(canvas, SKPointI.Empty, RenderForEditor ? EditorFocus : null);
return; return;
} }
@ -265,7 +266,7 @@ namespace Artemis.Core.Services
ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j]; ProfileConfiguration profileConfiguration = profileCategory.ProfileConfigurations[j];
// Ensure all criteria are met before rendering // Ensure all criteria are met before rendering
if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet) if (!profileConfiguration.IsSuspended && !profileConfiguration.IsMissingModule && profileConfiguration.ActivationConditionMet)
profileConfiguration.Profile?.Render(canvas, SKPointI.Empty); profileConfiguration.Profile?.Render(canvas, SKPointI.Empty, null);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -36,6 +36,7 @@ internal class ProfileEditorService : IProfileEditorService
private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes; private readonly SourceList<ILayerPropertyKeyframe> _selectedKeyframes;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ProfileEditorCommandScope? _profileEditorHistoryScope; private ProfileEditorCommandScope? _profileEditorHistoryScope;
private readonly PluginSetting<bool> _focusSelectedLayer;
public ProfileEditorService(ILogger logger, public ProfileEditorService(ILogger logger,
IProfileService profileService, IProfileService profileService,
@ -43,7 +44,8 @@ internal class ProfileEditorService : IProfileEditorService
IRgbService rgbService, IRgbService rgbService,
ILayerBrushService layerBrushService, ILayerBrushService layerBrushService,
IMainWindowService mainWindowService, IMainWindowService mainWindowService,
IWindowService windowService) IWindowService windowService,
ISettingsService settingsService)
{ {
_logger = logger; _logger = logger;
_profileService = profileService; _profileService = profileService;
@ -51,7 +53,8 @@ internal class ProfileEditorService : IProfileEditorService
_rgbService = rgbService; _rgbService = rgbService;
_layerBrushService = layerBrushService; _layerBrushService = layerBrushService;
_windowService = windowService; _windowService = windowService;
_focusSelectedLayer = settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false);
_tools = new SourceList<IToolViewModel>(); _tools = new SourceList<IToolViewModel>();
_selectedKeyframes = new SourceList<ILayerPropertyKeyframe>(); _selectedKeyframes = new SourceList<ILayerPropertyKeyframe>();
_tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Subscribe(OnToolSelected); _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 // When the main window closes, stop editing
mainWindowService.MainWindowClosed += (_, _) => ChangeCurrentProfileConfiguration(null); mainWindowService.MainWindowClosed += (_, _) => ChangeCurrentProfileConfiguration(null);
_focusSelectedLayer.SettingChanged += FocusSelectedLayerOnSettingChanged;
} }
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; } public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
@ -152,6 +156,7 @@ internal class ProfileEditorService : IProfileEditorService
{ {
_selectedKeyframes.Clear(); _selectedKeyframes.Clear();
_profileElementSubject.OnNext(renderProfileElement); _profileElementSubject.OnNext(renderProfileElement);
_profileService.EditorFocus = _focusSelectedLayer.Value ? renderProfileElement : null;
ChangeCurrentLayerProperty(null); ChangeCurrentLayerProperty(null);
} }
@ -504,4 +509,9 @@ internal class ProfileEditorService : IProfileEditorService
TickProfileElement(child, time); TickProfileElement(child, time);
} }
} }
private void FocusSelectedLayerOnSettingChanged(object? sender, EventArgs e)
{
_profileService.EditorFocus = _focusSelectedLayer.Value ? _profileElementSubject.Value : null;
}
} }

View File

@ -88,7 +88,7 @@ public class MenuBarViewModel : ActivatableViewModelBase
public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false); public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", false);
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false); public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true);
public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false); public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", false);
public ProfileEditorHistory? History public ProfileEditorHistory? History

View File

@ -18,7 +18,8 @@
<shared:SKColorToStringConverter x:Key="SKColorToStringConverter" /> <shared:SKColorToStringConverter x:Key="SKColorToStringConverter" />
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" /> <shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources> </UserControl.Resources>
<Canvas> <Canvas PointerEnter="OnPointerEnter"
PointerLeave="OnPointerLeave">
<Path Name="CablePath" <Path Name="CablePath"
Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}" Stroke="{CompiledBinding CableColor, Converter={StaticResource ColorToSolidColorBrushConverter}}"
StrokeThickness="4" StrokeThickness="4"

View File

@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
@ -51,4 +52,14 @@ public class CableView : ReactiveUserControl<CableViewModel>
segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y); segment.Point2 = new Point(ViewModel.ToPoint.X - CABLE_OFFSET, ViewModel.ToPoint.Y);
segment.Point3 = new Point(ViewModel.ToPoint.X, 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);
}
} }

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions; using Artemis.UI.Exceptions;
using Artemis.UI.Screens.VisualScripting.Pins; using Artemis.UI.Screens.VisualScripting.Pins;
using Artemis.UI.Shared; using Artemis.UI.Shared;
@ -15,6 +17,9 @@ namespace Artemis.UI.Screens.VisualScripting;
public class CableViewModel : ActivatableViewModelBase public class CableViewModel : ActivatableViewModelBase
{ {
private readonly NodeScriptViewModel _nodeScriptViewModel;
private readonly IPin _from;
private readonly IPin _to;
private ObservableAsPropertyHelper<Color>? _cableColor; private ObservableAsPropertyHelper<Color>? _cableColor;
private ObservableAsPropertyHelper<Point>? _fromPoint; private ObservableAsPropertyHelper<Point>? _fromPoint;
private ObservableAsPropertyHelper<Point>? _toPoint; private ObservableAsPropertyHelper<Point>? _toPoint;
@ -25,14 +30,19 @@ public class CableViewModel : ActivatableViewModelBase
private PinViewModel? _toViewModel; private PinViewModel? _toViewModel;
private bool _displayValue; 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) if (from.Direction != PinDirection.Output)
throw new ArtemisUIException("Can only create cables originating from an output pin"); throw new ArtemisUIException("Can only create cables originating from an output pin");
if (to.Direction != PinDirection.Input) if (to.Direction != PinDirection.Input)
throw new ArtemisUIException("Can only create cables targeted to an input pin"); throw new ArtemisUIException("Can only create cables targeted to an input pin");
this.WhenActivated(d => this.WhenActivated(d =>
{ {
nodeScriptViewModel.PinViewModels.ToObservableChangeSet().Filter(p => ReferenceEquals(p.Pin, from)).Transform(model => FromViewModel = model).Subscribe().DisposeWith(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)) .Select(tuple => tuple.Item1 != new Point(0, 0) && tuple.Item2 != new Point(0, 0))
.ToProperty(this, vm => vm.Connected) .ToProperty(this, vm => vm.Connected)
.DisposeWith(d); .DisposeWith(d);
AlwaysShowValues.SettingChanged += AlwaysShowValuesOnSettingChanged;
Disposable.Create(() => AlwaysShowValues.SettingChanged -= AlwaysShowValuesOnSettingChanged).DisposeWith(d);
}); });
DisplayValue = !nodeScriptViewModel.IsPreview; UpdateDisplayValue(false);
} }
public PluginSetting<bool> AlwaysShowValues { get; }
public PinViewModel? FromViewModel public PinViewModel? FromViewModel
{ {
get => _fromViewModel; get => _fromViewModel;
@ -86,13 +101,30 @@ public class CableViewModel : ActivatableViewModelBase
public bool DisplayValue public bool DisplayValue
{ {
get => _displayValue; get => _displayValue;
set => _displayValue = value; set => RaiseAndSetIfChanged(ref _displayValue, value);
} }
public bool Connected => _connected?.Value ?? false; public bool Connected => _connected?.Value ?? false;
public bool IsFirst => _from.ConnectedTo[0] == _to;
public Point FromPoint => _fromPoint?.Value ?? new Point(); public Point FromPoint => _fromPoint?.Value ?? new Point();
public Point ToPoint => _toPoint?.Value ?? new Point(); public Point ToPoint => _toPoint?.Value ?? new Point();
public Point ValuePoint => _valuePoint?.Value ?? new Point(); public Point ValuePoint => _valuePoint?.Value ?? new Point();
public Color CableColor => _cableColor?.Value ?? new Color(255, 255, 255, 255); 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;
}
} }

View File

@ -78,7 +78,7 @@ public class NodeScriptWindowViewModel : DialogViewModelBase<bool>
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false); public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", false);
public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", false); public PluginSetting<bool> AlwaysShowValues => _settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true);
private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting) private void ExecuteToggleBooleanSetting(PluginSetting<bool> setting)
{ {