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

View File

@ -28,7 +28,7 @@ namespace Artemis.UI.Shared.DefaultTypes.DataModel.Display
public string Display
{
get => _display;
set => this.RaiseAndSetIfChanged(ref _display, value);
set => RaiseAndSetIfChanged(ref _display, value);
}
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
{
get => _script;
internal set => this.RaiseAndSetIfChanged(ref _script, value);
internal set => RaiseAndSetIfChanged(ref _script, value);
}
/// <inheritdoc />

View File

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

View File

@ -1,118 +1,142 @@
using System;
using System.Reactive.Disposables;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Artemis.UI.Shared.Events;
using FluentAvalonia.UI.Controls;
using JetBrains.Annotations;
using ReactiveUI;
using ReactiveUI.Validation.Helpers;
namespace Artemis.UI.Shared
namespace Artemis.UI.Shared;
/// <summary>
/// Represents the base class for Artemis view models
/// </summary>
public abstract class ContentDialogViewModelBase : ReactiveValidationObject, IActivatableViewModel, IDisposable
{
/// <summary>
/// Represents the base class for Artemis view models
/// Gets the content dialog that hosts the view model
/// </summary>
public abstract class ContentDialogViewModelBase : ReactiveValidationObject, IActivatableViewModel, IDisposable
public ContentDialog? ContentDialog { get; internal set; }
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// Gets the content dialog that hosts the view model
/// </summary>
public ContentDialog? ContentDialog { get; internal set; }
}
#region Implementation of IActivatableViewModel
#region Implementation of IActivatableViewModel
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
#endregion
#endregion
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
}
/// <summary>
/// Represents the base class for Artemis view models
/// </summary>
public abstract class ViewModelValidationBase : ReactiveValidationObject
{
private string? _displayName;
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
}
#endregion
/// <summary>
/// Represents the base class for Artemis view models
/// </summary>
public abstract class ViewModelBase : ReactiveObject
{
private string? _displayName;
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => RaiseAndSetIfChanged(ref _displayName, value);
}
/// <summary>
/// Represents the base class for Artemis view models
/// 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>
public abstract class ViewModelValidationBase : ReactiveValidationObject
/// <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)
{
private string? _displayName;
if (propertyName is null)
throw new ArgumentNullException(nameof(propertyName));
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
if (EqualityComparer<TRet>.Default.Equals(backingField, newValue))
return newValue;
this.RaisePropertyChanging(propertyName);
backingField = newValue;
this.RaisePropertyChanged(propertyName);
return newValue;
}
}
/// <summary>
/// Represents the base class for Artemis view models that are interested in the activated event
/// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel
{
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
}
/// <summary>
/// Represents the base class for Artemis view models used to drive dialogs
/// </summary>
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
{
/// <summary>
/// Closes the dialog with the given <paramref name="result" />
/// </summary>
/// <param name="result">The result of the dialog</param>
public void Close(TResult result)
{
CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result));
}
/// <summary>
/// Represents the base class for Artemis view models
/// Closes the dialog without a result
/// </summary>
public abstract class ViewModelBase : ReactiveObject
public void Cancel()
{
private string? _displayName;
/// <summary>
/// Gets or sets the display name of the view model
/// </summary>
public string? DisplayName
{
get => _displayName;
set => this.RaiseAndSetIfChanged(ref _displayName, value);
}
CancelRequested?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Represents the base class for Artemis view models that are interested in the activated event
/// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel
{
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
}
/// <summary>
/// Represents the base class for Artemis view models used to drive dialogs
/// </summary>
public abstract class DialogViewModelBase<TResult> : ActivatableViewModelBase
{
/// <summary>
/// Closes the dialog with the given <paramref name="result" />
/// </summary>
/// <param name="result">The result of the dialog</param>
public void Close(TResult result)
{
CloseRequested?.Invoke(this, new DialogClosedEventArgs<TResult>(result));
}
/// <summary>
/// Closes the dialog without a result
/// </summary>
public void Cancel()
{
CancelRequested?.Invoke(this, EventArgs.Empty);
}
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
internal event EventHandler? CancelRequested;
}
internal event EventHandler<DialogClosedEventArgs<TResult>>? CloseRequested;
internal event EventHandler? CancelRequested;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -84,55 +84,55 @@ namespace Artemis.UI.Screens.Device
public int X
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
set => RaiseAndSetIfChanged(ref _x, value);
}
public int Y
{
get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value);
set => RaiseAndSetIfChanged(ref _y, value);
}
public float Scale
{
get => _scale;
set => this.RaiseAndSetIfChanged(ref _scale, value);
set => RaiseAndSetIfChanged(ref _scale, value);
}
public int Rotation
{
get => _rotation;
set => this.RaiseAndSetIfChanged(ref _rotation, value);
set => RaiseAndSetIfChanged(ref _rotation, value);
}
public float RedScale
{
get => _redScale;
set => this.RaiseAndSetIfChanged(ref _redScale, value);
set => RaiseAndSetIfChanged(ref _redScale, value);
}
public float GreenScale
{
get => _greenScale;
set => this.RaiseAndSetIfChanged(ref _greenScale, value);
set => RaiseAndSetIfChanged(ref _greenScale, value);
}
public float BlueScale
{
get => _blueScale;
set => this.RaiseAndSetIfChanged(ref _blueScale, value);
set => RaiseAndSetIfChanged(ref _blueScale, value);
}
public SKColor CurrentColor
{
get => _currentColor;
set => this.RaiseAndSetIfChanged(ref _currentColor, value);
set => RaiseAndSetIfChanged(ref _currentColor, value);
}
public bool 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,6 @@ public class MenuBarViewModel : ActivatableViewModelBase
public ProfileEditorHistory? 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
{
get => _repeating;
set => this.RaiseAndSetIfChanged(ref _repeating, value);
set => RaiseAndSetIfChanged(ref _repeating, value);
}
public bool RepeatTimeline
{
get => _repeatTimeline;
set => this.RaiseAndSetIfChanged(ref _repeatTimeline, value);
set => RaiseAndSetIfChanged(ref _repeatTimeline, value);
}
public bool RepeatSegment
{
get => _repeatSegment;
set => this.RaiseAndSetIfChanged(ref _repeatSegment, value);
set => RaiseAndSetIfChanged(ref _repeatSegment, value);
}
public void PlayFromStart()

View File

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

View File

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

View File

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

View File

@ -30,18 +30,18 @@ public class PropertyViewModel : ViewModelBase
public bool IsVisible
{
get => _isVisible;
set => this.RaiseAndSetIfChanged(ref _isVisible, value);
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public bool IsHighlighted
{
get => _isHighlighted;
set => this.RaiseAndSetIfChanged(ref _isHighlighted, value);
set => RaiseAndSetIfChanged(ref _isHighlighted, value);
}
public bool 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
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
set => RaiseAndSetIfChanged(ref _x, value);
}
public string Timestamp
{
get => _timestamp;
set => this.RaiseAndSetIfChanged(ref _timestamp, value);
set => RaiseAndSetIfChanged(ref _timestamp, value);
}
public bool IsSelected => _isSelected?.Value ?? false;

View File

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

View File

@ -52,12 +52,12 @@ public class StatusBarViewModel : ActivatableViewModelBase
public string? StatusMessage
{
get => _statusMessage;
set => this.RaiseAndSetIfChanged(ref _statusMessage, value);
set => RaiseAndSetIfChanged(ref _statusMessage, value);
}
public bool 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:d="http://schemas.microsoft.com/expression/blend/2008"
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"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.TransformToolView">
Welcome to Avalonia!
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.TransformToolView"
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>

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.ReactiveUI;
using ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{
private ZoomBorder? _zoomBorder;
private PointerPoint _dragOffset;
public TransformToolView()
{
InitializeComponent();
@ -14,4 +24,138 @@ public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{
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.UI.Shared.Extensions;
using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using Material.Icons;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
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 />
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);
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 />
public override bool IsEnabled => _isEnabled?.Value ?? false;
@ -34,6 +93,36 @@ public class TransformToolViewModel : ToolViewModel
/// <inheritdoc />
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 />
protected override void Dispose(bool disposing)
{
@ -42,4 +131,37 @@ public class TransformToolViewModel : ToolViewModel
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
{
get => _tools;
set => this.RaiseAndSetIfChanged(ref _tools, value);
set => RaiseAndSetIfChanged(ref _tools, value);
}
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,13 +20,13 @@ namespace Artemis.UI.Screens.SurfaceEditor
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
set => RaiseAndSetIfChanged(ref _isSelected, value);
}
public SKColor 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;
set
{
this.RaiseAndSetIfChanged(ref _selectionStatus, value);
RaiseAndSetIfChanged(ref _selectionStatus, value);
this.RaisePropertyChanged(nameof(Highlighted));
}
}
@ -65,7 +65,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
public Cursor Cursor
{
get => _cursor;
set => this.RaiseAndSetIfChanged(ref _cursor, value);
set => RaiseAndSetIfChanged(ref _cursor, value);
}
public void StartMouseDrag(Point mouseStartPosition)

View File

@ -6,24 +6,26 @@
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
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"
x:Class="Artemis.UI.Screens.Workshop.WorkshopView">
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
x:DataType="workshop:WorkshopViewModel">
<Border Classes="router-container">
<StackPanel Margin="12">
<TextBlock>Workshop!! :3</TextBlock>
<Border Classes="card" Margin="0 12">
<StackPanel Spacing="5">
<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)
</Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Warning}">
Notification test (warning)
</Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Error}">
Notification test (error)
</Button>
<Button Command="{Binding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
<Button Command="{CompiledBinding ShowNotification}" CommandParameter="{x:Static builders:NotificationSeverity.Success}">
Notification test (success)
</Button>
@ -36,6 +38,13 @@
<TextBox
attachedProperties:TextBoxAssist.PrefixText="$"
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>
</Border>
</StackPanel>

View File

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