mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-31 09:43:46 +00:00
Gradient editor - Implemented picker button and layer property editor
This commit is contained in:
parent
1262f84a56
commit
8806348386
@ -20,7 +20,7 @@ namespace Artemis.Core
|
|||||||
private void CreateDataBindingRegistrations()
|
private void CreateDataBindingRegistrations()
|
||||||
{
|
{
|
||||||
DataBinding.ClearDataBindingProperties();
|
DataBinding.ClearDataBindingProperties();
|
||||||
if (CurrentValue == null)
|
if (CurrentValue == null!)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (int index = 0; index < CurrentValue.Count; index++)
|
for (int index = 0; index < CurrentValue.Count; index++)
|
||||||
@ -54,10 +54,10 @@ namespace Artemis.Core
|
|||||||
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
|
private void OnCurrentValueSet(object? sender, LayerPropertyEventArgs e)
|
||||||
{
|
{
|
||||||
// Don't allow color gradients to be null
|
// Don't allow color gradients to be null
|
||||||
if (BaseValue == null)
|
if (BaseValue == null!)
|
||||||
BaseValue = DefaultValue ?? new ColorGradient();
|
BaseValue = new ColorGradient(DefaultValue);
|
||||||
|
|
||||||
if (_subscribedGradient != BaseValue)
|
if (!ReferenceEquals(_subscribedGradient, BaseValue))
|
||||||
{
|
{
|
||||||
if (_subscribedGradient != null)
|
if (_subscribedGradient != null)
|
||||||
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
|
_subscribedGradient.CollectionChanged -= SubscribedGradientOnPropertyChanged;
|
||||||
@ -80,8 +80,8 @@ namespace Artemis.Core
|
|||||||
protected override void OnInitialize()
|
protected override void OnInitialize()
|
||||||
{
|
{
|
||||||
// Don't allow color gradients to be null
|
// Don't allow color gradients to be null
|
||||||
if (BaseValue == null)
|
if (BaseValue == null!)
|
||||||
BaseValue = DefaultValue ?? new ColorGradient();
|
BaseValue = new ColorGradient(DefaultValue);
|
||||||
|
|
||||||
base.OnInitialize();
|
base.OnInitialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,379 +6,571 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A gradient containing a list of <see cref="ColorGradientStop" />s
|
||||||
|
/// </summary>
|
||||||
|
public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionChanged
|
||||||
{
|
{
|
||||||
|
#region Equality members
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A gradient containing a list of <see cref="ColorGradientStop" />s
|
/// Determines whether all the stops in this gradient are equal to the stops in the given <paramref name="other"/> gradient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ColorGradient : IList<ColorGradientStop>, IList, INotifyCollectionChanged
|
/// <param name="other">The other gradient to compare to</param>
|
||||||
|
protected bool Equals(ColorGradient other)
|
||||||
{
|
{
|
||||||
private static readonly SKColor[] FastLedRainbow =
|
if (Count != other.Count)
|
||||||
{
|
return false;
|
||||||
new(0xFFFF0000), // Red
|
|
||||||
new(0xFFFF9900), // Orange
|
|
||||||
new(0xFFFFFF00), // Yellow
|
|
||||||
new(0xFF00FF00), // Green
|
|
||||||
new(0xFF00FF7E), // Aqua
|
|
||||||
new(0xFF0078FF), // Blue
|
|
||||||
new(0xFF9E22FF), // Purple
|
|
||||||
new(0xFFFF34AE), // Pink
|
|
||||||
new(0xFFFF0000) // and back to Red
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly List<ColorGradientStop> _stops;
|
for (int i = 0; i < Count; i++)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
|
||||||
/// </summary>
|
|
||||||
public ColorGradient()
|
|
||||||
{
|
{
|
||||||
_stops = new List<ColorGradientStop>();
|
if (!Equals(this[i], other[i]))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return true;
|
||||||
/// Gets all the colors in the color gradient
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="timesToRepeat">The amount of times to repeat the colors</param>
|
/// <inheritdoc />
|
||||||
/// <param name="seamless">
|
public override bool Equals(object? obj)
|
||||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
{
|
||||||
/// last color
|
if (ReferenceEquals(null, obj))
|
||||||
/// </param>
|
return false;
|
||||||
/// <returns>An array containing each color in the gradient</returns>
|
if (ReferenceEquals(this, obj))
|
||||||
public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
|
return true;
|
||||||
|
if (obj.GetType() != this.GetType())
|
||||||
|
return false;
|
||||||
|
return Equals((ColorGradient) obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
unchecked
|
||||||
{
|
{
|
||||||
List<SKColor> result = new();
|
int hash = 19;
|
||||||
if (timesToRepeat == 0)
|
foreach (ColorGradientStop stops in this)
|
||||||
{
|
hash = hash * 31 + stops.GetHashCode();
|
||||||
result = this.Select(c => c.Color).ToList();
|
return hash;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<SKColor> colors = this.Select(c => c.Color).ToList();
|
|
||||||
for (int i = 0; i <= timesToRepeat; i++)
|
|
||||||
result.AddRange(colors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seamless && !IsSeamless())
|
|
||||||
result.Add(result[0]);
|
|
||||||
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets all the positions in the color gradient
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="timesToRepeat">
|
|
||||||
/// The amount of times to repeat the positions
|
|
||||||
/// </param>
|
|
||||||
/// <param name="seamless">
|
|
||||||
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
|
||||||
/// last color
|
|
||||||
/// </param>
|
|
||||||
/// <returns>An array containing a position for each color between 0.0 and 1.0</returns>
|
|
||||||
public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
|
|
||||||
{
|
|
||||||
List<float> result = new();
|
|
||||||
if (timesToRepeat == 0)
|
|
||||||
{
|
|
||||||
result = this.Select(c => c.Position).ToList();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Create stops and a list of divided stops
|
|
||||||
List<float> stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
|
|
||||||
|
|
||||||
// For each repeat cycle, add the base stops to the end result
|
|
||||||
for (int i = 0; i <= timesToRepeat; i++)
|
|
||||||
{
|
|
||||||
float lastStop = result.LastOrDefault();
|
|
||||||
result.AddRange(stops.Select(s => s + lastStop));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seamless && !IsSeamless())
|
|
||||||
{
|
|
||||||
// Compress current points evenly
|
|
||||||
float compression = 1f - 1f / result.Count;
|
|
||||||
for (int index = 0; index < result.Count; index++)
|
|
||||||
result[index] = result[index] * compression;
|
|
||||||
// Add one extra point at the end
|
|
||||||
result.Add(1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a color at any position between 0.0 and 1.0 using interpolation
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="position">A position between 0.0 and 1.0</param>
|
|
||||||
public SKColor GetColor(float position)
|
|
||||||
{
|
|
||||||
if (!this.Any())
|
|
||||||
return SKColor.Empty;
|
|
||||||
|
|
||||||
ColorGradientStop[] stops = this.ToArray();
|
|
||||||
if (position <= 0) return stops[0].Color;
|
|
||||||
if (position >= 1) return stops[^1].Color;
|
|
||||||
ColorGradientStop left = stops[0];
|
|
||||||
ColorGradientStop? right = null;
|
|
||||||
foreach (ColorGradientStop stop in stops)
|
|
||||||
{
|
|
||||||
if (stop.Position >= position)
|
|
||||||
{
|
|
||||||
right = stop;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
left = stop;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (right == null || left == right)
|
|
||||||
return left.Color;
|
|
||||||
|
|
||||||
position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2);
|
|
||||||
byte a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha);
|
|
||||||
byte r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red);
|
|
||||||
byte g = (byte) ((right.Color.Green - left.Color.Green) * position + left.Color.Green);
|
|
||||||
byte b = (byte) ((right.Color.Blue - left.Color.Blue) * position + left.Color.Blue);
|
|
||||||
return new SKColor(r, g, b, a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a new ColorGradient with colors looping through the HSV-spectrum
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static ColorGradient GetUnicornBarf()
|
|
||||||
{
|
|
||||||
ColorGradient gradient = new();
|
|
||||||
for (int index = 0; index < FastLedRainbow.Length; index++)
|
|
||||||
{
|
|
||||||
SKColor skColor = FastLedRainbow[index];
|
|
||||||
float position = 1f / (FastLedRainbow.Length - 1f) * index;
|
|
||||||
gradient.Add(new ColorGradientStop(skColor, position));
|
|
||||||
}
|
|
||||||
|
|
||||||
return gradient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the gradient is seamless
|
|
||||||
/// </summary>
|
|
||||||
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
|
|
||||||
public bool IsSeamless()
|
|
||||||
{
|
|
||||||
return Count == 0 || this.First().Color.Equals(this.Last().Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Sort()
|
|
||||||
{
|
|
||||||
int requiredIndex = 0;
|
|
||||||
foreach (ColorGradientStop colorGradientStop in _stops.OrderBy(s => s.Position).ToList())
|
|
||||||
{
|
|
||||||
int actualIndex = _stops.IndexOf(colorGradientStop);
|
|
||||||
if (requiredIndex != actualIndex)
|
|
||||||
{
|
|
||||||
_stops.RemoveAt(actualIndex);
|
|
||||||
_stops.Insert(requiredIndex, colorGradientStop);
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, colorGradientStop, requiredIndex, actualIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
requiredIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
Sort();
|
|
||||||
OnStopChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Implementation of IEnumerable
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerator<ColorGradientStop> GetEnumerator()
|
|
||||||
{
|
|
||||||
return _stops.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of ICollection<ColorGradientStop>
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Add(ColorGradientStop item)
|
|
||||||
{
|
|
||||||
_stops.Add(item);
|
|
||||||
item.PropertyChanged += ItemOnPropertyChanged;
|
|
||||||
Sort();
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, _stops.IndexOf(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Add(object? value)
|
|
||||||
{
|
|
||||||
if (value is ColorGradientStop stop)
|
|
||||||
_stops.Add(stop);
|
|
||||||
|
|
||||||
return IndexOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IList.Clear" />
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
foreach (ColorGradientStop item in _stops)
|
|
||||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
|
||||||
_stops.Clear();
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Contains(object? value)
|
|
||||||
{
|
|
||||||
return _stops.Contains(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int IndexOf(object? value)
|
|
||||||
{
|
|
||||||
return _stops.IndexOf(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Insert(int index, object? value)
|
|
||||||
{
|
|
||||||
if (value is ColorGradientStop stop)
|
|
||||||
_stops.Insert(index, stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Remove(object? value)
|
|
||||||
{
|
|
||||||
if (value is ColorGradientStop stop)
|
|
||||||
_stops.Remove(stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Contains(ColorGradientStop item)
|
|
||||||
{
|
|
||||||
return _stops.Contains(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void CopyTo(ColorGradientStop[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
_stops.CopyTo(array, arrayIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Remove(ColorGradientStop item)
|
|
||||||
{
|
|
||||||
item.PropertyChanged -= ItemOnPropertyChanged;
|
|
||||||
int index = _stops.IndexOf(item);
|
|
||||||
bool removed = _stops.Remove(item);
|
|
||||||
if (removed)
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
|
|
||||||
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void CopyTo(Array array, int index)
|
|
||||||
{
|
|
||||||
_stops.CopyTo((ColorGradientStop[]) array, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICollection{T}.Count" />
|
|
||||||
public int Count => _stops.Count;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsSynchronized => false;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public object SyncRoot => this;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="ICollection{T}.IsReadOnly" />
|
|
||||||
public bool IsReadOnly => false;
|
|
||||||
|
|
||||||
object? IList.this[int index]
|
|
||||||
{
|
|
||||||
get => this[index];
|
|
||||||
set => this[index] = (ColorGradientStop) value!;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of IList<ColorGradientStop>
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int IndexOf(ColorGradientStop item)
|
|
||||||
{
|
|
||||||
return _stops.IndexOf(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Insert(int index, ColorGradientStop item)
|
|
||||||
{
|
|
||||||
_stops.Insert(index, item);
|
|
||||||
item.PropertyChanged += ItemOnPropertyChanged;
|
|
||||||
Sort();
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IList{T}.RemoveAt" />
|
|
||||||
public void RemoveAt(int index)
|
|
||||||
{
|
|
||||||
_stops[index].PropertyChanged -= ItemOnPropertyChanged;
|
|
||||||
_stops.RemoveAt(index);
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsFixedSize { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ColorGradientStop this[int index]
|
|
||||||
{
|
|
||||||
get => _stops[index];
|
|
||||||
set
|
|
||||||
{
|
|
||||||
ColorGradientStop? oldValue = _stops[index];
|
|
||||||
oldValue.PropertyChanged -= ItemOnPropertyChanged;
|
|
||||||
_stops[index] = value;
|
|
||||||
_stops[index].PropertyChanged += ItemOnPropertyChanged;
|
|
||||||
Sort();
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Implementation of INotifyCollectionChanged
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
|
||||||
|
|
||||||
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
CollectionChanged?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when any of the stops has changed in some way
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler? StopChanged;
|
|
||||||
|
|
||||||
private void OnStopChanged()
|
|
||||||
{
|
|
||||||
StopChanged?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static readonly SKColor[] FastLedRainbow =
|
||||||
|
{
|
||||||
|
new(0xFFFF0000), // Red
|
||||||
|
new(0xFFFF9900), // Orange
|
||||||
|
new(0xFFFFFF00), // Yellow
|
||||||
|
new(0xFF00FF00), // Green
|
||||||
|
new(0xFF00FF7E), // Aqua
|
||||||
|
new(0xFF0078FF), // Blue
|
||||||
|
new(0xFF9E22FF), // Purple
|
||||||
|
new(0xFFFF34AE), // Pink
|
||||||
|
new(0xFFFF0000) // and back to Red
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly List<ColorGradientStop> _stops;
|
||||||
|
private bool _updating;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||||
|
/// </summary>
|
||||||
|
public ColorGradient()
|
||||||
|
{
|
||||||
|
_stops = new List<ColorGradientStop>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ColorGradient" /> class
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="colorGradient">The color gradient to copy</param>
|
||||||
|
public ColorGradient(ColorGradient? colorGradient)
|
||||||
|
{
|
||||||
|
_stops = new List<ColorGradientStop>();
|
||||||
|
if (colorGradient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (ColorGradientStop colorGradientStop in colorGradient)
|
||||||
|
{
|
||||||
|
ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position);
|
||||||
|
stop.PropertyChanged += ItemOnPropertyChanged;
|
||||||
|
_stops.Add(stop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the colors in the color gradient
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timesToRepeat">The amount of times to repeat the colors</param>
|
||||||
|
/// <param name="seamless">
|
||||||
|
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||||
|
/// last color
|
||||||
|
/// </param>
|
||||||
|
/// <returns>An array containing each color in the gradient</returns>
|
||||||
|
public SKColor[] GetColorsArray(int timesToRepeat = 0, bool seamless = false)
|
||||||
|
{
|
||||||
|
List<SKColor> result = new();
|
||||||
|
if (timesToRepeat == 0)
|
||||||
|
{
|
||||||
|
result = this.Select(c => c.Color).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<SKColor> colors = this.Select(c => c.Color).ToList();
|
||||||
|
for (int i = 0; i <= timesToRepeat; i++)
|
||||||
|
result.AddRange(colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seamless && !IsSeamless())
|
||||||
|
result.Add(result[0]);
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the positions in the color gradient
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timesToRepeat">
|
||||||
|
/// The amount of times to repeat the positions
|
||||||
|
/// </param>
|
||||||
|
/// <param name="seamless">
|
||||||
|
/// A boolean indicating whether to make the gradient seamless by adding the first color behind the
|
||||||
|
/// last color
|
||||||
|
/// </param>
|
||||||
|
/// <returns>An array containing a position for each color between 0.0 and 1.0</returns>
|
||||||
|
public float[] GetPositionsArray(int timesToRepeat = 0, bool seamless = false)
|
||||||
|
{
|
||||||
|
List<float> result = new();
|
||||||
|
if (timesToRepeat == 0)
|
||||||
|
{
|
||||||
|
result = this.Select(c => c.Position).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create stops and a list of divided stops
|
||||||
|
List<float> stops = this.Select(c => c.Position / (timesToRepeat + 1)).ToList();
|
||||||
|
|
||||||
|
// For each repeat cycle, add the base stops to the end result
|
||||||
|
for (int i = 0; i <= timesToRepeat; i++)
|
||||||
|
{
|
||||||
|
float lastStop = result.LastOrDefault();
|
||||||
|
result.AddRange(stops.Select(s => s + lastStop));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seamless && !IsSeamless())
|
||||||
|
{
|
||||||
|
// Compress current points evenly
|
||||||
|
float compression = 1f - 1f / result.Count;
|
||||||
|
for (int index = 0; index < result.Count; index++)
|
||||||
|
result[index] = result[index] * compression;
|
||||||
|
// Add one extra point at the end
|
||||||
|
result.Add(1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a color at any position between 0.0 and 1.0 using interpolation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">A position between 0.0 and 1.0</param>
|
||||||
|
public SKColor GetColor(float position)
|
||||||
|
{
|
||||||
|
if (!this.Any())
|
||||||
|
return new SKColor(255, 255, 255);
|
||||||
|
|
||||||
|
ColorGradientStop[] stops = this.ToArray();
|
||||||
|
if (position <= 0) return stops[0].Color;
|
||||||
|
if (position >= 1) return stops[^1].Color;
|
||||||
|
ColorGradientStop left = stops[0];
|
||||||
|
ColorGradientStop? right = null;
|
||||||
|
foreach (ColorGradientStop stop in stops)
|
||||||
|
{
|
||||||
|
if (stop.Position >= position)
|
||||||
|
{
|
||||||
|
right = stop;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right == null || left == right)
|
||||||
|
return left.Color;
|
||||||
|
|
||||||
|
position = (float) Math.Round((position - left.Position) / (right.Position - left.Position), 2);
|
||||||
|
byte a = (byte) ((right.Color.Alpha - left.Color.Alpha) * position + left.Color.Alpha);
|
||||||
|
byte r = (byte) ((right.Color.Red - left.Color.Red) * position + left.Color.Red);
|
||||||
|
byte g = (byte) ((right.Color.Green - left.Color.Green) * position + left.Color.Green);
|
||||||
|
byte b = (byte) ((right.Color.Blue - left.Color.Blue) * position + left.Color.Blue);
|
||||||
|
return new SKColor(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a new ColorGradient with colors looping through the HSV-spectrum
|
||||||
|
/// </summary>
|
||||||
|
public static ColorGradient GetUnicornBarf()
|
||||||
|
{
|
||||||
|
ColorGradient gradient = new();
|
||||||
|
for (int index = 0; index < FastLedRainbow.Length; index++)
|
||||||
|
{
|
||||||
|
SKColor skColor = FastLedRainbow[index];
|
||||||
|
float position = 1f / (FastLedRainbow.Length - 1f) * index;
|
||||||
|
gradient.Add(new ColorGradientStop(skColor, position));
|
||||||
|
}
|
||||||
|
|
||||||
|
return gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a new ColorGradient with random colors from the HSV-spectrum
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stops">The amount of stops to add</param>
|
||||||
|
public ColorGradient GetRandom(int stops)
|
||||||
|
{
|
||||||
|
ColorGradient gradient = new();
|
||||||
|
Random random = new();
|
||||||
|
for (int index = 0; index < stops; index++)
|
||||||
|
{
|
||||||
|
SKColor skColor = SKColor.FromHsv(random.NextSingle() * 360, 100, 100);
|
||||||
|
float position = 1f / (stops - 1f) * index;
|
||||||
|
gradient.Add(new ColorGradientStop(skColor, position));
|
||||||
|
}
|
||||||
|
|
||||||
|
return gradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the gradient is seamless
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see langword="true" /> if the gradient is seamless; <see langword="false" /> otherwise</returns>
|
||||||
|
public bool IsSeamless()
|
||||||
|
{
|
||||||
|
return Count == 0 || this.First().Color.Equals(this.Last().Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spreads the color stops equally across the gradient.
|
||||||
|
/// </summary>
|
||||||
|
public void SpreadStops()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_updating = true;
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
this[i].Position = MathF.Round(i / ((float) Count - 1), 3, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updating = false;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If not already the case, makes the gradient seamless by adding the first color to the end of the gradient and
|
||||||
|
/// compressing the other stops.
|
||||||
|
/// <para>
|
||||||
|
/// If the gradient is already seamless, removes the last color and spreads the remaining stops to fill the freed
|
||||||
|
/// space.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public void ToggleSeamless()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_updating = true;
|
||||||
|
|
||||||
|
if (IsSeamless())
|
||||||
|
{
|
||||||
|
ColorGradientStop stopToRemove = this.Last();
|
||||||
|
Remove(stopToRemove);
|
||||||
|
|
||||||
|
// Uncompress the stops if there is still more than one
|
||||||
|
if (Count >= 2)
|
||||||
|
{
|
||||||
|
float multiplier = Count / (Count - 1f);
|
||||||
|
foreach (ColorGradientStop stop in this)
|
||||||
|
stop.Position = MathF.Round(Math.Min(stop.Position * multiplier, 100f), 3, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Compress existing stops to the left
|
||||||
|
float multiplier = (Count - 1f) / Count;
|
||||||
|
foreach (ColorGradientStop stop in this)
|
||||||
|
stop.Position = MathF.Round(stop.Position * multiplier, 3, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
// Add a stop to the end that is the same color as the first stop
|
||||||
|
ColorGradientStop newStop = new(this.First().Color, 1f);
|
||||||
|
Add(newStop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updating = false;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flips the stops of the gradient.
|
||||||
|
/// </summary>
|
||||||
|
public void FlipStops()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_updating = true;
|
||||||
|
foreach (ColorGradientStop stop in this)
|
||||||
|
stop.Position = 1 - stop.Position;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updating = false;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotates the stops of the gradient shifting every stop over to the position of it's neighbor and wrapping around at
|
||||||
|
/// the end of the gradient.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inverse">A boolean indicating whether or not the invert the rotation.</param>
|
||||||
|
public void RotateStops(bool inverse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_updating = true;
|
||||||
|
List<ColorGradientStop> stops = inverse
|
||||||
|
? this.OrderBy(s => s.Position).ToList()
|
||||||
|
: this.OrderByDescending(s => s.Position).ToList();
|
||||||
|
|
||||||
|
float lastStopPosition = stops.Last().Position;
|
||||||
|
foreach (ColorGradientStop stop in stops)
|
||||||
|
(stop.Position, lastStopPosition) = (lastStopPosition, stop.Position);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updating = false;
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when any of the stops has changed in some way
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler? StopChanged;
|
||||||
|
|
||||||
|
internal void Sort()
|
||||||
|
{
|
||||||
|
if (_updating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int requiredIndex = 0;
|
||||||
|
foreach (ColorGradientStop colorGradientStop in _stops.OrderBy(s => s.Position).ToList())
|
||||||
|
{
|
||||||
|
int actualIndex = _stops.IndexOf(colorGradientStop);
|
||||||
|
if (requiredIndex != actualIndex)
|
||||||
|
{
|
||||||
|
_stops.RemoveAt(actualIndex);
|
||||||
|
_stops.Insert(requiredIndex, colorGradientStop);
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, colorGradientStop, requiredIndex, actualIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ItemOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Sort();
|
||||||
|
OnStopChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnStopChanged()
|
||||||
|
{
|
||||||
|
StopChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IEnumerable
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerator<ColorGradientStop> GetEnumerator()
|
||||||
|
{
|
||||||
|
return _stops.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of ICollection<ColorGradientStop>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Add(ColorGradientStop item)
|
||||||
|
{
|
||||||
|
_stops.Add(item);
|
||||||
|
item.PropertyChanged += ItemOnPropertyChanged;
|
||||||
|
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int Add(object? value)
|
||||||
|
{
|
||||||
|
if (value is ColorGradientStop stop)
|
||||||
|
_stops.Add(stop);
|
||||||
|
|
||||||
|
return IndexOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IList.Clear" />
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (ColorGradientStop item in _stops)
|
||||||
|
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||||
|
_stops.Clear();
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Contains(object? value)
|
||||||
|
{
|
||||||
|
return _stops.Contains(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int IndexOf(object? value)
|
||||||
|
{
|
||||||
|
return _stops.IndexOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Insert(int index, object? value)
|
||||||
|
{
|
||||||
|
if (value is ColorGradientStop stop)
|
||||||
|
_stops.Insert(index, stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Remove(object? value)
|
||||||
|
{
|
||||||
|
if (value is ColorGradientStop stop)
|
||||||
|
_stops.Remove(stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Contains(ColorGradientStop item)
|
||||||
|
{
|
||||||
|
return _stops.Contains(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(ColorGradientStop[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
_stops.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Remove(ColorGradientStop item)
|
||||||
|
{
|
||||||
|
item.PropertyChanged -= ItemOnPropertyChanged;
|
||||||
|
int index = _stops.IndexOf(item);
|
||||||
|
bool removed = _stops.Remove(item);
|
||||||
|
if (removed)
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
_stops.CopyTo((ColorGradientStop[]) array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.Count" />
|
||||||
|
public int Count => _stops.Count;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object SyncRoot => this;
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ICollection{T}.IsReadOnly" />
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = (ColorGradientStop) value!;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IList<ColorGradientStop>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public int IndexOf(ColorGradientStop item)
|
||||||
|
{
|
||||||
|
return _stops.IndexOf(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Insert(int index, ColorGradientStop item)
|
||||||
|
{
|
||||||
|
_stops.Insert(index, item);
|
||||||
|
item.PropertyChanged += ItemOnPropertyChanged;
|
||||||
|
Sort();
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IList{T}.RemoveAt" />
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
_stops[index].PropertyChanged -= ItemOnPropertyChanged;
|
||||||
|
_stops.RemoveAt(index);
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsFixedSize { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ColorGradientStop this[int index]
|
||||||
|
{
|
||||||
|
get => _stops[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ColorGradientStop? oldValue = _stops[index];
|
||||||
|
oldValue.PropertyChanged -= ItemOnPropertyChanged;
|
||||||
|
_stops[index] = value;
|
||||||
|
_stops[index].PropertyChanged += ItemOnPropertyChanged;
|
||||||
|
Sort();
|
||||||
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of INotifyCollectionChanged
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event NotifyCollectionChangedEventHandler? CollectionChanged;
|
||||||
|
|
||||||
|
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
CollectionChanged?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using SkiaSharp;
|
using System;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.Core
|
namespace Artemis.Core
|
||||||
{
|
{
|
||||||
@ -7,6 +8,34 @@ namespace Artemis.Core
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ColorGradientStop : CorePropertyChanged
|
public class ColorGradientStop : CorePropertyChanged
|
||||||
{
|
{
|
||||||
|
#region Equality members
|
||||||
|
|
||||||
|
/// <inheritdoc cref="object.Equals(object)" />
|
||||||
|
protected bool Equals(ColorGradientStop other)
|
||||||
|
{
|
||||||
|
return _color.Equals(other._color) && _position.Equals(other._position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
return false;
|
||||||
|
if (ReferenceEquals(this, obj))
|
||||||
|
return true;
|
||||||
|
if (obj.GetType() != this.GetType())
|
||||||
|
return false;
|
||||||
|
return Equals((ColorGradientStop) obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(_color, _position);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private SKColor _color;
|
private SKColor _color;
|
||||||
private float _position;
|
private float _position;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Controls.Flyouts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a flyout that hosts a gradient picker.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GradientPickerFlyout : Flyout
|
||||||
|
{
|
||||||
|
private GradientPicker.GradientPicker? _picker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the gradient picker that this flyout hosts
|
||||||
|
/// </summary>
|
||||||
|
public GradientPicker.GradientPicker GradientPicker => _picker ??= new GradientPicker.GradientPicker();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Control CreatePresenter()
|
||||||
|
{
|
||||||
|
_picker ??= new GradientPicker.GradientPicker();
|
||||||
|
FlyoutPresenter presenter = new() {Content = GradientPicker};
|
||||||
|
return presenter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,24 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Providers;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
|
using FluentAvalonia.UI.Media;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Artemis.UI.Shared.Controls.GradientPicker;
|
namespace Artemis.UI.Shared.Controls.GradientPicker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a gradient picker that can be used to edit a gradient.
|
||||||
|
/// </summary>
|
||||||
public class GradientPicker : TemplatedControl
|
public class GradientPicker : TemplatedControl
|
||||||
{
|
{
|
||||||
private LinearGradientBrush _linearGradientBrush = new();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color gradient.
|
/// Gets or sets the color gradient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly StyledProperty<ColorGradient> ColorGradientProperty =
|
public static readonly StyledProperty<ColorGradient> ColorGradientProperty =
|
||||||
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(Core.ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
|
AvaloniaProperty.Register<GradientPicker, ColorGradient>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the currently selected color stop.
|
/// Gets or sets the currently selected color stop.
|
||||||
@ -27,11 +36,55 @@ public class GradientPicker : TemplatedControl
|
|||||||
AvaloniaProperty.Register<GradientPicker, ColorGradientStop?>(nameof(SelectedColorStop), defaultBindingMode: BindingMode.TwoWay);
|
AvaloniaProperty.Register<GradientPicker, ColorGradientStop?>(nameof(SelectedColorStop), defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the linear gradient brush representing the color gradient.
|
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<bool> IsCompactProperty =
|
||||||
|
AvaloniaProperty.Register<GradientPicker, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a storage provider to use for storing and loading gradients.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
|
||||||
|
AvaloniaProperty.Register<GradientPicker, IColorGradientStorageProvider?>(nameof(StorageProvider), notifying: StorageProviderChanged);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the linear gradient brush representing the color gradient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly DirectProperty<GradientPicker, LinearGradientBrush> LinearGradientBrushProperty =
|
public static readonly DirectProperty<GradientPicker, LinearGradientBrush> LinearGradientBrushProperty =
|
||||||
AvaloniaProperty.RegisterDirect<GradientPicker, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
AvaloniaProperty.RegisterDirect<GradientPicker, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command to execute when deleting stops.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DirectProperty<GradientPicker, ICommand> DeleteStopProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<GradientPicker, ICommand>(nameof(DeleteStop), g => g.DeleteStop);
|
||||||
|
|
||||||
|
private readonly ICommand _deleteStop;
|
||||||
|
private Button? _flipStops;
|
||||||
|
private Border? _gradient;
|
||||||
|
private Button? _rotateStops;
|
||||||
|
private bool _shiftDown;
|
||||||
|
private Button? _spreadStops;
|
||||||
|
private Button? _toggleSeamless;
|
||||||
|
private ColorGradient? _lastColorGradient;
|
||||||
|
private ColorPicker? _colorPicker;
|
||||||
|
|
||||||
|
public GradientPicker()
|
||||||
|
{
|
||||||
|
_deleteStop = ReactiveCommand.Create<ColorGradientStop>(s =>
|
||||||
|
{
|
||||||
|
if (ColorGradient.Count <= 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = ColorGradient.IndexOf(s);
|
||||||
|
ColorGradient.Remove(s);
|
||||||
|
if (index > ColorGradient.Count - 1)
|
||||||
|
index--;
|
||||||
|
|
||||||
|
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color gradient.
|
/// Gets or sets the color gradient.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -47,39 +100,130 @@ public class GradientPicker : TemplatedControl
|
|||||||
public ColorGradientStop? SelectedColorStop
|
public ColorGradientStop? SelectedColorStop
|
||||||
{
|
{
|
||||||
get => GetValue(SelectedColorStopProperty);
|
get => GetValue(SelectedColorStopProperty);
|
||||||
set => SetValue(SelectedColorStopProperty, value);
|
set
|
||||||
|
{
|
||||||
|
if (_colorPicker != null && SelectedColorStop != null)
|
||||||
|
_colorPicker.PreviousColor = new Color2(SelectedColorStop.Color.Red, SelectedColorStop.Color.Green, SelectedColorStop.Color.Blue, SelectedColorStop.Color.Alpha);
|
||||||
|
SetValue(SelectedColorStopProperty, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the linear gradient brush representing the color gradient.
|
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LinearGradientBrush LinearGradientBrush
|
public bool IsCompact
|
||||||
{
|
{
|
||||||
get => _linearGradientBrush;
|
get => GetValue(IsCompactProperty);
|
||||||
private set => SetAndRaise(LinearGradientBrushProperty, ref _linearGradientBrush, value);
|
set => SetValue(IsCompactProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a storage provider to use for storing and loading gradients.
|
||||||
|
/// </summary>
|
||||||
|
public IColorGradientStorageProvider? StorageProvider
|
||||||
|
{
|
||||||
|
get => GetValue(StorageProviderProperty);
|
||||||
|
set => SetValue(StorageProviderProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the linear gradient brush representing the color gradient.
|
||||||
|
/// </summary>
|
||||||
|
public LinearGradientBrush LinearGradientBrush { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the command to execute when deleting stops.
|
||||||
|
/// </summary>
|
||||||
|
public ICommand DeleteStop
|
||||||
|
{
|
||||||
|
get => _deleteStop;
|
||||||
|
private init => SetAndRaise(DeleteStopProperty, ref _deleteStop, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_gradient != null)
|
||||||
|
_gradient.PointerPressed -= GradientOnPointerPressed;
|
||||||
|
if (_spreadStops != null)
|
||||||
|
_spreadStops.Click -= SpreadStopsOnClick;
|
||||||
|
if (_toggleSeamless != null)
|
||||||
|
_toggleSeamless.Click -= ToggleSeamlessOnClick;
|
||||||
|
if (_flipStops != null)
|
||||||
|
_flipStops.Click -= FlipStopsOnClick;
|
||||||
|
if (_rotateStops != null)
|
||||||
|
_rotateStops.Click -= RotateStopsOnClick;
|
||||||
|
|
||||||
|
_colorPicker = e.NameScope.Find<ColorPicker>("ColorPicker");
|
||||||
|
_gradient = e.NameScope.Find<Border>("Gradient");
|
||||||
|
_spreadStops = e.NameScope.Find<Button>("SpreadStops");
|
||||||
|
_toggleSeamless = e.NameScope.Find<Button>("ToggleSeamless");
|
||||||
|
_flipStops = e.NameScope.Find<Button>("FlipStops");
|
||||||
|
_rotateStops = e.NameScope.Find<Button>("RotateStops");
|
||||||
|
|
||||||
|
if (_gradient != null)
|
||||||
|
_gradient.PointerPressed += GradientOnPointerPressed;
|
||||||
|
if (_spreadStops != null)
|
||||||
|
_spreadStops.Click += SpreadStopsOnClick;
|
||||||
|
if (_toggleSeamless != null)
|
||||||
|
_toggleSeamless.Click += ToggleSeamlessOnClick;
|
||||||
|
if (_flipStops != null)
|
||||||
|
_flipStops.Click += FlipStopsOnClick;
|
||||||
|
if (_rotateStops != null)
|
||||||
|
_rotateStops.Click += RotateStopsOnClick;
|
||||||
|
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
Subscribe();
|
||||||
|
|
||||||
|
KeyUp += OnKeyUp;
|
||||||
|
KeyDown += OnKeyDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
KeyUp -= OnKeyUp;
|
||||||
|
KeyDown -= OnKeyDown;
|
||||||
|
|
||||||
|
_shiftDown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
|
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
|
||||||
{
|
{
|
||||||
if (before)
|
(sender as GradientPicker)?.Subscribe();
|
||||||
(sender as GradientPicker)?.Unsubscribe();
|
}
|
||||||
else
|
|
||||||
(sender as GradientPicker)?.Subscribe();
|
private static void StorageProviderChanged(IAvaloniaObject sender, bool before)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Subscribe()
|
private void Subscribe()
|
||||||
{
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
|
||||||
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
|
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
|
||||||
ColorGradient.StopChanged += ColorGradientOnStopChanged;
|
ColorGradient.StopChanged += ColorGradientOnStopChanged;
|
||||||
|
SelectedColorStop = ColorGradient.FirstOrDefault();
|
||||||
|
|
||||||
UpdateGradient();
|
UpdateGradient();
|
||||||
SelectedColorStop = ColorGradient.FirstOrDefault();
|
|
||||||
|
_lastColorGradient = ColorGradient;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Unsubscribe()
|
private void Unsubscribe()
|
||||||
{
|
{
|
||||||
ColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
|
if (_lastColorGradient == null)
|
||||||
ColorGradient.StopChanged -= ColorGradientOnStopChanged;
|
return;
|
||||||
|
|
||||||
|
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
|
||||||
|
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
|
||||||
|
_lastColorGradient = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
@ -94,42 +238,75 @@ public class GradientPicker : TemplatedControl
|
|||||||
|
|
||||||
private void UpdateGradient()
|
private void UpdateGradient()
|
||||||
{
|
{
|
||||||
// Remove old stops
|
|
||||||
|
|
||||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
|
||||||
if (ColorGradient == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Add new stops
|
|
||||||
|
|
||||||
// Update the display gradient
|
// Update the display gradient
|
||||||
GradientStops collection = new();
|
GradientStops collection = new();
|
||||||
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
|
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
|
||||||
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
||||||
LinearGradientBrush = new LinearGradientBrush {GradientStops = collection};
|
|
||||||
|
LinearGradientBrush.GradientStops = collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectColorStop(object? sender, PointerReleasedEventArgs e)
|
private void GradientOnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is IDataContextProvider dataContextProvider && dataContextProvider.DataContext is ColorGradientStop colorStop)
|
if (_gradient == null)
|
||||||
SelectedColorStop = colorStop;
|
return;
|
||||||
|
|
||||||
|
float position = (float) (e.GetPosition(_gradient).X / _gradient.Bounds.Width);
|
||||||
|
|
||||||
|
ColorGradientStop newStop = new(ColorGradient.GetColor(position), position);
|
||||||
|
ColorGradient.Add(newStop);
|
||||||
|
SelectedColorStop = newStop;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Overrides of Visual
|
private void OnKeyDown(object? sender, KeyEventArgs e)
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
{
|
||||||
Subscribe();
|
if (e.Key is Key.LeftShift or Key.RightShift)
|
||||||
base.OnAttachedToVisualTree(e);
|
_shiftDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private void OnKeyUp(object? sender, KeyEventArgs e)
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
{
|
||||||
Unsubscribe();
|
if (e.Key is Key.LeftShift or Key.RightShift)
|
||||||
base.OnDetachedFromVisualTree(e);
|
_shiftDown = false;
|
||||||
|
|
||||||
|
if (e.Key != Key.Delete || SelectedColorStop == null || ColorGradient.Count <= 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = ColorGradient.IndexOf(SelectedColorStop);
|
||||||
|
ColorGradient.Remove(SelectedColorStop);
|
||||||
|
if (index > ColorGradient.Count - 1)
|
||||||
|
index--;
|
||||||
|
|
||||||
|
SelectedColorStop = ColorGradient.ElementAtOrDefault(index);
|
||||||
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private void SpreadStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ColorGradient.SpreadStops();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleSeamlessOnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ColorGradient.ToggleSeamless();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlipStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ColorGradient.FlipStops();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateStopsOnClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (SelectedColorStop == null || ColorGradient.Count < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ColorGradient.RotateStops(_shiftDown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,212 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
|
using Artemis.Core;
|
||||||
|
using Artemis.UI.Shared.Controls.Flyouts;
|
||||||
|
using Artemis.UI.Shared.Providers;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Data;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using FluentAvalonia.Core;
|
||||||
|
using Button = FluentAvalonia.UI.Controls.Button;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Controls.GradientPicker;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a gradient picker box that can be used to edit a gradient
|
||||||
|
/// </summary>
|
||||||
|
public class GradientPickerButton : TemplatedControl
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the color gradient.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<ColorGradient?> ColorGradientProperty =
|
||||||
|
AvaloniaProperty.Register<GradientPickerButton, ColorGradient?>(nameof(ColorGradient), notifying: ColorGradientChanged, defaultValue: ColorGradient.GetUnicornBarf());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<bool> IsCompactProperty =
|
||||||
|
AvaloniaProperty.Register<GradientPickerButton, bool>(nameof(IsCompact), defaultBindingMode: BindingMode.TwoWay);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a storage provider to use for storing and loading gradients.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly StyledProperty<IColorGradientStorageProvider?> StorageProviderProperty =
|
||||||
|
AvaloniaProperty.Register<GradientPickerButton, IColorGradientStorageProvider?>(nameof(StorageProvider));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the linear gradient brush representing the color gradient.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DirectProperty<GradientPickerButton, LinearGradientBrush> LinearGradientBrushProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<GradientPickerButton, LinearGradientBrush>(nameof(LinearGradientBrush), g => g.LinearGradientBrush);
|
||||||
|
|
||||||
|
private ColorGradient? _lastColorGradient;
|
||||||
|
private Button? _button;
|
||||||
|
private GradientPickerFlyout? _flyout;
|
||||||
|
private bool _flyoutActive;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the color gradient.
|
||||||
|
/// </summary>
|
||||||
|
public ColorGradient? ColorGradient
|
||||||
|
{
|
||||||
|
get => GetValue(ColorGradientProperty);
|
||||||
|
set => SetValue(ColorGradientProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a boolean indicating whether the gradient picker should be in compact mode or not.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsCompact
|
||||||
|
{
|
||||||
|
get => GetValue(IsCompactProperty);
|
||||||
|
set => SetValue(IsCompactProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a storage provider to use for storing and loading gradients.
|
||||||
|
/// </summary>
|
||||||
|
public IColorGradientStorageProvider? StorageProvider
|
||||||
|
{
|
||||||
|
get => GetValue(StorageProviderProperty);
|
||||||
|
set => SetValue(StorageProviderProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the linear gradient brush representing the color gradient.
|
||||||
|
/// </summary>
|
||||||
|
public LinearGradientBrush LinearGradientBrush { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the flyout opens.
|
||||||
|
/// </summary>
|
||||||
|
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutOpened;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the flyout closes.
|
||||||
|
/// </summary>
|
||||||
|
public event TypedEventHandler<GradientPickerButton, EventArgs>? FlyoutClosed;
|
||||||
|
|
||||||
|
#region Overrides of TemplatedControl
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_button != null)
|
||||||
|
_button.Click -= OnButtonClick;
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
_button = e.NameScope.Find<Button>("MainButton");
|
||||||
|
if (_button != null)
|
||||||
|
_button.Click += OnButtonClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private static void ColorGradientChanged(IAvaloniaObject sender, bool before)
|
||||||
|
{
|
||||||
|
(sender as GradientPickerButton)?.Subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Subscribe()
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
|
||||||
|
if (ColorGradient != null)
|
||||||
|
{
|
||||||
|
ColorGradient.CollectionChanged += ColorGradientOnCollectionChanged;
|
||||||
|
ColorGradient.StopChanged += ColorGradientOnStopChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateGradient();
|
||||||
|
_lastColorGradient = ColorGradient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unsubscribe()
|
||||||
|
{
|
||||||
|
if (_lastColorGradient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastColorGradient.CollectionChanged -= ColorGradientOnCollectionChanged;
|
||||||
|
_lastColorGradient.StopChanged -= ColorGradientOnStopChanged;
|
||||||
|
_lastColorGradient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ColorGradientOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ColorGradientOnStopChanged(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
UpdateGradient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_flyout == null || ColorGradient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Logic here is taken from Fluent Avalonia's ColorPicker which also reuses the same control since it's large
|
||||||
|
_flyout.GradientPicker.ColorGradient = ColorGradient;
|
||||||
|
_flyout.GradientPicker.IsCompact = IsCompact;
|
||||||
|
_flyout.GradientPicker.StorageProvider = StorageProvider;
|
||||||
|
|
||||||
|
_flyout.ShowAt(this);
|
||||||
|
_flyoutActive = true;
|
||||||
|
|
||||||
|
FlyoutOpened?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void OnFlyoutClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_flyoutActive)
|
||||||
|
{
|
||||||
|
FlyoutClosed?.Invoke(this, EventArgs.Empty);
|
||||||
|
_flyoutActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateGradient()
|
||||||
|
{
|
||||||
|
// Update the display gradient
|
||||||
|
GradientStops collection = new();
|
||||||
|
if (ColorGradient != null)
|
||||||
|
{
|
||||||
|
foreach (ColorGradientStop c in ColorGradient.OrderBy(s => s.Position))
|
||||||
|
collection.Add(new GradientStop(Color.FromArgb(c.Color.Alpha, c.Color.Red, c.Color.Green, c.Color.Blue), c.Position));
|
||||||
|
}
|
||||||
|
|
||||||
|
LinearGradientBrush.GradientStops = collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of Visual
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
Subscribe();
|
||||||
|
|
||||||
|
if (_flyout == null)
|
||||||
|
{
|
||||||
|
_flyout = new GradientPickerFlyout();
|
||||||
|
_flyout.FlyoutPresenterClasses.Add("gradient-picker-presenter");
|
||||||
|
}
|
||||||
|
|
||||||
|
_flyout.Closed += OnFlyoutClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
if (_flyout != null)
|
||||||
|
_flyout.Closed -= OnFlyoutClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -29,7 +29,7 @@ public class GradientPickerColorStop : TemplatedControl
|
|||||||
else if (self.GradientPicker != null)
|
else if (self.GradientPicker != null)
|
||||||
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
|
self.GradientPicker.PropertyChanged += self.GradientPickerOnPropertyChanged;
|
||||||
|
|
||||||
self.IsSelected = self.GradientPicker?.SelectedColorStop == self.ColorStop;
|
self.IsSelected = ReferenceEquals(self.GradientPicker?.SelectedColorStop, self.ColorStop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Converters;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts <see cref="T:SkiaSharp.SKColor" /> into <see cref="T:Avalonia.Media.SolidColorBrush" />.
|
||||||
|
/// </summary>
|
||||||
|
public class SKColorToBrushConverter : IValueConverter
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is SKColor skColor)
|
||||||
|
return new SolidColorBrush(new Color(skColor.Alpha, skColor.Red, skColor.Green, skColor.Blue));
|
||||||
|
return new SolidColorBrush(Colors.Transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is SolidColorBrush brush)
|
||||||
|
return new SKColor(brush.Color.R, brush.Color.G, brush.Color.B, brush.Color.A);
|
||||||
|
return SKColor.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Providers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a provider for color gradient storage.
|
||||||
|
/// </summary>
|
||||||
|
public interface IColorGradientStorageProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a list containing a copy of all stored color gradients.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="List{T}" /> of <see cref="ColorGradient" /> containing a copy of all stored color gradients.</returns>
|
||||||
|
public List<ColorGradient> GetColorGradients();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the provided color gradient to storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="colorGradient">The color gradient to save.</param>
|
||||||
|
public void SaveColorGradient(ColorGradient colorGradient);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the provided color gradient from storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="colorGradient">The color gradient to delete.</param>
|
||||||
|
public void DeleteColorGradient(ColorGradient colorGradient);
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Reactive.Disposables;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
using Avalonia.Controls.Mixins;
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using ReactiveUI.Validation.Helpers;
|
using ReactiveUI.Validation.Helpers;
|
||||||
@ -18,9 +18,9 @@ namespace Artemis.UI.Shared.Services.PropertyInput;
|
|||||||
public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
||||||
{
|
{
|
||||||
[AllowNull] private T _inputValue;
|
[AllowNull] private T _inputValue;
|
||||||
|
|
||||||
private LayerPropertyPreview<T>? _preview;
|
private LayerPropertyPreview<T>? _preview;
|
||||||
|
|
||||||
private TimeSpan _time;
|
|
||||||
private bool _updating;
|
private bool _updating;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,7 +36,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
|
|
||||||
this.WhenActivated(d =>
|
this.WhenActivated(d =>
|
||||||
{
|
{
|
||||||
ProfileEditorService.Time.Subscribe(t => _time = t).DisposeWith(d);
|
ProfileEditorService.Time.Subscribe(t => Time = t).DisposeWith(d);
|
||||||
UpdateInputValue();
|
UpdateInputValue();
|
||||||
|
|
||||||
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.Updated += x, x => LayerProperty.Updated -= x)
|
Observable.FromEventPattern<LayerPropertyEventArgs>(x => LayerProperty.Updated += x, x => LayerProperty.Updated -= x)
|
||||||
@ -51,6 +51,8 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
Observable.FromEventPattern<DataBindingEventArgs>(x => LayerProperty.DataBinding.DataBindingDisabled += x, x => LayerProperty.DataBinding.DataBindingDisabled -= x)
|
Observable.FromEventPattern<DataBindingEventArgs>(x => LayerProperty.DataBinding.DataBindingDisabled += x, x => LayerProperty.DataBinding.DataBindingDisabled -= x)
|
||||||
.Subscribe(_ => UpdateDataBinding())
|
.Subscribe(_ => UpdateDataBinding())
|
||||||
.DisposeWith(d);
|
.DisposeWith(d);
|
||||||
|
|
||||||
|
Disposable.Create(DiscardPreview).DisposeWith(d);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,34 +109,39 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Affix => LayerProperty.PropertyDescription.InputAffix;
|
public string? Affix => LayerProperty.PropertyDescription.InputAffix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current time at which the property is being edited
|
||||||
|
/// </summary>
|
||||||
|
protected TimeSpan Time { get; private set; }
|
||||||
|
|
||||||
internal override object InternalGuard { get; } = new();
|
internal override object InternalGuard { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts the preview of the current property, allowing updates without causing real changes to the property.
|
/// Starts the preview of the current property, allowing updates without causing real changes to the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void StartPreview()
|
public virtual void StartPreview()
|
||||||
{
|
{
|
||||||
_preview?.DiscardPreview();
|
_preview?.DiscardPreview();
|
||||||
_preview = new LayerPropertyPreview<T>(LayerProperty, _time);
|
_preview = new LayerPropertyPreview<T>(LayerProperty, Time);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies the current preview to the property.
|
/// Applies the current preview to the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ApplyPreview()
|
public virtual void ApplyPreview()
|
||||||
{
|
{
|
||||||
if (_preview == null)
|
if (_preview == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_preview.DiscardPreview() && _preview.PreviewValue != null)
|
if (_preview.DiscardPreview() && _preview.PreviewValue != null)
|
||||||
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, _time));
|
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _preview.PreviewValue, Time));
|
||||||
_preview = null;
|
_preview = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Discard the preview of the property.
|
/// Discard the preview of the property.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void DiscardPreview()
|
public virtual void DiscardPreview()
|
||||||
{
|
{
|
||||||
if (_preview == null)
|
if (_preview == null)
|
||||||
return;
|
return;
|
||||||
@ -169,7 +176,7 @@ public abstract class PropertyInputViewModel<T> : PropertyInputViewModel
|
|||||||
if (_preview != null)
|
if (_preview != null)
|
||||||
_preview.Preview(_inputValue);
|
_preview.Preview(_inputValue);
|
||||||
else
|
else
|
||||||
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, _time));
|
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<T>(LayerProperty, _inputValue, Time));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateInputValue()
|
private void UpdateInputValue()
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</VisualBrush.Visual>
|
</VisualBrush.Visual>
|
||||||
</VisualBrush>
|
</VisualBrush>
|
||||||
<VisualBrush x:Key="LargeCheckerboardBrush" TileMode="Tile" Stretch="Uniform" SourceRect="0,0,25,25">
|
<VisualBrush x:Key="LargeCheckerboardBrush" TileMode="Tile" Stretch="Uniform" SourceRect="0,0,25,25">
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
||||||
@ -33,6 +33,7 @@
|
|||||||
<StyleInclude Source="/Styles/TreeView.axaml" />
|
<StyleInclude Source="/Styles/TreeView.axaml" />
|
||||||
|
|
||||||
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
<StyleInclude Source="/Styles/Controls/GradientPicker.axaml" />
|
||||||
|
<StyleInclude Source="/Styles/Controls/GradientPickerButton.axaml" />
|
||||||
|
|
||||||
<Style Selector="Window:windows:windows10 /template/ Border#RootBorder">
|
<Style Selector="Window:windows:windows10 /template/ Border#RootBorder">
|
||||||
<!-- This will show if custom accent color setting is used in Settings page-->
|
<!-- This will show if custom accent color setting is used in Settings page-->
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls">
|
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls"
|
||||||
|
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker">
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<Border Padding="50">
|
<Border Padding="50">
|
||||||
<StackPanel Spacing="5">
|
<StackPanel Spacing="5">
|
||||||
@ -28,6 +29,9 @@
|
|||||||
|
|
||||||
<controls:ColorPickerButton Color="Firebrick"></controls:ColorPickerButton>
|
<controls:ColorPickerButton Color="Firebrick"></controls:ColorPickerButton>
|
||||||
<controls:ColorPickerButton Color="Firebrick" Classes="condensed"></controls:ColorPickerButton>
|
<controls:ColorPickerButton Color="Firebrick" Classes="condensed"></controls:ColorPickerButton>
|
||||||
|
|
||||||
|
<gradientPicker:GradientPickerButton></gradientPicker:GradientPickerButton>
|
||||||
|
<gradientPicker:GradientPickerButton Classes="condensed"></gradientPicker:GradientPickerButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
@ -65,4 +69,10 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="gradientPicker|GradientPickerButton.condensed">
|
||||||
|
<Setter Property="Padding" Value="4 2" />
|
||||||
|
<Setter Property="FontSize" Value="13" />
|
||||||
|
<Setter Property="MinHeight" Value="24" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
</Styles>
|
</Styles>
|
||||||
@ -3,10 +3,22 @@
|
|||||||
xmlns:controls="using:Artemis.UI.Shared.Controls.GradientPicker"
|
xmlns:controls="using:Artemis.UI.Shared.Controls.GradientPicker"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||||
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:fluent="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters">
|
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters">
|
||||||
|
<Styles.Resources>
|
||||||
|
<VisualBrush x:Key="LightCheckerboardBrush" TileMode="Tile" Stretch="Uniform" DestinationRect="3,0,10,10">
|
||||||
|
<VisualBrush.Visual>
|
||||||
|
<Grid Width="10" Height="10" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||||
|
<Rectangle Grid.Row="0" Grid.Column="0" Fill="#c3c3c3" />
|
||||||
|
<Rectangle Grid.Row="0" Grid.Column="1" Fill="White" />
|
||||||
|
<Rectangle Grid.Row="1" Grid.Column="0" Fill="White" />
|
||||||
|
<Rectangle Grid.Row="1" Grid.Column="1" Fill="#c3c3c3" />
|
||||||
|
</Grid>
|
||||||
|
</VisualBrush.Visual>
|
||||||
|
</VisualBrush>
|
||||||
|
</Styles.Resources>
|
||||||
<Design.PreviewWith>
|
<Design.PreviewWith>
|
||||||
<controls:GradientPicker Width="800" />
|
<controls:GradientPicker />
|
||||||
</Design.PreviewWith>
|
</Design.PreviewWith>
|
||||||
|
|
||||||
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle">
|
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle">
|
||||||
@ -15,17 +27,19 @@
|
|||||||
<Setter Property="Height" Value="60" />
|
<Setter Property="Height" Value="60" />
|
||||||
<Setter Property="Margin" Value="-9 -10 0 0"></Setter>
|
<Setter Property="Margin" Value="-9 -10 0 0"></Setter>
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource ToolTipBorderBrush}" />
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border">
|
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border">
|
||||||
<Setter Property="CornerRadius" Value="18" />
|
<Setter Property="CornerRadius" Value="18" />
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
<Setter Property="BorderThickness" Value="3" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorQuarternaryBrush}" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource LightCheckerboardBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border > Border">
|
<Style Selector="controls|GradientPickerColorStop /template/ Border.stop-handle > Border > Border">
|
||||||
<Setter Property="CornerRadius" Value="18" />
|
<Setter Property="CornerRadius" Value="18" />
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
|
<Setter Property="Margin" Value="-1" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource SolidBackgroundFillColorSecondaryBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
@ -33,17 +47,25 @@
|
|||||||
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorLight2}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource SystemAccentColorLight2}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.stop-position">
|
<Style Selector="controls|GradientPicker Border#Gradient">
|
||||||
|
<Setter Property="Height" Value="40" />
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="controls|GradientPicker Border#Gradient Border">
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="controls|GradientPicker Border.stop-position">
|
||||||
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
|
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
|
||||||
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
|
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="Width" Value="40" />
|
<Setter Property="Width" Value="40" />
|
||||||
<Setter Property="Height" Value="30" />
|
<Setter Property="Height" Value="30" />
|
||||||
<Setter Property="Margin" Value="-20 -15 0 0"></Setter>
|
<Setter Property="Margin" Value="-20 -15 0 0" />
|
||||||
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Border.stop-position TextBlock">
|
<Style Selector="controls|GradientPicker Border.stop-position TextBlock">
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||||
</Style>
|
</Style>
|
||||||
@ -52,79 +74,21 @@
|
|||||||
<Style.Resources>
|
<Style.Resources>
|
||||||
<converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
|
<converters:ColorGradientToGradientStopsConverter x:Key="ColorGradientToGradientStopsConverter" />
|
||||||
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||||
|
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
|
||||||
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
|
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
|
||||||
<converters:WidthNormalizedConverter x:Key="WidthNormalizedConverter" />
|
<converters:WidthNormalizedConverter x:Key="WidthNormalizedConverter" />
|
||||||
</Style.Resources>
|
</Style.Resources>
|
||||||
|
<Setter Property="Focusable" Value="True" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Grid>
|
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="Auto,255" Margin="20">
|
||||||
<Border Name="Background" Background="{TemplateBinding LinearGradientBrush}" />
|
<Border Name="Gradient"
|
||||||
<Border Classes="card" Margin="20" Padding="20" Background="{DynamicResource SolidBackgroundFillColorBaseBrush}">
|
Grid.Row="0"
|
||||||
<Grid RowDefinitions="Auto,Auto,*" ColumnDefinitions="Auto,*">
|
Grid.ColumnSpan="2"
|
||||||
<Border Name="Gradient" Grid.Row="0" Grid.ColumnSpan="2" Height="40" CornerRadius="4" Background="{TemplateBinding LinearGradientBrush}">
|
Background="{DynamicResource LightCheckerboardBrush}"
|
||||||
<ItemsControl Items="{TemplateBinding ColorGradient}" ClipToBounds="False">
|
Margin="5 0">
|
||||||
<ItemsControl.Styles>
|
<Border Background="{TemplateBinding LinearGradientBrush}">
|
||||||
<Style Selector="ContentPresenter">
|
<ItemsControl Items="{TemplateBinding ColorGradient}" ClipToBounds="False">
|
||||||
<Setter Property="Canvas.Left">
|
|
||||||
<Setter.Value>
|
|
||||||
<MultiBinding Converter="{StaticResource WidthNormalizedConverter}">
|
|
||||||
<Binding Path="Position" />
|
|
||||||
<Binding Path="Bounds.Width" ElementName="Gradient" />
|
|
||||||
</MultiBinding>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ItemsControl.Styles>
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate DataType="core:ColorGradientStop">
|
|
||||||
<controls:GradientPickerColorStop ColorStop="{Binding}"
|
|
||||||
GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}"
|
|
||||||
PositionReference="{Binding $parent[Border]}">
|
|
||||||
<controls:GradientPickerColorStop.Styles>
|
|
||||||
<Style Selector="controls|GradientPickerColorStop">
|
|
||||||
<Setter Property="ClipToBounds" Value="False"></Setter>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<ControlTemplate>
|
|
||||||
<Border Classes="stop-handle">
|
|
||||||
<Border>
|
|
||||||
<Border>
|
|
||||||
<Border.Background>
|
|
||||||
<SolidColorBrush Color="{CompiledBinding Color, Converter={StaticResource SKColorToColorConverter}}" />
|
|
||||||
</Border.Background>
|
|
||||||
</Border>
|
|
||||||
</Border>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</controls:GradientPickerColorStop.Styles>
|
|
||||||
</controls:GradientPickerColorStop>
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
<ItemsControl.ItemsPanel>
|
|
||||||
<ItemsPanelTemplate>
|
|
||||||
<Canvas />
|
|
||||||
</ItemsPanelTemplate>
|
|
||||||
</ItemsControl.ItemsPanel>
|
|
||||||
</ItemsControl>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<Border Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
Grid.ColumnSpan="2"
|
|
||||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
|
||||||
BorderThickness="0 0 0 1"
|
|
||||||
Margin="-20 30"
|
|
||||||
Height="2"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<ItemsControl Grid.Row="1"
|
|
||||||
Grid.Column="0"
|
|
||||||
Grid.ColumnSpan="2"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Items="{TemplateBinding ColorGradient}"
|
|
||||||
ClipToBounds="False">
|
|
||||||
<ItemsControl.Styles>
|
<ItemsControl.Styles>
|
||||||
<Style Selector="ContentPresenter">
|
<Style Selector="ContentPresenter">
|
||||||
<Setter Property="Canvas.Left">
|
<Setter Property="Canvas.Left">
|
||||||
@ -139,9 +103,11 @@
|
|||||||
</ItemsControl.Styles>
|
</ItemsControl.Styles>
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="core:ColorGradientStop">
|
<DataTemplate DataType="core:ColorGradientStop">
|
||||||
<Border Classes="stop-position">
|
<controls:GradientPickerColorStop ColorStop="{Binding}"
|
||||||
<TextBlock Text="{Binding Position}"></TextBlock>
|
PositionReference="{Binding $parent[Border]}"
|
||||||
</Border>
|
Classes="gradient-handle"
|
||||||
|
GradientPicker="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}">
|
||||||
|
</controls:GradientPickerColorStop>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl.ItemsPanel>
|
||||||
@ -150,43 +116,147 @@
|
|||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsControl.ItemsPanel>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
</Border>
|
||||||
<controls1:ColorPicker Grid.Row="2" Grid.Column="0"
|
|
||||||
ColorTextType="HexAlpha"
|
|
||||||
UseColorWheel="True"
|
|
||||||
UseColorTriangle="True"
|
|
||||||
IsMoreButtonVisible="True"
|
|
||||||
IsCompact="True"
|
|
||||||
IsVisible="{TemplateBinding SelectedColorStop, Converter={x:Static ObjectConverters.IsNotNull}}"
|
|
||||||
Color="{Binding SelectedColorStop.Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource SKColorToColorConverter}}" />
|
|
||||||
|
|
||||||
<ListBox Grid.Row="2"
|
|
||||||
Grid.Column="1"
|
|
||||||
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
|
||||||
Items="{TemplateBinding ColorGradient}"
|
|
||||||
BorderThickness="1 0 0 0"
|
|
||||||
Margin="10 0 0 0">
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate DataType="core:ColorGradientStop">
|
|
||||||
<Grid VerticalAlignment="Center" ColumnDefinitions="34,*,Auto,Auto">
|
|
||||||
<Border Grid.Column="0" Width="28" Height="28" CornerRadius="4" HorizontalAlignment="Left">
|
|
||||||
<Border.Background>
|
|
||||||
<SolidColorBrush Color="{Binding Color, Converter={StaticResource SKColorToColorConverter}}"></SolidColorBrush>
|
|
||||||
</Border.Background>
|
|
||||||
</Border>
|
|
||||||
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}"></TextBox>
|
|
||||||
<NumericUpDown Grid.Column="2" Value="{Binding Position}" FormatString="F3" ShowButtonSpinner="False" Margin="5 0"></NumericUpDown>
|
|
||||||
<Button Grid.Column="3" Classes="icon-button">
|
|
||||||
<avalonia:MaterialIcon Kind="Close"></avalonia:MaterialIcon>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
|
BorderThickness="0 0 0 1"
|
||||||
|
Margin="-20 30"
|
||||||
|
Height="2"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ItemsControl Name="GradientPositions"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Items="{TemplateBinding ColorGradient}"
|
||||||
|
ClipToBounds="False"
|
||||||
|
Margin="5 0">
|
||||||
|
<ItemsControl.Styles>
|
||||||
|
<Style Selector="ContentPresenter">
|
||||||
|
<Setter Property="Canvas.Left">
|
||||||
|
<Setter.Value>
|
||||||
|
<MultiBinding Converter="{StaticResource WidthNormalizedConverter}">
|
||||||
|
<Binding Path="Position" />
|
||||||
|
<Binding Path="#GradientPositions.Bounds.Width" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.Styles>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="core:ColorGradientStop">
|
||||||
|
<Border Classes="stop-position">
|
||||||
|
<TextBlock Text="{Binding Position}"></TextBlock>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<Canvas />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
|
BorderThickness="0 0 1 0"
|
||||||
|
Padding="0 0 10 0">
|
||||||
|
<fluent:ColorPicker Name="ColorPicker"
|
||||||
|
ColorTextType="HexAlpha"
|
||||||
|
UseColorWheel="True"
|
||||||
|
UseColorTriangle="True"
|
||||||
|
IsMoreButtonVisible="True"
|
||||||
|
IsVisible="{TemplateBinding SelectedColorStop, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||||
|
IsCompact="{Binding IsCompact, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
Color="{Binding SelectedColorStop.Color, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource SKColorToColorConverter}}" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="2" Grid.Column="1" RowDefinitions="*,Auto">
|
||||||
|
<ListBox Grid.Row="0"
|
||||||
|
MaxHeight="280"
|
||||||
|
Items="{TemplateBinding ColorGradient}"
|
||||||
|
SelectedItem="{TemplateBinding SelectedColorStop, Mode=TwoWay}"
|
||||||
|
Padding="10 0 15 0">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate DataType="core:ColorGradientStop">
|
||||||
|
<Grid VerticalAlignment="Center"
|
||||||
|
ColumnDefinitions="34,*,Auto,Auto"
|
||||||
|
Margin="-5 0"
|
||||||
|
Classes="stop-list-item">
|
||||||
|
<Border Grid.Column="0" Width="28" Height="28" CornerRadius="4" HorizontalAlignment="Left"
|
||||||
|
BorderThickness="3"
|
||||||
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
|
ClipToBounds="True"
|
||||||
|
Background="{DynamicResource LightCheckerboardBrush}">
|
||||||
|
<Border CornerRadius="4" Margin="-2" Background="{Binding Color, Converter={StaticResource SKColorToBrushConverter}}" />
|
||||||
|
</Border>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding Color, Converter={StaticResource SKColorToStringConverter}}" />
|
||||||
|
<NumericUpDown Grid.Column="2" Value="{Binding Position}" FormatString="F3" ShowButtonSpinner="False" Margin="5 0" />
|
||||||
|
<Button Name="DeleteButton"
|
||||||
|
Grid.Column="3"
|
||||||
|
Classes="icon-button"
|
||||||
|
Command="{Binding DeleteStop, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GradientPicker}}}"
|
||||||
|
CommandParameter="{Binding}">
|
||||||
|
<avalonia:MaterialIcon Kind="Close" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Margin="10 5 0 0"
|
||||||
|
Padding="0 5 0 0"
|
||||||
|
BorderBrush="{DynamicResource ButtonBorderBrush}"
|
||||||
|
BorderThickness="0 1 0 0">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="5" HorizontalAlignment="Right">
|
||||||
|
<Button Name="SpreadStops" Classes="icon-button operation-button" ToolTip.Tip="Spread the color stops equally across the gradient.">
|
||||||
|
<avalonia:MaterialIcon Kind="ArrowLeftRight" />
|
||||||
|
</Button>
|
||||||
|
<Button Name="ToggleSeamless" Classes="icon-button operation-button"
|
||||||
|
ToolTip.Tip="Toggle the gradient being seamless by starting and ending with the same color.">
|
||||||
|
<avalonia:MaterialIcon Kind="SineWave" />
|
||||||
|
</Button>
|
||||||
|
<Button Name="FlipStops" Classes="icon-button operation-button" ToolTip.Tip="Flip the stops of the gradient.">
|
||||||
|
<avalonia:MaterialIcon Kind="FlipHorizontal" />
|
||||||
|
</Button>
|
||||||
|
<Button Name="RotateStops" Classes="icon-button operation-button">
|
||||||
|
<ToolTip.Tip>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock>Rotates the stops of the gradient.</TextBlock>
|
||||||
|
<TextBlock>Hold shift to change direction.</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</ToolTip.Tip>
|
||||||
|
<avalonia:MaterialIcon Kind="AxisZRotateCounterclockwise" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="controls|GradientPickerColorStop.gradient-handle">
|
||||||
|
<Style.Resources>
|
||||||
|
<converters:SKColorToBrushConverter x:Key="SKColorToBrushConverter" />
|
||||||
|
</Style.Resources>
|
||||||
|
<Setter Property="ClipToBounds" Value="False"></Setter>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Border Classes="stop-handle">
|
||||||
|
<Border>
|
||||||
|
<Border Background="{Binding Color, Converter={StaticResource SKColorToBrushConverter}}" />
|
||||||
|
</Border>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
</Styles>
|
</Styles>
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"
|
||||||
|
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||||
|
<Design.PreviewWith>
|
||||||
|
<Border Padding="20">
|
||||||
|
<gradientPicker:GradientPickerButton IsCompact="True" />
|
||||||
|
</Border>
|
||||||
|
</Design.PreviewWith>
|
||||||
|
|
||||||
|
<Style Selector="FlyoutPresenter.gradient-picker-presenter">
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="MaxWidth" Value="1200" />
|
||||||
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
<Setter Property="MaxHeight" Value="1200" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SolidBackgroundFillColorBaseBrush}" />
|
||||||
|
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||||
|
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="gradientPicker|GradientPickerButton">
|
||||||
|
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
|
||||||
|
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}"/>
|
||||||
|
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<controls:Button Grid.Column="0"
|
||||||
|
Name="MainButton"
|
||||||
|
Padding="0 0 25 0"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
HorizontalContentAlignment="Stretch"
|
||||||
|
VerticalContentAlignment="Stretch">
|
||||||
|
<Border BorderBrush="{DynamicResource ColorPickerButtonOutline}"
|
||||||
|
BorderThickness="1"
|
||||||
|
Margin="5"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="{TemplateBinding LinearGradientBrush}"
|
||||||
|
CornerRadius="{TemplateBinding CornerRadius}" />
|
||||||
|
</controls:Button>
|
||||||
|
|
||||||
|
<Viewbox Grid.Column="1"
|
||||||
|
Width="18"
|
||||||
|
Height="18"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="-25 0 8 0"
|
||||||
|
Name="Chevron">
|
||||||
|
<controls:SymbolIcon Symbol="ChevronDown" />
|
||||||
|
</Viewbox>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
</Styles>
|
||||||
@ -2,7 +2,15 @@
|
|||||||
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:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
|
||||||
|
xmlns:propertyInput="clr-namespace:Artemis.UI.DefaultTypes.PropertyInput"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView">
|
x:Class="Artemis.UI.DefaultTypes.PropertyInput.ColorGradientPropertyInputView"
|
||||||
TODO
|
x:DataType="propertyInput:ColorGradientPropertyInputViewModel">
|
||||||
</UserControl>
|
<gradientPicker:GradientPickerButton Classes="condensed"
|
||||||
|
Width="200"
|
||||||
|
ColorGradient="{CompiledBinding ColorGradient}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FlyoutOpened="GradientPickerButton_OnFlyoutOpened"
|
||||||
|
FlyoutClosed="GradientPickerButton_OnFlyoutClosed"/>
|
||||||
|
</UserControl>
|
||||||
@ -1,20 +1,29 @@
|
|||||||
using Avalonia;
|
using System;
|
||||||
using Avalonia.Controls;
|
using Artemis.UI.Shared.Controls.GradientPicker;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.DefaultTypes.PropertyInput
|
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
||||||
{
|
|
||||||
public partial class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel>
|
|
||||||
{
|
|
||||||
public ColorGradientPropertyInputView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
public class ColorGradientPropertyInputView : ReactiveUserControl<ColorGradientPropertyInputViewModel>
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
public ColorGradientPropertyInputView()
|
||||||
}
|
{
|
||||||
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientPickerButton_OnFlyoutOpened(GradientPickerButton sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ViewModel?.StartPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GradientPickerButton_OnFlyoutClosed(GradientPickerButton sender, EventArgs args)
|
||||||
|
{
|
||||||
|
ViewModel?.ApplyPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,19 +1,87 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Linq;
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
using Artemis.UI.Shared.Services.PropertyInput;
|
using Artemis.UI.Shared.Services.PropertyInput;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
||||||
|
|
||||||
public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient>
|
public class ColorGradientPropertyInputViewModel : PropertyInputViewModel<ColorGradient>
|
||||||
{
|
{
|
||||||
|
private ColorGradient _colorGradient;
|
||||||
|
private ColorGradient? _originalGradient;
|
||||||
|
|
||||||
public ColorGradientPropertyInputViewModel(LayerProperty<ColorGradient> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
|
public ColorGradientPropertyInputViewModel(LayerProperty<ColorGradient> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
|
||||||
: base(layerProperty, profileEditorService, propertyInputService)
|
: base(layerProperty, profileEditorService, propertyInputService)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DialogClosed(object sender, EventArgs e)
|
public ColorGradient ColorGradient
|
||||||
{
|
{
|
||||||
ApplyInputValue();
|
get => _colorGradient;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _colorGradient, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnInputValueChanged()
|
||||||
|
{
|
||||||
|
ColorGradient = new ColorGradient(InputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Overrides of PropertyInputViewModel<ColorGradient>
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void StartPreview()
|
||||||
|
{
|
||||||
|
_originalGradient = LayerProperty.CurrentValue;
|
||||||
|
|
||||||
|
// Set the property value to the gradient being edited by the picker, this will cause any updates to show right away because
|
||||||
|
// ColorGradient is a reference type
|
||||||
|
LayerProperty.CurrentValue = ColorGradient;
|
||||||
|
|
||||||
|
// This won't fly if we ever support keyframes but at that point ColorGradient would have to be a value type anyway and this
|
||||||
|
// whole VM no longer makes sense
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void ApplyInputValue()
|
||||||
|
{
|
||||||
|
// Don't do anything, ColorGradient is a reference type and will update regardless
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void ApplyPreview()
|
||||||
|
{
|
||||||
|
if (_originalGradient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make sure something actually changed
|
||||||
|
if (Equals(ColorGradient, _originalGradient))
|
||||||
|
{
|
||||||
|
LayerProperty.CurrentValue = _originalGradient;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Update the gradient for realsies, giving the command a reference to the old gradient
|
||||||
|
ProfileEditorService.ExecuteCommand(new UpdateLayerProperty<ColorGradient>(LayerProperty, ColorGradient, _originalGradient, Time));
|
||||||
|
}
|
||||||
|
|
||||||
|
_originalGradient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void DiscardPreview()
|
||||||
|
{
|
||||||
|
if (_originalGradient == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Put the old gradient back
|
||||||
|
InputValue = _originalGradient;
|
||||||
|
ColorGradient = new ColorGradient(InputValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
@ -28,7 +28,8 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
private double _dragOffsetY;
|
private double _dragOffsetY;
|
||||||
private SelectionStatus _selectionStatus;
|
private SelectionStatus _selectionStatus;
|
||||||
|
|
||||||
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory, IWindowService windowService)
|
public SurfaceDeviceViewModel(ArtemisDevice device, IRgbService rgbService, IDeviceService deviceService, ISettingsService settingsService, IDeviceVmFactory deviceVmFactory,
|
||||||
|
IWindowService windowService)
|
||||||
{
|
{
|
||||||
_rgbService = rgbService;
|
_rgbService = rgbService;
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
@ -77,29 +78,36 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
_dragOffsetY = Device.Y - mouseStartPosition.Y;
|
_dragOffsetY = Device.Y - mouseStartPosition.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMouseDrag(Point mousePosition)
|
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||||
{
|
{
|
||||||
if (SelectionStatus != SelectionStatus.Selected)
|
if (SelectionStatus != SelectionStatus.Selected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
float roundedX = (float) Math.Round((mousePosition.X + _dragOffsetX) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
float x = (float) (mousePosition.X + _dragOffsetX);
|
||||||
float roundedY = (float) Math.Round((mousePosition.Y + _dragOffsetY) / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
float y = (float) (mousePosition.Y + _dragOffsetY);
|
||||||
|
|
||||||
if (Fits(roundedX, roundedY))
|
if (round)
|
||||||
{
|
{
|
||||||
Device.X = roundedX;
|
x = (float) Math.Round(x / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||||
Device.Y = roundedY;
|
y = (float) Math.Round(y / 10d, 0, MidpointRounding.AwayFromZero) * 10f;
|
||||||
}
|
}
|
||||||
else if (Fits(roundedX, Device.Y))
|
|
||||||
|
|
||||||
|
if (Fits(x, y, ignoreOverlap))
|
||||||
{
|
{
|
||||||
Device.X = roundedX;
|
Device.X = x;
|
||||||
|
Device.Y = y;
|
||||||
}
|
}
|
||||||
else if (Fits(Device.X, roundedY))
|
else if (Fits(x, Device.Y, ignoreOverlap))
|
||||||
{
|
{
|
||||||
Device.Y = roundedY;
|
Device.X = x;
|
||||||
|
}
|
||||||
|
else if (Fits(Device.X, y, ignoreOverlap))
|
||||||
|
{
|
||||||
|
Device.Y = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExecuteIdentifyDevice(ArtemisDevice device)
|
private void ExecuteIdentifyDevice(ArtemisDevice device)
|
||||||
{
|
{
|
||||||
_deviceService.IdentifyDevice(device);
|
_deviceService.IdentifyDevice(device);
|
||||||
@ -110,7 +118,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device));
|
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(device));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Fits(float x, float y)
|
private bool Fits(float x, float y, bool ignoreOverlap)
|
||||||
{
|
{
|
||||||
if (x < 0 || y < 0)
|
if (x < 0 || y < 0)
|
||||||
return false;
|
return false;
|
||||||
@ -119,16 +127,16 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
|
if (x + Device.Rectangle.Width > maxTextureSize || y + Device.Rectangle.Height > maxTextureSize)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
List<SKRect> own = Device.Leds
|
if (ignoreOverlap)
|
||||||
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height))
|
return true;
|
||||||
.ToList();
|
|
||||||
List<SKRect> others = _rgbService.EnabledDevices
|
IEnumerable<SKRect> own = Device.Leds
|
||||||
|
.Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height));
|
||||||
|
IEnumerable<SKRect> others = _rgbService.EnabledDevices
|
||||||
.Where(d => d != Device && d.IsEnabled)
|
.Where(d => d != Device && d.IsEnabled)
|
||||||
.SelectMany(d => d.Leds)
|
.SelectMany(d => d.Leds)
|
||||||
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height))
|
.Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height));
|
||||||
.ToList();
|
|
||||||
|
|
||||||
|
|
||||||
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
|
return !own.Any(o => others.Any(l => l.IntersectsWith(o)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
{
|
{
|
||||||
if (!_dragging)
|
if (!_dragging)
|
||||||
ViewModel?.StartMouseDrag(e.GetPosition(_containerGrid));
|
ViewModel?.StartMouseDrag(e.GetPosition(_containerGrid));
|
||||||
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid));
|
ViewModel?.UpdateMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
|
||||||
}
|
}
|
||||||
|
|
||||||
_dragging = true;
|
_dragging = true;
|
||||||
@ -93,7 +93,7 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
|
|
||||||
if (ReferenceEquals(e.Pointer.Captured, sender))
|
if (ReferenceEquals(e.Pointer.Captured, sender))
|
||||||
{
|
{
|
||||||
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid));
|
ViewModel?.StopMouseDrag(e.GetPosition(_containerGrid), e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Alt));
|
||||||
e.Pointer.Capture(null);
|
e.Pointer.Capture(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,23 +61,22 @@ namespace Artemis.UI.Screens.SurfaceEditor
|
|||||||
startedOn.SelectionStatus = SelectionStatus.Selected;
|
startedOn.SelectionStatus = SelectionStatus.Selected;
|
||||||
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(vm => vm != startedOn))
|
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(vm => vm != startedOn))
|
||||||
device.SelectionStatus = SelectionStatus.None;
|
device.SelectionStatus = SelectionStatus.None;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||||
surfaceDeviceViewModel.StartMouseDrag(mousePosition);
|
surfaceDeviceViewModel.StartMouseDrag(mousePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateMouseDrag(Point mousePosition)
|
public void UpdateMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||||
{
|
{
|
||||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
|
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopMouseDrag(Point mousePosition)
|
public void StopMouseDrag(Point mousePosition, bool round, bool ignoreOverlap)
|
||||||
{
|
{
|
||||||
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels)
|
||||||
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition);
|
surfaceDeviceViewModel.UpdateMouseDrag(mousePosition, round, ignoreOverlap);
|
||||||
|
|
||||||
if (_saving)
|
if (_saving)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
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"
|
xmlns:workshop="clr-namespace:Artemis.UI.Screens.Workshop"
|
||||||
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
|
xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker;assembly=Artemis.UI.Shared"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800"
|
||||||
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
|
x:Class="Artemis.UI.Screens.Workshop.WorkshopView"
|
||||||
x:DataType="workshop:WorkshopViewModel">
|
x:DataType="workshop:WorkshopViewModel">
|
||||||
<Border Classes="router-container">
|
<Border Classes="router-container">
|
||||||
@ -47,7 +47,12 @@
|
|||||||
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
|
<controls:EnumComboBox Value="{CompiledBinding SelectedCursor}"></controls:EnumComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<gradientPicker:GradientPicker ColorGradient="{CompiledBinding ColorGradient}"></gradientPicker:GradientPicker>
|
<Button Command="{Binding CreateRandomGradient}">
|
||||||
|
Create random gradient
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
|
<gradientPicker:GradientPickerButton ColorGradient="{CompiledBinding ColorGradient}" IsCompact="True"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Artemis.UI.Shared.Services.Builders;
|
|||||||
using Artemis.UI.Shared.Services.Interfaces;
|
using Artemis.UI.Shared.Services.Interfaces;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.Workshop
|
namespace Artemis.UI.Screens.Workshop
|
||||||
{
|
{
|
||||||
@ -13,6 +14,15 @@ namespace Artemis.UI.Screens.Workshop
|
|||||||
private readonly INotificationService _notificationService;
|
private readonly INotificationService _notificationService;
|
||||||
private StandardCursorType _selectedCursor;
|
private StandardCursorType _selectedCursor;
|
||||||
private readonly ObservableAsPropertyHelper<Cursor> _cursor;
|
private readonly ObservableAsPropertyHelper<Cursor> _cursor;
|
||||||
|
private ColorGradient _colorGradient = new()
|
||||||
|
{
|
||||||
|
new ColorGradientStop(new SKColor(0xFFFF6D00), 0f),
|
||||||
|
new ColorGradientStop(new SKColor(0xFFFE6806), 0.2f),
|
||||||
|
new ColorGradientStop(new SKColor(0xFFEF1788), 0.4f),
|
||||||
|
new ColorGradientStop(new SKColor(0xFFEF1788), 0.6f),
|
||||||
|
new ColorGradientStop(new SKColor(0xFF00FCCC), 0.8f),
|
||||||
|
new ColorGradientStop(new SKColor(0xFF00FCCC), 1f),
|
||||||
|
};
|
||||||
|
|
||||||
public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop")
|
public WorkshopViewModel(IScreen hostScreen, INotificationService notificationService) : base(hostScreen, "workshop")
|
||||||
{
|
{
|
||||||
@ -33,7 +43,16 @@ namespace Artemis.UI.Screens.Workshop
|
|||||||
|
|
||||||
public Cursor Cursor => _cursor.Value;
|
public Cursor Cursor => _cursor.Value;
|
||||||
|
|
||||||
public ColorGradient ColorGradient { get; set; } = ColorGradient.GetUnicornBarf();
|
public ColorGradient ColorGradient
|
||||||
|
{
|
||||||
|
get => _colorGradient;
|
||||||
|
set => RaiseAndSetIfChanged(ref _colorGradient, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateRandomGradient()
|
||||||
|
{
|
||||||
|
ColorGradient = ColorGradient.GetRandom(6);
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteShowNotification(NotificationSeverity severity)
|
private void ExecuteShowNotification(NotificationSeverity severity)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user