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

Transform tool - WIP commit

This commit is contained in:
Robert 2022-02-08 23:07:15 +01:00
parent c36110e79d
commit 12e91b8c81
47 changed files with 782 additions and 208 deletions

View File

@ -22,7 +22,7 @@ namespace Artemis.UI.Shared.DataModelVisualization
set set
{ {
if (Equals(value, _displayValue)) return; if (Equals(value, _displayValue)) return;
this.RaiseAndSetIfChanged(ref _displayValue, value); RaiseAndSetIfChanged(ref _displayValue, value);
OnDisplayValueUpdated(); OnDisplayValueUpdated();
} }
} }
@ -56,7 +56,7 @@ namespace Artemis.UI.Shared.DataModelVisualization
public DataModelPropertyAttribute? PropertyDescription public DataModelPropertyAttribute? PropertyDescription
{ {
get => _propertyDescription; get => _propertyDescription;
internal set => this.RaiseAndSetIfChanged(ref _propertyDescription, value); internal set => RaiseAndSetIfChanged(ref _propertyDescription, value);
} }
/// <summary> /// <summary>

View File

@ -28,7 +28,7 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
public string Display public string Display
{ {
get => _display; get => _display;
set => this.RaiseAndSetIfChanged(ref _display, value); set => RaiseAndSetIfChanged(ref _display, value);
} }
protected override void OnDisplayValueUpdated() protected override void OnDisplayValueUpdated()

View File

@ -0,0 +1,109 @@
using System.Linq;
using Artemis.Core;
using SkiaSharp;
namespace Artemis.UI.Shared.Extensions;
/// <summary>
/// Provides utilities when working with layers in UI elements.
/// </summary>
public static class LayerExtensions
{
/// <summary>
/// Returns the layer's bounds in real coordinates.
/// </summary>
public static SKRect GetLayerBounds(this Layer layer)
{
return new SKRect(
layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.X),
layer.Leds.Min(l => l.RgbLed.AbsoluteBoundary.Location.Y),
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.X + l.RgbLed.AbsoluteBoundary.Size.Width),
layer.Leds.Max(l => l.RgbLed.AbsoluteBoundary.Location.Y + l.RgbLed.AbsoluteBoundary.Size.Height)
);
}
/// <summary>
/// Returns the layer's anchor in real coordinates.
/// </summary>
public static SKPoint GetLayerAnchorPosition(this Layer layer, SKPoint? positionOverride = null)
{
SKRect layerBounds = GetLayerBounds(layer);
SKPoint positionProperty = layer.Transform.Position.CurrentValue;
if (positionOverride != null)
positionProperty = positionOverride.Value;
// Start at the center of the shape
SKPoint position = new(layerBounds.MidX, layerBounds.MidY);
// Apply translation
position.X += positionProperty.X * layerBounds.Width;
position.Y += positionProperty.Y * layerBounds.Height;
return position;
}
/// <summary>
/// Returns an absolute and scaled rectangular path for the given layer in real coordinates.
/// </summary>
public static SKPath GetLayerPath(this Layer layer, bool includeTranslation, bool includeScale, bool includeRotation, SKPoint? anchorOverride = null)
{
SKRect layerBounds = GetLayerBounds(layer);
// Apply transformation like done by the core during layer rendering (same differences apply as in GetLayerTransformGroup)
SKPoint anchorPosition = GetLayerAnchorPosition(layer);
if (anchorOverride != null)
anchorPosition = anchorOverride.Value;
SKPoint anchorProperty = layer.Transform.AnchorPoint.CurrentValue;
// Translation originates from the unscaled center of the shape and is tied to the anchor
float x = anchorPosition.X - layerBounds.MidX - anchorProperty.X * layerBounds.Width;
float y = anchorPosition.Y - layerBounds.MidY - anchorProperty.Y * layerBounds.Height;
SKPath path = new();
path.AddRect(layerBounds);
if (includeTranslation)
path.Transform(SKMatrix.CreateTranslation(x, y));
if (includeScale)
path.Transform(SKMatrix.CreateScale(layer.Transform.Scale.CurrentValue.Width / 100f, layer.Transform.Scale.CurrentValue.Height / 100f, anchorPosition.X, anchorPosition.Y));
if (includeRotation)
path.Transform(SKMatrix.CreateRotationDegrees(layer.Transform.Rotation.CurrentValue, anchorPosition.X, anchorPosition.Y));
return path;
}
/// <summary>
/// Returns a new point normalized to 0.0-1.0
/// </summary>
public static SKPoint GetScaledPoint(this Layer layer, SKPoint point, bool absolute)
{
SKRect bounds = GetLayerBounds(layer);
if (absolute)
return new SKPoint(
100 / bounds.Width * (point.X - bounds.Left) / 100,
100 / bounds.Height * (point.Y - bounds.Top) / 100
);
return new SKPoint(
100 / bounds.Width * point.X / 100,
100 / bounds.Height * point.Y / 100
);
}
/// <summary>
/// Returns the offset from the given point to the top-left of the layer
/// </summary>
public static SKPoint GetDragOffset(this Layer layer, SKPoint dragStart)
{
// Figure out what the top left will be if the shape moves to the current cursor position
SKPoint scaledDragStart = GetScaledPoint(layer, dragStart, true);
SKPoint tempAnchor = GetLayerAnchorPosition(layer, scaledDragStart);
SKPoint tempTopLeft = GetLayerPath(layer, true, true, true, tempAnchor)[0];
// Get the shape's position
SKPoint topLeft = GetLayerPath(layer, true, true, true)[0];
// The difference between the two is the offset
return topLeft - tempTopLeft;
}
}

View File

@ -42,7 +42,7 @@ namespace Artemis.UI.Shared.ScriptingProviders
public Script? Script public Script? Script
{ {
get => _script; get => _script;
internal set => this.RaiseAndSetIfChanged(ref _script, value); internal set => RaiseAndSetIfChanged(ref _script, value);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -78,7 +78,7 @@ public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel
public bool IsSelected public bool IsSelected
{ {
get => _isSelected; get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value); set => RaiseAndSetIfChanged(ref _isSelected, value);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,12 +1,14 @@
using System; using System;
using System.Reactive.Disposables; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Artemis.UI.Shared.Events; using Artemis.UI.Shared.Events;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using JetBrains.Annotations;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.Validation.Helpers; using ReactiveUI.Validation.Helpers;
namespace Artemis.UI.Shared namespace Artemis.UI.Shared;
{
/// <summary> /// <summary>
/// Represents the base class for Artemis view models /// Represents the base class for Artemis view models
/// </summary> /// </summary>
@ -17,15 +19,6 @@ namespace Artemis.UI.Shared
/// </summary> /// </summary>
public ContentDialog? ContentDialog { get; internal set; } public ContentDialog? ContentDialog { get; internal set; }
#region Implementation of IActivatableViewModel
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
#endregion
#region IDisposable
/// <summary> /// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary> /// </summary>
@ -37,14 +30,19 @@ namespace Artemis.UI.Shared
{ {
} }
#region Implementation of IActivatableViewModel
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
#endregion
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
#endregion
} }
/// <summary> /// <summary>
@ -77,7 +75,34 @@ namespace Artemis.UI.Shared
public string? DisplayName public string? DisplayName
{ {
get => _displayName; get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value); set => RaiseAndSetIfChanged(ref _displayName, value);
}
/// <summary>
/// RaiseAndSetIfChanged fully implements a Setter for a read-write property on a ReactiveObject, using
/// CallerMemberName to raise the notification and the ref to the backing field to set the property.
/// </summary>
/// <typeparam name="TRet">The type of the return value.</typeparam>
/// <param name="backingField">A Reference to the backing field for this property.</param>
/// <param name="newValue">The new value.</param>
/// <param name="propertyName">
/// The name of the property, usually automatically provided through the CallerMemberName
/// attribute.
/// </param>
/// <returns>The newly set value, normally discarded.</returns>
[NotifyPropertyChangedInvocator]
public TRet RaiseAndSetIfChanged<TRet>(ref TRet backingField, TRet newValue, [CallerMemberName] string? propertyName = null)
{
if (propertyName is null)
throw new ArgumentNullException(nameof(propertyName));
if (EqualityComparer<TRet>.Default.Equals(backingField, newValue))
return newValue;
this.RaisePropertyChanging(propertyName);
backingField = newValue;
this.RaisePropertyChanged(propertyName);
return newValue;
} }
} }
@ -115,4 +140,3 @@ namespace Artemis.UI.Shared
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested; internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
internal event EventHandler? CancelRequested; internal event EventHandler? CancelRequested;
} }
}

View File

@ -38,7 +38,7 @@ namespace Artemis.UI.Screens.Debugger
public NavigationViewItem? SelectedItem public NavigationViewItem? SelectedItem
{ {
get => _selectedItem; get => _selectedItem;
set => this.RaiseAndSetIfChanged(ref _selectedItem, value); set => RaiseAndSetIfChanged(ref _selectedItem, value);
} }
private void NavigateToSelectedItem(NavigationViewItem item) private void NavigateToSelectedItem(NavigationViewItem item)

View File

@ -59,13 +59,13 @@ namespace Artemis.UI.Screens.Debugger.DataModel
public DataModelPropertiesViewModel? MainDataModel public DataModelPropertiesViewModel? MainDataModel
{ {
get => _mainDataModel; get => _mainDataModel;
set => this.RaiseAndSetIfChanged(ref _mainDataModel, value); set => RaiseAndSetIfChanged(ref _mainDataModel, value);
} }
public string? PropertySearch public string? PropertySearch
{ {
get => _propertySearch; get => _propertySearch;
set => this.RaiseAndSetIfChanged(ref _propertySearch, value); set => RaiseAndSetIfChanged(ref _propertySearch, value);
} }
public bool SlowUpdates public bool SlowUpdates
@ -73,7 +73,7 @@ namespace Artemis.UI.Screens.Debugger.DataModel
get => _slowUpdates; get => _slowUpdates;
set set
{ {
this.RaiseAndSetIfChanged(ref _slowUpdates, value); RaiseAndSetIfChanged(ref _slowUpdates, value);
_updateTimer.Interval = _slowUpdates ? 500 : 25; _updateTimer.Interval = _slowUpdates ? 500 : 25;
} }
} }
@ -85,7 +85,7 @@ namespace Artemis.UI.Screens.Debugger.DataModel
get => _selectedModule; get => _selectedModule;
set set
{ {
this.RaiseAndSetIfChanged(ref _selectedModule, value); RaiseAndSetIfChanged(ref _selectedModule, value);
GetDataModel(); GetDataModel();
} }
} }
@ -95,7 +95,7 @@ namespace Artemis.UI.Screens.Debugger.DataModel
get => _isModuleFilterEnabled; get => _isModuleFilterEnabled;
set set
{ {
this.RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value); RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value);
if (!IsModuleFilterEnabled) if (!IsModuleFilterEnabled)
SelectedModule = null; SelectedModule = null;

View File

@ -22,31 +22,31 @@ namespace Artemis.UI.Screens.Debugger.Performance
public string? Last public string? Last
{ {
get => _last; get => _last;
set => this.RaiseAndSetIfChanged(ref _last, value); set => RaiseAndSetIfChanged(ref _last, value);
} }
public string? Average public string? Average
{ {
get => _average; get => _average;
set => this.RaiseAndSetIfChanged(ref _average, value); set => RaiseAndSetIfChanged(ref _average, value);
} }
public string? Min public string? Min
{ {
get => _min; get => _min;
set => this.RaiseAndSetIfChanged(ref _min, value); set => RaiseAndSetIfChanged(ref _min, value);
} }
public string? Max public string? Max
{ {
get => _max; get => _max;
set => this.RaiseAndSetIfChanged(ref _max, value); set => RaiseAndSetIfChanged(ref _max, value);
} }
public string? Percentile public string? Percentile
{ {
get => _percentile; get => _percentile;
set => this.RaiseAndSetIfChanged(ref _percentile, value); set => RaiseAndSetIfChanged(ref _percentile, value);
} }
public void Update() public void Update()

View File

@ -66,25 +66,25 @@ namespace Artemis.UI.Screens.Debugger.Performance
public double CurrentFps public double CurrentFps
{ {
get => _currentFps; get => _currentFps;
set => this.RaiseAndSetIfChanged(ref _currentFps, value); set => RaiseAndSetIfChanged(ref _currentFps, value);
} }
public int RenderWidth public int RenderWidth
{ {
get => _renderWidth; get => _renderWidth;
set => this.RaiseAndSetIfChanged(ref _renderWidth, value); set => RaiseAndSetIfChanged(ref _renderWidth, value);
} }
public int RenderHeight public int RenderHeight
{ {
get => _renderHeight; get => _renderHeight;
set => this.RaiseAndSetIfChanged(ref _renderHeight, value); set => RaiseAndSetIfChanged(ref _renderHeight, value);
} }
public string? Renderer public string? Renderer
{ {
get => _renderer; get => _renderer;
set => this.RaiseAndSetIfChanged(ref _renderer, value); set => RaiseAndSetIfChanged(ref _renderer, value);
} }
private void HandleActivation() private void HandleActivation()

View File

@ -35,31 +35,31 @@ namespace Artemis.UI.Screens.Debugger.Render
public Bitmap? CurrentFrame public Bitmap? CurrentFrame
{ {
get => _currentFrame; get => _currentFrame;
set => this.RaiseAndSetIfChanged(ref _currentFrame, value); set => RaiseAndSetIfChanged(ref _currentFrame, value);
} }
public double CurrentFps public double CurrentFps
{ {
get => _currentFps; get => _currentFps;
set => this.RaiseAndSetIfChanged(ref _currentFps, value); set => RaiseAndSetIfChanged(ref _currentFps, value);
} }
public int RenderWidth public int RenderWidth
{ {
get => _renderWidth; get => _renderWidth;
set => this.RaiseAndSetIfChanged(ref _renderWidth, value); set => RaiseAndSetIfChanged(ref _renderWidth, value);
} }
public int RenderHeight public int RenderHeight
{ {
get => _renderHeight; get => _renderHeight;
set => this.RaiseAndSetIfChanged(ref _renderHeight, value); set => RaiseAndSetIfChanged(ref _renderHeight, value);
} }
public string? Renderer public string? Renderer
{ {
get => _renderer; get => _renderer;
set => this.RaiseAndSetIfChanged(ref _renderer, value); set => RaiseAndSetIfChanged(ref _renderer, value);
} }
private void HandleActivation() private void HandleActivation()

View File

@ -57,7 +57,7 @@ namespace Artemis.UI.Screens.Device
public bool TogglingDevice public bool TogglingDevice
{ {
get => _togglingDevice; get => _togglingDevice;
set => this.RaiseAndSetIfChanged(ref _togglingDevice, value); set => RaiseAndSetIfChanged(ref _togglingDevice, value);
} }
public void IdentifyDevice() public void IdentifyDevice()

View File

@ -60,7 +60,7 @@ namespace Artemis.UI.Screens.Device
get => _isSelected; get => _isSelected;
set set
{ {
if (!this.RaiseAndSetIfChanged(ref _isSelected, value)) if (!RaiseAndSetIfChanged(ref _isSelected, value))
return; return;
Apply(); Apply();
} }

View File

@ -84,55 +84,55 @@ namespace Artemis.UI.Screens.Device
public int X public int X
{ {
get => _x; get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value); set => RaiseAndSetIfChanged(ref _x, value);
} }
public int Y public int Y
{ {
get => _y; get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value); set => RaiseAndSetIfChanged(ref _y, value);
} }
public float Scale public float Scale
{ {
get => _scale; get => _scale;
set => this.RaiseAndSetIfChanged(ref _scale, value); set => RaiseAndSetIfChanged(ref _scale, value);
} }
public int Rotation public int Rotation
{ {
get => _rotation; get => _rotation;
set => this.RaiseAndSetIfChanged(ref _rotation, value); set => RaiseAndSetIfChanged(ref _rotation, value);
} }
public float RedScale public float RedScale
{ {
get => _redScale; get => _redScale;
set => this.RaiseAndSetIfChanged(ref _redScale, value); set => RaiseAndSetIfChanged(ref _redScale, value);
} }
public float GreenScale public float GreenScale
{ {
get => _greenScale; get => _greenScale;
set => this.RaiseAndSetIfChanged(ref _greenScale, value); set => RaiseAndSetIfChanged(ref _greenScale, value);
} }
public float BlueScale public float BlueScale
{ {
get => _blueScale; get => _blueScale;
set => this.RaiseAndSetIfChanged(ref _blueScale, value); set => RaiseAndSetIfChanged(ref _blueScale, value);
} }
public SKColor CurrentColor public SKColor CurrentColor
{ {
get => _currentColor; get => _currentColor;
set => this.RaiseAndSetIfChanged(ref _currentColor, value); set => RaiseAndSetIfChanged(ref _currentColor, value);
} }
public bool DisplayOnDevices public bool DisplayOnDevices
{ {
get => _displayOnDevices; get => _displayOnDevices;
set => this.RaiseAndSetIfChanged(ref _displayOnDevices, value); set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
} }
// This solution won't scale well but I don't expect there to be many more categories. // This solution won't scale well but I don't expect there to be many more categories.

View File

@ -37,7 +37,7 @@ namespace Artemis.UI.Screens.Device
public ArtemisLed? SelectedLed public ArtemisLed? SelectedLed
{ {
get => _selectedLed; get => _selectedLed;
set => this.RaiseAndSetIfChanged(ref _selectedLed, value); set => RaiseAndSetIfChanged(ref _selectedLed, value);
} }
public ObservableCollection<(ArtemisLed, ArtemisLed)> InputMappings { get; } public ObservableCollection<(ArtemisLed, ArtemisLed)> InputMappings { get; }

View File

@ -47,37 +47,37 @@ namespace Artemis.UI.Screens.Plugins
public PluginPrerequisiteViewModel? ActivePrerequisite public PluginPrerequisiteViewModel? ActivePrerequisite
{ {
get => _activePrerequisite; get => _activePrerequisite;
set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value); set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
} }
public bool ShowProgress public bool ShowProgress
{ {
get => _showProgress; get => _showProgress;
set => this.RaiseAndSetIfChanged(ref _showProgress, value); set => RaiseAndSetIfChanged(ref _showProgress, value);
} }
public bool ShowIntro public bool ShowIntro
{ {
get => _showIntro; get => _showIntro;
set => this.RaiseAndSetIfChanged(ref _showIntro, value); set => RaiseAndSetIfChanged(ref _showIntro, value);
} }
public bool ShowFailed public bool ShowFailed
{ {
get => _showFailed; get => _showFailed;
set => this.RaiseAndSetIfChanged(ref _showFailed, value); set => RaiseAndSetIfChanged(ref _showFailed, value);
} }
public bool ShowInstall public bool ShowInstall
{ {
get => _showInstall; get => _showInstall;
set => this.RaiseAndSetIfChanged(ref _showInstall, value); set => RaiseAndSetIfChanged(ref _showInstall, value);
} }
public bool CanInstall public bool CanInstall
{ {
get => _canInstall; get => _canInstall;
set => this.RaiseAndSetIfChanged(ref _canInstall, value); set => RaiseAndSetIfChanged(ref _canInstall, value);
} }
public async Task Install() public async Task Install()

View File

@ -55,19 +55,19 @@ namespace Artemis.UI.Screens.Plugins
public PluginPrerequisiteViewModel? ActivePrerequisite public PluginPrerequisiteViewModel? ActivePrerequisite
{ {
get => _activePrerequisite; get => _activePrerequisite;
set => this.RaiseAndSetIfChanged(ref _activePrerequisite, value); set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
} }
public bool CanUninstall public bool CanUninstall
{ {
get => _canUninstall; get => _canUninstall;
set => this.RaiseAndSetIfChanged(ref _canUninstall, value); set => RaiseAndSetIfChanged(ref _canUninstall, value);
} }
public bool IsFinished public bool IsFinished
{ {
get => _isFinished; get => _isFinished;
set => this.RaiseAndSetIfChanged(ref _isFinished, value); set => RaiseAndSetIfChanged(ref _isFinished, value);
} }
public async Task Uninstall() public async Task Uninstall()

View File

@ -66,7 +66,7 @@ namespace Artemis.UI.Screens.Plugins
public bool Enabling public bool Enabling
{ {
get => _enabling; get => _enabling;
set => this.RaiseAndSetIfChanged(ref _enabling, value); set => RaiseAndSetIfChanged(ref _enabling, value);
} }
public bool IsEnabled public bool IsEnabled

View File

@ -50,7 +50,7 @@ namespace Artemis.UI.Screens.Plugins
public PluginPrerequisiteActionViewModel? ActiveAction public PluginPrerequisiteActionViewModel? ActiveAction
{ {
get => _activeAction; get => _activeAction;
set => this.RaiseAndSetIfChanged(ref _activeAction, value); set => RaiseAndSetIfChanged(ref _activeAction, value);
} }
public PluginPrerequisite PluginPrerequisite { get; } public PluginPrerequisite PluginPrerequisite { get; }
@ -58,19 +58,19 @@ namespace Artemis.UI.Screens.Plugins
public bool Installing public bool Installing
{ {
get => _installing; get => _installing;
set => this.RaiseAndSetIfChanged(ref _installing, value); set => RaiseAndSetIfChanged(ref _installing, value);
} }
public bool Uninstalling public bool Uninstalling
{ {
get => _uninstalling; get => _uninstalling;
set => this.RaiseAndSetIfChanged(ref _uninstalling, value); set => RaiseAndSetIfChanged(ref _uninstalling, value);
} }
public bool IsMet public bool IsMet
{ {
get => _isMet; get => _isMet;
set => this.RaiseAndSetIfChanged(ref _isMet, value); set => RaiseAndSetIfChanged(ref _isMet, value);
} }
public bool Busy => _busy.Value; public bool Busy => _busy.Value;

View File

@ -78,13 +78,13 @@ namespace Artemis.UI.Screens.Plugins
public Plugin Plugin public Plugin Plugin
{ {
get => _plugin; get => _plugin;
set => this.RaiseAndSetIfChanged(ref _plugin, value); set => RaiseAndSetIfChanged(ref _plugin, value);
} }
public bool Enabling public bool Enabling
{ {
get => _enabling; get => _enabling;
set => this.RaiseAndSetIfChanged(ref _enabling, value); set => RaiseAndSetIfChanged(ref _enabling, value);
} }
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
@ -100,7 +100,7 @@ namespace Artemis.UI.Screens.Plugins
get => _isSettingsPopupOpen; get => _isSettingsPopupOpen;
set set
{ {
if (!this.RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return; if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites(); CheckPrerequisites();
} }
} }
@ -108,13 +108,13 @@ namespace Artemis.UI.Screens.Plugins
public bool CanInstallPrerequisites public bool CanInstallPrerequisites
{ {
get => _canInstallPrerequisites; get => _canInstallPrerequisites;
set => this.RaiseAndSetIfChanged(ref _canInstallPrerequisites, value); set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
} }
public bool CanRemovePrerequisites public bool CanRemovePrerequisites
{ {
get => _canRemovePrerequisites; get => _canRemovePrerequisites;
set => this.RaiseAndSetIfChanged(ref _canRemovePrerequisites, value); set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
} }
private void ExecuteOpenSettings() private void ExecuteOpenSettings()

View File

@ -18,6 +18,6 @@ public class MenuBarViewModel : ActivatableViewModelBase
public ProfileEditorHistory? History public ProfileEditorHistory? History
{ {
get => _history; get => _history;
set => this.RaiseAndSetIfChanged(ref _history, value); set => RaiseAndSetIfChanged(ref _history, value);
} }
} }

View File

@ -50,19 +50,19 @@ public class PlaybackViewModel : ActivatableViewModelBase
public bool Repeating public bool Repeating
{ {
get => _repeating; get => _repeating;
set => this.RaiseAndSetIfChanged(ref _repeating, value); set => RaiseAndSetIfChanged(ref _repeating, value);
} }
public bool RepeatTimeline public bool RepeatTimeline
{ {
get => _repeatTimeline; get => _repeatTimeline;
set => this.RaiseAndSetIfChanged(ref _repeatTimeline, value); set => RaiseAndSetIfChanged(ref _repeatTimeline, value);
} }
public bool RepeatSegment public bool RepeatSegment
{ {
get => _repeatSegment; get => _repeatSegment;
set => this.RaiseAndSetIfChanged(ref _repeatSegment, value); set => RaiseAndSetIfChanged(ref _repeatSegment, value);
} }
public void PlayFromStart() public void PlayFromStart()

View File

@ -48,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
public TreeItemViewModel? SelectedChild public TreeItemViewModel? SelectedChild
{ {
get => _selectedChild; get => _selectedChild;
set => this.RaiseAndSetIfChanged(ref _selectedChild, value); set => RaiseAndSetIfChanged(ref _selectedChild, value);
} }
private void SelectCurrentProfileElement(RenderProfileElement? element) private void SelectCurrentProfileElement(RenderProfileElement? element)

View File

@ -89,19 +89,19 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
public ProfileElement? ProfileElement public ProfileElement? ProfileElement
{ {
get => _profileElement; get => _profileElement;
set => this.RaiseAndSetIfChanged(ref _profileElement, value); set => RaiseAndSetIfChanged(ref _profileElement, value);
} }
public bool IsExpanded public bool IsExpanded
{ {
get => _isExpanded; get => _isExpanded;
set => this.RaiseAndSetIfChanged(ref _isExpanded, value); set => RaiseAndSetIfChanged(ref _isExpanded, value);
} }
public bool Renaming public bool Renaming
{ {
get => _renaming; get => _renaming;
set => this.RaiseAndSetIfChanged(ref _renaming, value); set => RaiseAndSetIfChanged(ref _renaming, value);
} }
public TreeItemViewModel? Parent { get; set; } public TreeItemViewModel? Parent { get; set; }
@ -118,7 +118,7 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree
public string? RenameValue public string? RenameValue
{ {
get => _renameValue; get => _renameValue;
set => this.RaiseAndSetIfChanged(ref _renameValue, value); set => RaiseAndSetIfChanged(ref _renameValue, value);
} }
public async Task ShowBrokenStateExceptions() public async Task ShowBrokenStateExceptions()

View File

@ -64,19 +64,19 @@ public class PropertyGroupViewModel : ViewModelBase
public bool IsVisible public bool IsVisible
{ {
get => _isVisible; get => _isVisible;
set => this.RaiseAndSetIfChanged(ref _isVisible, value); set => RaiseAndSetIfChanged(ref _isVisible, value);
} }
public bool IsExpanded public bool IsExpanded
{ {
get => _isExpanded; get => _isExpanded;
set => this.RaiseAndSetIfChanged(ref _isExpanded, value); set => RaiseAndSetIfChanged(ref _isExpanded, value);
} }
public bool HasChildren public bool HasChildren
{ {
get => _hasChildren; get => _hasChildren;
set => this.RaiseAndSetIfChanged(ref _hasChildren, value); set => RaiseAndSetIfChanged(ref _hasChildren, value);
} }
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly) public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly)

View File

@ -30,18 +30,18 @@ public class PropertyViewModel : ViewModelBase
public bool IsVisible public bool IsVisible
{ {
get => _isVisible; get => _isVisible;
set => this.RaiseAndSetIfChanged(ref _isVisible, value); set => RaiseAndSetIfChanged(ref _isVisible, value);
} }
public bool IsHighlighted public bool IsHighlighted
{ {
get => _isHighlighted; get => _isHighlighted;
set => this.RaiseAndSetIfChanged(ref _isHighlighted, value); set => RaiseAndSetIfChanged(ref _isHighlighted, value);
} }
public bool IsExpanded public bool IsExpanded
{ {
get => _isExpanded; get => _isExpanded;
set => this.RaiseAndSetIfChanged(ref _isExpanded, value); set => RaiseAndSetIfChanged(ref _isExpanded, value);
} }
} }

View File

@ -47,13 +47,13 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
public double X public double X
{ {
get => _x; get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value); set => RaiseAndSetIfChanged(ref _x, value);
} }
public string Timestamp public string Timestamp
{ {
get => _timestamp; get => _timestamp;
set => this.RaiseAndSetIfChanged(ref _timestamp, value); set => RaiseAndSetIfChanged(ref _timestamp, value);
} }
public bool IsSelected => _isSelected?.Value ?? false; public bool IsSelected => _isSelected?.Value ?? false;

View File

@ -36,7 +36,7 @@ public class TimelineGroupViewModel : ActivatableViewModelBase
public ObservableCollection<double>? KeyframePositions public ObservableCollection<double>? KeyframePositions
{ {
get => _keyframePositions; get => _keyframePositions;
set => this.RaiseAndSetIfChanged(ref _keyframePositions, value); set => RaiseAndSetIfChanged(ref _keyframePositions, value);
} }
private void UpdateKeyframePositions() private void UpdateKeyframePositions()

View File

@ -52,12 +52,12 @@ public class StatusBarViewModel : ActivatableViewModelBase
public string? StatusMessage public string? StatusMessage
{ {
get => _statusMessage; get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value); set => RaiseAndSetIfChanged(ref _statusMessage, value);
} }
public bool ShowStatusMessage public bool ShowStatusMessage
{ {
get => _showStatusMessage; get => _showStatusMessage;
set => this.RaiseAndSetIfChanged(ref _showStatusMessage, value); set => RaiseAndSetIfChanged(ref _showStatusMessage, value);
} }
} }

View File

@ -2,7 +2,160 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tools="clr-namespace:Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.TransformToolView"> x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.TransformToolView"
Welcome to Avalonia! x:DataType="tools:TransformToolViewModel">
<UserControl.Styles>
<Style Selector="Ellipse.rotation-handle">
<Setter Property="Margin" Value="-15"></Setter>
<Setter Property="Width" Value="30"></Setter>
<Setter Property="Height" Value="30"></Setter>
<Setter Property="Fill" Value="Green"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
</Style>
<Style Selector="Ellipse.anchor-handle">
<Setter Property="Margin" Value="-15"></Setter>
<Setter Property="Width" Value="30"></Setter>
<Setter Property="Height" Value="30"></Setter>
<Setter Property="Fill" Value="Red"></Setter>
<Setter Property="Cursor" Value="SizeAll"></Setter>
</Style>
<Style Selector="Rectangle.resize-handle">
<Setter Property="Margin" Value="-6"></Setter>
<Setter Property="Width" Value="12"></Setter>
<Setter Property="Height" Value="12"></Setter>
<Setter Property="Fill" Value="Blue"></Setter>
<Setter Property="Cursor" Value="Hand"></Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="{CompiledBinding InverseRotation}"></RotateTransform>
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
<Canvas ClipToBounds="False" UseLayoutRounding="False">
<Border Name="TransformationContainer"
Width="{CompiledBinding ShapeBounds.Width}"
Height="{CompiledBinding ShapeBounds.Height}"
ClipToBounds="False"
Canvas.Left="{CompiledBinding ShapeBounds.Left}"
Canvas.Top="{CompiledBinding ShapeBounds.Top}"
RenderTransformOrigin="{CompiledBinding RelativeAnchor}">
<Border.RenderTransform>
<RotateTransform Angle="{CompiledBinding Rotation}"></RotateTransform>
</Border.RenderTransform>
<Grid>
<!-- Render these first so that they are always behind the actual shape -->
<Ellipse Name="RotateTopLeft"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Classes="rotation-handle"
PointerPressed="RotationOnPointerPressed"
PointerReleased="RotationOnPointerReleased"
PointerMoved="RotationOnPointerMoved" />
<Ellipse Name="RotateTopRight"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Classes="rotation-handle"
PointerPressed="RotationOnPointerPressed"
PointerReleased="RotationOnPointerReleased"
PointerMoved="RotationOnPointerMoved" />
<Ellipse Name="RotateBottomRight"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Classes="rotation-handle"
PointerPressed="RotationOnPointerPressed"
PointerReleased="RotationOnPointerReleased"
PointerMoved="RotationOnPointerMoved" />
<Ellipse Name="RotateBottomLeft"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Classes="rotation-handle"
PointerPressed="RotationOnPointerPressed"
PointerReleased="RotationOnPointerReleased"
PointerMoved="RotationOnPointerMoved" />
<!-- Mutation points -->
<Rectangle Name="ResizeTopCenter"
VerticalAlignment="Top"
HorizontalAlignment="Center"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeRightCenter"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeBottomCenter"
VerticalAlignment="Bottom"
HorizontalAlignment="Center"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeLeftCenter"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeTopLeft"
Classes="resize-handle"
VerticalAlignment="Top"
HorizontalAlignment="Left"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeTopRight"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeBottomRight"
VerticalAlignment="Bottom"
HorizontalAlignment="Right"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Rectangle Name="ResizeBottomLeft"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Classes="resize-handle"
PointerPressed="ResizeOnPointerPressed"
PointerReleased="ResizeOnPointerReleased"
PointerMoved="ResizeOnPointerMoved">
</Rectangle>
<Canvas>
<Ellipse Name="AnchorPoint"
Canvas.Left="{CompiledBinding Anchor.X}"
Canvas.Top="{CompiledBinding Anchor.Y}"
Classes="anchor-handle"
PointerPressed="MoveOnPointerPressed"
PointerReleased="MoveOnPointerReleased"
PointerMoved="MoveOnPointerMoved" />
</Canvas>
</Grid>
</Border>
</Canvas>
</UserControl> </UserControl>

View File

@ -1,10 +1,20 @@
using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class TransformToolView : ReactiveUserControl<TransformToolViewModel> public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{ {
private ZoomBorder? _zoomBorder;
private PointerPoint _dragOffset;
public TransformToolView() public TransformToolView()
{ {
InitializeComponent(); InitializeComponent();
@ -14,4 +24,138 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
#region Zoom
/// <inheritdoc />
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_zoomBorder = (ZoomBorder?)this.GetLogicalAncestors().FirstOrDefault(l => l is ZoomBorder);
if (_zoomBorder != null)
_zoomBorder.PropertyChanged += ZoomBorderOnPropertyChanged;
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc />
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_zoomBorder != null)
_zoomBorder.PropertyChanged -= ZoomBorderOnPropertyChanged;
base.OnDetachedFromLogicalTree(e);
}
private void ZoomBorderOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != ZoomBorder.ZoomXProperty || _zoomBorder == null)
return;
// TODO
}
#endregion
#region Rotation
private void RotationOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
_dragOffset = e.GetCurrentPoint(_zoomBorder);
e.Pointer.Capture(element);
e.Handled = true;
}
private void RotationOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Pointer.Capture(null);
e.Handled = true;
}
private void RotationOnPointerMoved(object? sender, PointerEventArgs e)
{
IInputElement? element = (IInputElement?) sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Handled = true;
}
#endregion
#region Movement
private void MoveOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
_dragOffset = e.GetCurrentPoint(_zoomBorder);
e.Pointer.Capture(element);
e.Handled = true;
}
private void MoveOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Pointer.Capture(null);
e.Handled = true;
}
private void MoveOnPointerMoved(object? sender, PointerEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Handled = true;
}
#endregion
#region Resizing
private void ResizeOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
_dragOffset = e.GetCurrentPoint(_zoomBorder);
e.Pointer.Capture(element);
e.Handled = true;
}
private void ResizeOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Pointer.Capture(null);
e.Handled = true;
}
private void ResizeOnPointerMoved(object? sender, PointerEventArgs e)
{
IInputElement? element = (IInputElement?)sender;
if (element == null || e.Pointer.Captured != element || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Handled = true;
}
#endregion
} }

View File

@ -1,21 +1,80 @@
using System.Reactive.Linq; using System;
using System.Reactive;
using System.Reactive.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.ProfileEditor; using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using Material.Icons; using Material.Icons;
using ReactiveUI; using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class TransformToolViewModel : ToolViewModel public class TransformToolViewModel : ToolViewModel
{ {
private readonly ObservableAsPropertyHelper<bool>? _isEnabled; private readonly ObservableAsPropertyHelper<bool> _isEnabled;
private RelativePoint _relativeAnchor;
private double _inverseRotation;
private ObservableAsPropertyHelper<Layer?>? _layer;
private double _rotation;
private Rect _shapeBounds;
private Point _anchor;
/// <inheritdoc /> /// <inheritdoc />
public TransformToolViewModel(IProfileEditorService profileEditorService) public TransformToolViewModel(IProfileEditorService profileEditorService)
{ {
// Not disposed when deactivated but when really disposed
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
this.WhenActivated(d =>
{
_layer = profileEditorService.ProfileElement.Select(p => p as Layer).ToProperty(this, vm => vm.Layer).DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer)
.Select(l => l != null
? Observable.FromEventPattern(x => l.RenderPropertiesUpdated += x, x => l.RenderPropertiesUpdated -= x)
: Observable.Never<EventPattern<object>>())
.Switch()
.Subscribe(_ => Update())
.DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer)
.Select(l => l != null
? Observable.FromEventPattern<LayerPropertyEventArgs>(x => l.Transform.Position.CurrentValueSet += x, x => l.Transform.Position.CurrentValueSet -= x)
: Observable.Never<EventPattern<LayerPropertyEventArgs>>())
.Switch()
.Subscribe(_ => Update())
.DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer)
.Select(l => l != null
? Observable.FromEventPattern<LayerPropertyEventArgs>(x => l.Transform.Rotation.CurrentValueSet += x, x => l.Transform.Rotation.CurrentValueSet -= x)
: Observable.Never<EventPattern<LayerPropertyEventArgs>>())
.Switch()
.Subscribe(_ => Update())
.DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer)
.Select(l => l != null
? Observable.FromEventPattern<LayerPropertyEventArgs>(x => l.Transform.Scale.CurrentValueSet += x, x => l.Transform.Scale.CurrentValueSet -= x)
: Observable.Never<EventPattern<LayerPropertyEventArgs>>())
.Switch()
.Subscribe(_ => Update())
.DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer)
.Select(l => l != null
? Observable.FromEventPattern<LayerPropertyEventArgs>(x => l.Transform.AnchorPoint.CurrentValueSet += x, x => l.Transform.AnchorPoint.CurrentValueSet -= x)
: Observable.Never<EventPattern<LayerPropertyEventArgs>>())
.Switch()
.Subscribe(_ => Update())
.DisposeWith(d);
this.WhenAnyValue(vm => vm.Layer).Subscribe(_ => Update()).DisposeWith(d);
profileEditorService.Time.Subscribe(_ => Update()).DisposeWith(d);
});
} }
public Layer? Layer => _layer?.Value;
/// <inheritdoc /> /// <inheritdoc />
public override bool IsEnabled => _isEnabled?.Value ?? false; public override bool IsEnabled => _isEnabled?.Value ?? false;
@ -34,6 +93,36 @@ public class TransformToolViewModel : ToolViewModel
/// <inheritdoc /> /// <inheritdoc />
public override string ToolTip => "Transform the shape of the current layer"; public override string ToolTip => "Transform the shape of the current layer";
public Rect ShapeBounds
{
get => _shapeBounds;
set => RaiseAndSetIfChanged(ref _shapeBounds, value);
}
public double Rotation
{
get => _rotation;
set => RaiseAndSetIfChanged(ref _rotation, value);
}
public double InverseRotation
{
get => _inverseRotation;
set => RaiseAndSetIfChanged(ref _inverseRotation, value);
}
public RelativePoint RelativeAnchor
{
get => _relativeAnchor;
set => RaiseAndSetIfChanged(ref _relativeAnchor, value);
}
public Point Anchor
{
get => _anchor;
set => RaiseAndSetIfChanged(ref _anchor, value);
}
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
@ -42,4 +131,37 @@ public class TransformToolViewModel : ToolViewModel
base.Dispose(disposing); base.Dispose(disposing);
} }
private void Update()
{
if (Layer == null)
return;
SKPath path = new();
path.AddRect(Layer.GetLayerBounds());
path.Transform(Layer.GetTransformMatrix(false, true, true, false));
ShapeBounds = path.Bounds.ToRect();
Rotation = Layer.Transform.Rotation.CurrentValue;
InverseRotation = Rotation * -1;
// The middle of the element is 0.5/0.5 in Avalonia, in Artemis it's 0.0/0.0 so compensate for that below
SKPoint layerAnchor = Layer.Transform.AnchorPoint.CurrentValue;
RelativeAnchor = new RelativePoint(layerAnchor.X + 0.5, layerAnchor.Y + 0.5, RelativeUnit.Relative);
Anchor = new Point(ShapeBounds.Width * RelativeAnchor.Point.X, ShapeBounds.Height * RelativeAnchor.Point.Y);
}
public enum ShapeControlPoint
{
TopLeft,
TopRight,
BottomRight,
BottomLeft,
TopCenter,
RightCenter,
BottomCenter,
LeftCenter,
LayerShape,
Anchor
}
} }

View File

@ -51,7 +51,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase
public ReadOnlyObservableCollection<IToolViewModel> Tools public ReadOnlyObservableCollection<IToolViewModel> Tools
{ {
get => _tools; get => _tools;
set => this.RaiseAndSetIfChanged(ref _tools, value); set => RaiseAndSetIfChanged(ref _tools, value);
} }
private void CreateVisualizers(ProfileConfiguration? profileConfiguration) private void CreateVisualizers(ProfileConfiguration? profileConfiguration)

View File

@ -54,25 +54,25 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
public Rect LayerBounds public Rect LayerBounds
{ {
get => _layerBounds; get => _layerBounds;
private set => this.RaiseAndSetIfChanged(ref _layerBounds, value); private set => RaiseAndSetIfChanged(ref _layerBounds, value);
} }
public double X public double X
{ {
get => _x; get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value); set => RaiseAndSetIfChanged(ref _x, value);
} }
public double Y public double Y
{ {
get => _y; get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value); set => RaiseAndSetIfChanged(ref _y, value);
} }
public Geometry? ShapeGeometry public Geometry? ShapeGeometry
{ {
get => _shapeGeometry; get => _shapeGeometry;
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value); set => RaiseAndSetIfChanged(ref _shapeGeometry, value);
} }
public int Order => 2; public int Order => 2;

View File

@ -40,19 +40,19 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
public Rect LayerBounds public Rect LayerBounds
{ {
get => _layerBounds; get => _layerBounds;
private set => this.RaiseAndSetIfChanged(ref _layerBounds, value); private set => RaiseAndSetIfChanged(ref _layerBounds, value);
} }
public double X public double X
{ {
get => _x; get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value); set => RaiseAndSetIfChanged(ref _x, value);
} }
public double Y public double Y
{ {
get => _y; get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value); set => RaiseAndSetIfChanged(ref _y, value);
} }
public int Order => 1; public int Order => 1;

View File

@ -70,7 +70,7 @@ namespace Artemis.UI.Screens.ProfileEditor
public ReadOnlyObservableCollection<IToolViewModel> Tools public ReadOnlyObservableCollection<IToolViewModel> Tools
{ {
get => _tools; get => _tools;
set => this.RaiseAndSetIfChanged(ref _tools, value); set => RaiseAndSetIfChanged(ref _tools, value);
} }
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value; public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;

View File

@ -77,13 +77,13 @@ namespace Artemis.UI.Screens.Root
public SidebarViewModel? SidebarViewModel public SidebarViewModel? SidebarViewModel
{ {
get => _sidebarViewModel; get => _sidebarViewModel;
set => this.RaiseAndSetIfChanged(ref _sidebarViewModel, value); set => RaiseAndSetIfChanged(ref _sidebarViewModel, value);
} }
public ViewModelBase? TitleBarViewModel public ViewModelBase? TitleBarViewModel
{ {
get => _titleBarViewModel; get => _titleBarViewModel;
set => this.RaiseAndSetIfChanged(ref _titleBarViewModel, value); set => RaiseAndSetIfChanged(ref _titleBarViewModel, value);
} }
private void CurrentMainWindowOnClosed(object? sender, EventArgs e) private void CurrentMainWindowOnClosed(object? sender, EventArgs e)

View File

@ -30,7 +30,7 @@ namespace Artemis.UI.Screens.Root
public string Status public string Status
{ {
get => _status; get => _status;
set => this.RaiseAndSetIfChanged(ref _status, value); set => RaiseAndSetIfChanged(ref _status, value);
} }
private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args) private void OnPluginManagementServiceOnPluginManagementLoaded(object? sender, PluginEventArgs args)

View File

@ -27,31 +27,31 @@ namespace Artemis.UI.Screens.Settings
public string? Version public string? Version
{ {
get => _version; get => _version;
set => this.RaiseAndSetIfChanged(ref _version, value); set => RaiseAndSetIfChanged(ref _version, value);
} }
public Bitmap? RobertProfileImage public Bitmap? RobertProfileImage
{ {
get => _robertProfileImage; get => _robertProfileImage;
set => this.RaiseAndSetIfChanged(ref _robertProfileImage, value); set => RaiseAndSetIfChanged(ref _robertProfileImage, value);
} }
public Bitmap? DarthAffeProfileImage public Bitmap? DarthAffeProfileImage
{ {
get => _darthAffeProfileImage; get => _darthAffeProfileImage;
set => this.RaiseAndSetIfChanged(ref _darthAffeProfileImage, value); set => RaiseAndSetIfChanged(ref _darthAffeProfileImage, value);
} }
public Bitmap? DrMeteorProfileImage public Bitmap? DrMeteorProfileImage
{ {
get => _drMeteorProfileImage; get => _drMeteorProfileImage;
set => this.RaiseAndSetIfChanged(ref _drMeteorProfileImage, value); set => RaiseAndSetIfChanged(ref _drMeteorProfileImage, value);
} }
public Bitmap? KaiProfileImage public Bitmap? KaiProfileImage
{ {
get => _kaiProfileImage; get => _kaiProfileImage;
set => this.RaiseAndSetIfChanged(ref _kaiProfileImage, value); set => RaiseAndSetIfChanged(ref _kaiProfileImage, value);
} }
private async Task Activate() private async Task Activate()

View File

@ -46,7 +46,7 @@ namespace Artemis.UI.Screens.Settings
public string? SearchPluginInput public string? SearchPluginInput
{ {
get => _searchPluginInput; get => _searchPluginInput;
set => this.RaiseAndSetIfChanged(ref _searchPluginInput, value); set => RaiseAndSetIfChanged(ref _searchPluginInput, value);
} }
public void OpenUrl(string url) public void OpenUrl(string url)

View File

@ -64,31 +64,31 @@ namespace Artemis.UI.Screens.Sidebar
public ProfileConfiguration ProfileConfiguration public ProfileConfiguration ProfileConfiguration
{ {
get => _profileConfiguration; get => _profileConfiguration;
set => this.RaiseAndSetIfChanged(ref _profileConfiguration, value); set => RaiseAndSetIfChanged(ref _profileConfiguration, value);
} }
public string ProfileName public string ProfileName
{ {
get => _profileName; get => _profileName;
set => this.RaiseAndSetIfChanged(ref _profileName, value); set => RaiseAndSetIfChanged(ref _profileName, value);
} }
public ProfileConfigurationHotkeyMode HotkeyMode public ProfileConfigurationHotkeyMode HotkeyMode
{ {
get => _hotkeyMode; get => _hotkeyMode;
set => this.RaiseAndSetIfChanged(ref _hotkeyMode, value); set => RaiseAndSetIfChanged(ref _hotkeyMode, value);
} }
public Hotkey? EnableHotkey public Hotkey? EnableHotkey
{ {
get => _enableHotkey; get => _enableHotkey;
set => this.RaiseAndSetIfChanged(ref _enableHotkey, value); set => RaiseAndSetIfChanged(ref _enableHotkey, value);
} }
public Hotkey? DisableHotkey public Hotkey? DisableHotkey
{ {
get => _disableHotkey; get => _disableHotkey;
set => this.RaiseAndSetIfChanged(ref _disableHotkey, value); set => RaiseAndSetIfChanged(ref _disableHotkey, value);
} }
public ObservableCollection<ProfileModuleViewModel> Modules { get; } public ObservableCollection<ProfileModuleViewModel> Modules { get; }
@ -96,7 +96,7 @@ namespace Artemis.UI.Screens.Sidebar
public ProfileModuleViewModel? SelectedModule public ProfileModuleViewModel? SelectedModule
{ {
get => _selectedModule; get => _selectedModule;
set => this.RaiseAndSetIfChanged(ref _selectedModule, value); set => RaiseAndSetIfChanged(ref _selectedModule, value);
} }
public async Task Import() public async Task Import()
@ -183,31 +183,31 @@ namespace Artemis.UI.Screens.Sidebar
public ProfileConfigurationIconType IconType public ProfileConfigurationIconType IconType
{ {
get => _iconType; get => _iconType;
set => this.RaiseAndSetIfChanged(ref _iconType, value); set => RaiseAndSetIfChanged(ref _iconType, value);
} }
public ObservableCollection<ProfileIconViewModel>? MaterialIcons public ObservableCollection<ProfileIconViewModel>? MaterialIcons
{ {
get => _materialIcons; get => _materialIcons;
set => this.RaiseAndSetIfChanged(ref _materialIcons, value); set => RaiseAndSetIfChanged(ref _materialIcons, value);
} }
public ProfileIconViewModel? SelectedMaterialIcon public ProfileIconViewModel? SelectedMaterialIcon
{ {
get => _selectedMaterialIcon; get => _selectedMaterialIcon;
set => this.RaiseAndSetIfChanged(ref _selectedMaterialIcon, value); set => RaiseAndSetIfChanged(ref _selectedMaterialIcon, value);
} }
public Bitmap? SelectedBitmapSource public Bitmap? SelectedBitmapSource
{ {
get => _selectedBitmapSource; get => _selectedBitmapSource;
set => this.RaiseAndSetIfChanged(ref _selectedBitmapSource, value); set => RaiseAndSetIfChanged(ref _selectedBitmapSource, value);
} }
public SvgImage? SelectedSvgSource public SvgImage? SelectedSvgSource
{ {
get => _selectedSvgSource; get => _selectedSvgSource;
set => this.RaiseAndSetIfChanged(ref _selectedSvgSource, value); set => RaiseAndSetIfChanged(ref _selectedSvgSource, value);
} }
private void LoadIcon() private void LoadIcon()

View File

@ -53,7 +53,7 @@ namespace Artemis.UI.Screens.Sidebar
public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration public SidebarProfileConfigurationViewModel? SelectedProfileConfiguration
{ {
get => _selectedProfileConfiguration; get => _selectedProfileConfiguration;
set => this.RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value); set => RaiseAndSetIfChanged(ref _selectedProfileConfiguration, value);
} }
public bool ShowItems public bool ShowItems

View File

@ -89,13 +89,13 @@ namespace Artemis.UI.Screens.Sidebar
public ArtemisDevice? HeaderDevice public ArtemisDevice? HeaderDevice
{ {
get => _headerDevice; get => _headerDevice;
set => this.RaiseAndSetIfChanged(ref _headerDevice, value); set => RaiseAndSetIfChanged(ref _headerDevice, value);
} }
public SidebarScreenViewModel? SelectedSidebarScreen public SidebarScreenViewModel? SelectedSidebarScreen
{ {
get => _selectedSidebarScreen; get => _selectedSidebarScreen;
set => this.RaiseAndSetIfChanged(ref _selectedSidebarScreen, value); set => RaiseAndSetIfChanged(ref _selectedSidebarScreen, value);
} }
public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory) public SidebarCategoryViewModel AddProfileCategoryViewModel(ProfileCategory profileCategory)

View File

@ -20,13 +20,13 @@ namespace Artemis.UI.Screens.SurfaceEditor
public bool IsSelected public bool IsSelected
{ {
get => _isSelected; get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value); set => RaiseAndSetIfChanged(ref _isSelected, value);
} }
public SKColor Color public SKColor Color
{ {
get => _color; get => _color;
set => this.RaiseAndSetIfChanged(ref _color, value); set => RaiseAndSetIfChanged(ref _color, value);
} }
} }
} }

View File

@ -53,7 +53,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
get => _selectionStatus; get => _selectionStatus;
set set
{ {
this.RaiseAndSetIfChanged(ref _selectionStatus, value); RaiseAndSetIfChanged(ref _selectionStatus, value);
this.RaisePropertyChanged(nameof(Highlighted)); this.RaisePropertyChanged(nameof(Highlighted));
} }
} }
@ -65,7 +65,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
public Cursor Cursor public Cursor Cursor
{ {
get => _cursor; get => _cursor;
set => this.RaiseAndSetIfChanged(ref _cursor, value); set => RaiseAndSetIfChanged(ref _cursor, value);
} }
public void StartMouseDrag(Point mouseStartPosition) public void StartMouseDrag(Point mouseStartPosition)

View File

@ -6,24 +6,26 @@
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared" xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared" xmlns:attachedProperties="clr-namespace:Artemis.UI.Shared.AttachedProperties;assembly=Artemis.UI.Shared"
xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"> x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
x:DataType="workshop:WorkshopViewModel">
<Border Classes="router-container"> <Border Classes="router-container">
<StackPanel Margin="12"> <StackPanel Margin="12">
<TextBlock>Workshop!! :3</TextBlock> <TextBlock>Workshop!! :3</TextBlock>
<Border Classes="card" Margin="0 12"> <Border Classes="card" Margin="0 12">
<StackPanel Spacing="5"> <StackPanel Spacing="5">
<TextBlock Classes="h4">Notification tests</TextBlock> <TextBlock Classes="h4">Notification tests</TextBlock>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}"> <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Informational}">
Notification test (default) Notification test (default)
</Button> </Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}"> <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
Notification test (warning) Notification test (warning)
</Button> </Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}"> <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
Notification test (error) Notification test (error)
</Button> </Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}"> <Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
Notification test (success) Notification test (success)
</Button> </Button>
@ -36,6 +38,13 @@
<TextBox <TextBox
attachedProperties:TextBoxAssist.PrefixText="$" attachedProperties:TextBoxAssist.PrefixText="$"
attachedProperties:TextBoxAssist.SuffixText="%"></TextBox> attachedProperties:TextBoxAssist.SuffixText="%"></TextBox>
<StackPanel Orientation="Horizontal" Spacing="5">
<Border Classes="card" Cursor="{CompiledBinding Cursor}">
<TextBlock Text="{CompiledBinding SelectedCursor}"></TextBlock>
</Border>
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
</StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>

View File

@ -1,6 +1,8 @@
using System.Reactive; using System.Reactive;
using System.Reactive.Linq;
using Artemis.UI.Shared.Services.Builders; using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Input;
using ReactiveUI; using ReactiveUI;
namespace Artemis.UI.Screens.Workshop namespace Artemis.UI.Screens.Workshop
@ -8,10 +10,13 @@ namespace Artemis.UI.Screens.Workshop
public class WorkshopViewModel : MainScreenViewModel public class WorkshopViewModel : MainScreenViewModel
{ {
private readonly INotificationService _notificationService; private readonly INotificationService _notificationService;
private StandardCursorType _selectedCursor;
private readonly ObservableAsPropertyHelper<Cursor> _cursor;
public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop") public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop")
{ {
_notificationService = notificationService; _notificationService = notificationService;
_cursor = this.WhenAnyValue(vm => vm.SelectedCursor).Select(c => new Cursor(c)).ToProperty(this, vm => vm.Cursor);
DisplayName = "Workshop"; DisplayName = "Workshop";
ShowNotification = ReactiveCommand.Create<NotificationSeverity>(ExecuteShowNotification); ShowNotification = ReactiveCommand.Create<NotificationSeverity>(ExecuteShowNotification);
@ -19,6 +24,14 @@ namespace Artemis.UI.Screens.Workshop
public ReactiveCommand<NotificationSeverity, Unit> ShowNotification { get; set; } public ReactiveCommand<NotificationSeverity, Unit> ShowNotification { get; set; }
public StandardCursorType SelectedCursor
{
get => _selectedCursor;
set => RaiseAndSetIfChanged(ref _selectedCursor, value);
}
public Cursor Cursor => _cursor.Value;
private void ExecuteShowNotification(NotificationSeverity severity) private void ExecuteShowNotification(NotificationSeverity severity)
{ {
_notificationService.CreateNotification().WithTitle("Test title").WithMessage("Test message").WithSeverity(severity).Show(); _notificationService.CreateNotification().WithTitle("Test title").WithMessage("Test message").WithSeverity(severity).Show();