mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 21:38:38 +00:00
Dynamic properties WIP
This commit is contained in:
parent
8394fbc418
commit
3e47ffeff7
@ -418,8 +418,9 @@
|
||||
<Compile Include="Modules\Games\Dota2\Dota2ViewModel.cs" />
|
||||
<Compile Include="Modules\Games\RocketLeague\RocketLeagueViewModel.cs" />
|
||||
<Compile Include="Modules\Games\Witcher3\Witcher3ViewModel.cs" />
|
||||
<Compile Include="ViewModels\LayerEditorViewModel.cs" />
|
||||
<Compile Include="ViewModels\LayerEditor\LayerConditionViewModel.cs" />
|
||||
<Compile Include="ViewModels\LayerEditor\LayerDynamicPropertiesViewModel.cs" />
|
||||
<Compile Include="ViewModels\LayerEditor\LayerEditorViewModel.cs" />
|
||||
<Compile Include="ViewModels\OverlaysViewModel.cs" />
|
||||
<Compile Include="Modules\Overlays\VolumeDisplay\VolumeDisplayViewModel.cs" />
|
||||
<Compile Include="ViewModels\ProfileEditorViewModel.cs" />
|
||||
@ -459,12 +460,15 @@
|
||||
<Compile Include="Modules\Games\Witcher3\Witcher3View.xaml.cs">
|
||||
<DependentUpon>Witcher3View.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\LayerEditorView.xaml.cs">
|
||||
<DependentUpon>LayerEditorView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\LayerEditor\LayerConditionView.xaml.cs">
|
||||
<DependentUpon>LayerConditionView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\LayerEditor\LayerDynamicPropertiesView.xaml.cs">
|
||||
<DependentUpon>LayerDynamicPropertiesView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\LayerEditor\LayerEditorView.xaml.cs">
|
||||
<DependentUpon>LayerEditorView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Views\OverlaysView.xaml.cs">
|
||||
<DependentUpon>OverlaysView.xaml</DependentUpon>
|
||||
</Compile>
|
||||
@ -623,14 +627,18 @@
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LayerEditorView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LayerEditor\LayerConditionView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LayerEditor\LayerDynamicPropertiesView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="Views\LayerEditor\LayerEditorView.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="Views\OverlaysView.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interactivity;
|
||||
|
||||
@ -44,7 +45,42 @@ namespace Artemis.ItemBehaviours
|
||||
{
|
||||
var item = ((BindableSelectedItemBehavior) sender).AssociatedObject
|
||||
.ItemContainerGenerator.ContainerFromItem(e.NewValue) as TreeViewItem;
|
||||
item?.SetValue(TreeViewItem.IsSelectedProperty, true);
|
||||
if (item != null)
|
||||
item.SetValue(TreeViewItem.IsSelectedProperty, true);
|
||||
else
|
||||
ClearTreeViewSelection(((BindableSelectedItemBehavior) sender).AssociatedObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a TreeView's selected item recursively
|
||||
/// Tom Wright - http://stackoverflow.com/a/1406116/5015269
|
||||
/// </summary>
|
||||
/// <param name="tv"></param>
|
||||
public static void ClearTreeViewSelection(TreeView tv)
|
||||
{
|
||||
if (tv != null)
|
||||
ClearTreeViewItemsControlSelection(tv.Items, tv.ItemContainerGenerator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a TreeView's selected item recursively
|
||||
/// Tom Wright - http://stackoverflow.com/a/1406116/5015269
|
||||
/// </summary>
|
||||
/// <param name="ic"></param>
|
||||
/// <param name="icg"></param>
|
||||
private static void ClearTreeViewItemsControlSelection(ICollection ic, ItemContainerGenerator icg)
|
||||
{
|
||||
if ((ic == null) || (icg == null))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < ic.Count; i++)
|
||||
{
|
||||
var tvi = icg.ContainerFromIndex(i) as TreeViewItem;
|
||||
if (tvi == null)
|
||||
continue;
|
||||
ClearTreeViewItemsControlSelection(tvi.Items, tvi.ItemContainerGenerator);
|
||||
tvi.IsSelected = false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -1,68 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq.Dynamic;
|
||||
using System.Reflection;
|
||||
using System.ComponentModel;
|
||||
using Artemis.Models.Interfaces;
|
||||
using Artemis.Utilities;
|
||||
using static System.Decimal;
|
||||
|
||||
namespace Artemis.Models.Profiles
|
||||
{
|
||||
public class LayerDynamicPropertiesModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Property this dynamic property applies on
|
||||
/// </summary>
|
||||
public string LayerProperty { get; set; }
|
||||
public string GameProperty { get; set; }
|
||||
public string RequiredOperator { get; set; }
|
||||
public string RequiredValue { get; set; }
|
||||
public LayerPopertyType LayerPopertyType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only used when LayerPropertyType is PercentageOf or PercentageOfProperty
|
||||
/// Property to base the percentage upon
|
||||
/// </summary>
|
||||
public string GameProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Percentage source, the number that defines 100%
|
||||
/// </summary>
|
||||
public string PercentageSource { get; set; }
|
||||
|
||||
internal void ApplyProperty<T>(IGameDataModel dataModel, LayerPropertiesModel userProps,
|
||||
LayerPropertiesModel props)
|
||||
/// <summary>
|
||||
/// Type of property
|
||||
/// </summary>
|
||||
public LayerPropertyType LayerPropertyType { get; set; }
|
||||
|
||||
internal void ApplyProperty<T>(IGameDataModel data, LayerPropertiesModel userProps, LayerPropertiesModel props)
|
||||
{
|
||||
var dataList = new List<T> {(T) dataModel};
|
||||
|
||||
// Attempt to set the property
|
||||
var layerProp = props.GetType().GetProperty(LayerProperty);
|
||||
var layerUserProp = userProps.GetType().GetProperty(LayerProperty);
|
||||
|
||||
if (LayerPopertyType == LayerPopertyType.PercentageOf)
|
||||
SetPercentageOf(props, userProps, dataModel, int.Parse(PercentageSource));
|
||||
if (LayerPopertyType == LayerPopertyType.PercentageOfProperty)
|
||||
SetPercentageOfProperty(props, userProps, dataModel);
|
||||
if (LayerPropertyType == LayerPropertyType.PercentageOf)
|
||||
Apply(props, userProps, data, int.Parse(PercentageSource));
|
||||
if (LayerPropertyType == LayerPropertyType.PercentageOfProperty)
|
||||
ApplyProp(props, userProps, data);
|
||||
}
|
||||
|
||||
private void SetPercentageOf(LayerPropertiesModel props, LayerPropertiesModel userProps,
|
||||
IGameDataModel dataModel, int percentageSource)
|
||||
private void Apply(LayerPropertiesModel props, LayerPropertiesModel userProps, IGameDataModel data,
|
||||
int percentageSource)
|
||||
{
|
||||
// Property that will be set
|
||||
// Property to apply on
|
||||
var layerProp = props.GetType().GetProperty(LayerProperty);
|
||||
// Property to use as a 100%
|
||||
// User's settings
|
||||
var userProp = userProps.GetType().GetProperty(LayerProperty);
|
||||
// Value to use as a source
|
||||
var source = dataModel.GetType().GetProperty(GameProperty)?.GetValue(dataModel, null);
|
||||
if (layerProp == null || userProp == null || source == null)
|
||||
// Property to base the percentage upon
|
||||
var gameProperty = data.GetPropValue<int>(GameProperty);
|
||||
if (layerProp == null || userProp == null)
|
||||
return;
|
||||
|
||||
var percentage = double.Parse(source.ToString())/percentageSource;
|
||||
var percentage = ToDouble(gameProperty) / percentageSource;
|
||||
layerProp.SetValue(props, (int) (percentage*(int) userProp.GetValue(userProps, null)));
|
||||
}
|
||||
|
||||
private void SetPercentageOfProperty(LayerPropertiesModel props, LayerPropertiesModel userProps,
|
||||
IGameDataModel dataModel)
|
||||
private void ApplyProp(LayerPropertiesModel props, LayerPropertiesModel userProps, IGameDataModel data)
|
||||
{
|
||||
var value = dataModel.GetType().GetProperty(PercentageSource)?.GetValue(dataModel, null);
|
||||
if (value != null)
|
||||
SetPercentageOf(props, userProps, dataModel, (int) value);
|
||||
var value = data.GetPropValue<int>(PercentageSource);
|
||||
Apply(props, userProps, data, value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum LayerPopertyType
|
||||
public enum LayerPropertyType
|
||||
{
|
||||
PercentageOf,
|
||||
PercentageOfProperty,
|
||||
Color
|
||||
[Description("None")] None,
|
||||
[Description("% of")] PercentageOf,
|
||||
[Description("% of property")] PercentageOfProperty
|
||||
}
|
||||
}
|
||||
@ -49,22 +49,6 @@ namespace Artemis.Models.Profiles
|
||||
[XmlIgnore]
|
||||
public ProfileModel ParentProfile { get; internal set; }
|
||||
|
||||
#region IChildItem<Parent> Members
|
||||
|
||||
LayerModel IChildItem<LayerModel>.Parent
|
||||
{
|
||||
get { return ParentLayer; }
|
||||
set { ParentLayer = value; }
|
||||
}
|
||||
|
||||
ProfileModel IChildItem<ProfileModel>.Parent
|
||||
{
|
||||
get { return ParentProfile; }
|
||||
set { ParentProfile = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool ConditionsMet<T>(IGameDataModel dataModel)
|
||||
{
|
||||
return Enabled && LayerConditions.All(cm => cm.ConditionMet<T>(dataModel));
|
||||
@ -116,7 +100,7 @@ namespace Artemis.Models.Profiles
|
||||
{
|
||||
// Fix the sorting just in case
|
||||
FixOrder();
|
||||
|
||||
|
||||
int newOrder;
|
||||
if (moveUp)
|
||||
newOrder = selectedLayer.Order - 1;
|
||||
@ -137,6 +121,22 @@ namespace Artemis.Models.Profiles
|
||||
for (var i = 0; i < Children.Count; i++)
|
||||
Children[i].Order = i;
|
||||
}
|
||||
|
||||
#region IChildItem<Parent> Members
|
||||
|
||||
LayerModel IChildItem<LayerModel>.Parent
|
||||
{
|
||||
get { return ParentLayer; }
|
||||
set { ParentLayer = value; }
|
||||
}
|
||||
|
||||
ProfileModel IChildItem<ProfileModel>.Parent
|
||||
{
|
||||
get { return ParentProfile; }
|
||||
set { ParentProfile = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum LayerType
|
||||
|
||||
@ -68,6 +68,8 @@ namespace Artemis.Modules.Games.CounterStrike
|
||||
if (!jsonString.Contains("Counter-Strike: Global Offensive"))
|
||||
return;
|
||||
|
||||
|
||||
Debug.WriteLine("Got data");
|
||||
// Parse the JSON
|
||||
GameDataModel = JsonConvert.DeserializeObject<CounterStrikeDataModel>(jsonString);
|
||||
}
|
||||
|
||||
@ -24,5 +24,51 @@ namespace Artemis.Utilities
|
||||
// TODO: Convert ColorHelpers to ExtensionMethods
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reflection
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value by path
|
||||
/// jheddings - http://stackoverflow.com/a/1954663/5015269
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="name">Path, such as "TimeOfDay.Minutes"</param>
|
||||
/// <returns></returns>
|
||||
public static object GetPropValue(this object obj, string name)
|
||||
{
|
||||
foreach (var part in name.Split('.'))
|
||||
{
|
||||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var type = obj.GetType();
|
||||
var info = type.GetProperty(part);
|
||||
if (info == null)
|
||||
return null;
|
||||
|
||||
obj = info.GetValue(obj, null);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value by path
|
||||
/// jheddings - http://stackoverflow.com/a/1954663/5015269
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="obj"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public static T GetPropValue<T>(this object obj, string name)
|
||||
{
|
||||
var retVal = GetPropValue(obj, name);
|
||||
if (retVal == null)
|
||||
return default(T);
|
||||
|
||||
// throws InvalidCastException if types are incompatible
|
||||
return (T) retVal;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Artemis.Settings;
|
||||
@ -9,12 +8,18 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Utilities.GameState
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for JSON calls, parses them and raises an event.
|
||||
/// Includes some code from https://github.com/rakijah/CSGSI
|
||||
/// </summary>
|
||||
public class GameStateWebServer
|
||||
{
|
||||
public delegate void GameDataReceivedEventHandler(
|
||||
object sender, GameDataReceivedEventArgs gameDataReceivedEventArgs);
|
||||
|
||||
private readonly HttpListener _listener = new HttpListener();
|
||||
private readonly AutoResetEvent _waitForConnection = new AutoResetEvent(false);
|
||||
|
||||
private HttpListener _listener;
|
||||
|
||||
public GameStateWebServer()
|
||||
{
|
||||
@ -31,64 +36,69 @@ namespace Artemis.Utilities.GameState
|
||||
if (Running)
|
||||
return;
|
||||
|
||||
Port = General.Default.GamestatePort;
|
||||
|
||||
_listener = new HttpListener();
|
||||
_listener.Prefixes.Add($"http://localhost:{Port}/");
|
||||
var listenerThread = new Thread(ListenerRun);
|
||||
try
|
||||
{
|
||||
_listener.Prefixes.Clear();
|
||||
Port = General.Default.GamestatePort;
|
||||
_listener.Prefixes.Add($"http://localhost:{Port}/");
|
||||
|
||||
_listener.Start();
|
||||
}
|
||||
catch (Exception)
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
MessageBox.Show("Couldn't start the webserver. CS:GO/Dota2 effects won't work :c \n\nTry changing the port in Settings and restart Artemis.");
|
||||
MessageBox.Show(
|
||||
"Couldn't start the webserver. CS:GO/Dota2 effects won't work :c \n\nTry changing the port in Settings and restart Artemis.");
|
||||
}
|
||||
|
||||
ThreadPool.QueueUserWorkItem(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
while (_listener.IsListening)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(c =>
|
||||
{
|
||||
var ctx = c as HttpListenerContext;
|
||||
if (ctx == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
HandleRequest(ctx.Request);
|
||||
var buf = Encoding.UTF8.GetBytes("ok");
|
||||
ctx.Response.ContentLength64 = buf.Length;
|
||||
ctx.Response.OutputStream.Write(buf, 0, buf.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
// always close the stream
|
||||
ctx.Response.OutputStream.Close();
|
||||
}
|
||||
}, _listener.GetContext());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
});
|
||||
|
||||
Running = true;
|
||||
listenerThread.Start();
|
||||
}
|
||||
|
||||
private void ListenerRun()
|
||||
{
|
||||
while (Running)
|
||||
{
|
||||
_listener.BeginGetContext(HandleRequest, _listener);
|
||||
_waitForConnection.WaitOne();
|
||||
_waitForConnection.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleRequest(IAsyncResult ar)
|
||||
{
|
||||
HttpListenerContext context = null;
|
||||
try
|
||||
{
|
||||
context = _listener.EndGetContext(ar);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Listener was Closed due to call of Stop();
|
||||
}
|
||||
catch (HttpListenerException)
|
||||
{
|
||||
// Listener was Closed due to call of Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_waitForConnection.Set();
|
||||
}
|
||||
|
||||
if (context != null)
|
||||
{
|
||||
HandleRequest(context.Request);
|
||||
context.Response.OutputStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_listener.Stop();
|
||||
_listener.Close();
|
||||
Running = false;
|
||||
}
|
||||
|
||||
private string HandleRequest(HttpListenerRequest request)
|
||||
private void HandleRequest(HttpListenerRequest request)
|
||||
{
|
||||
object json;
|
||||
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
|
||||
@ -99,7 +109,6 @@ namespace Artemis.Utilities.GameState
|
||||
|
||||
if (json != null)
|
||||
OnGameDataReceived(new GameDataReceivedEventArgs(json));
|
||||
return JsonConvert.SerializeObject(json);
|
||||
}
|
||||
|
||||
protected virtual void OnGameDataReceived(GameDataReceivedEventArgs e)
|
||||
|
||||
@ -2,12 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Principal;
|
||||
using System.Windows;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static System.String;
|
||||
|
||||
namespace Artemis.Utilities
|
||||
@ -62,10 +59,7 @@ namespace Artemis.Utilities
|
||||
|
||||
if (propertyNames.Length == 1 || value == null)
|
||||
return value;
|
||||
else
|
||||
{
|
||||
return GetPropertyValue(value, path.Replace(propertyNames[0] + ".", ""));
|
||||
}
|
||||
return GetPropertyValue(value, path.Replace(propertyNames[0] + ".", ""));
|
||||
}
|
||||
|
||||
public static List<PropertyCollection> GenerateTypeMap(object o) => GenerateTypeMap(o.GetType().GetProperties());
|
||||
@ -102,7 +96,9 @@ namespace Artemis.Utilities
|
||||
if (friendlyName != Empty)
|
||||
list.Add(parent);
|
||||
|
||||
list.AddRange(GenerateTypeMap(propertyInfo.PropertyType.GetProperties(), path + $"{propertyInfo.Name}."));
|
||||
if (propertyInfo.PropertyType.Name != "String")
|
||||
list.AddRange(GenerateTypeMap(propertyInfo.PropertyType.GetProperties(),
|
||||
path + $"{propertyInfo.Name}."));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Artemis.Models.Profiles;
|
||||
|
||||
namespace Artemis.Utilities
|
||||
@ -112,46 +111,18 @@ namespace Artemis.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public BitmapImage GetThumbnail()
|
||||
public DrawingImage GetThumbnail()
|
||||
{
|
||||
if (_layerModel.UserProps.Brush == null)
|
||||
return null;
|
||||
|
||||
_rectangle = new Rect(0, 0, 18, 18);
|
||||
var visual = new DrawingVisual();
|
||||
using (var c = visual.RenderOpen())
|
||||
c.DrawRectangle(_layerModel.UserProps.Brush, new Pen(new SolidColorBrush(Colors.White), 1),
|
||||
new Rect(0, 0, 18, 18));
|
||||
|
||||
//var bitmap = new Bitmap(18, 18);
|
||||
|
||||
//using (var g = Graphics.FromImage(bitmap))
|
||||
//{
|
||||
// g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
// if (_layerModel.LayerType == LayerType.KeyboardEllipse)
|
||||
// {
|
||||
// g.FillEllipse(_layerModel.LayerUserProperties.Brush, _rectangle);
|
||||
// g.DrawEllipse(new Pen(Color.Black, 1), 0, 0, 17, 17);
|
||||
// }
|
||||
// else if (_layerModel.LayerType == LayerType.KeyboardRectangle)
|
||||
// {
|
||||
// g.FillRectangle(_layerModel.LayerUserProperties.Brush, _rectangle);
|
||||
// g.DrawRectangle(new Pen(Color.Black, 1), 0, 0, 17, 17);
|
||||
// }
|
||||
// else
|
||||
// bitmap = Resources.folder;
|
||||
//}
|
||||
|
||||
//using (var memory = new MemoryStream())
|
||||
//{
|
||||
// bitmap.Save(memory, ImageFormat.Png);
|
||||
// memory.Position = 0;
|
||||
|
||||
// var bitmapImage = new BitmapImage();
|
||||
// bitmapImage.BeginInit();
|
||||
// bitmapImage.StreamSource = memory;
|
||||
// bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
// bitmapImage.EndInit();
|
||||
|
||||
// return bitmapImage;
|
||||
//}
|
||||
return null;
|
||||
var image = new DrawingImage(visual.Drawing);
|
||||
return image;
|
||||
}
|
||||
|
||||
public void DrawRectangle(DrawingContext c)
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Models.Profiles;
|
||||
using Artemis.Utilities;
|
||||
using Caliburn.Micro;
|
||||
|
||||
namespace Artemis.ViewModels.LayerEditor
|
||||
{
|
||||
public class LayerDynamicPropertiesViewModel : Screen
|
||||
{
|
||||
private LayerDynamicPropertiesModel _layerDynamicPropertiesModel;
|
||||
private string _name;
|
||||
private GeneralHelpers.PropertyCollection _selectedSource;
|
||||
private GeneralHelpers.PropertyCollection _selectedTarget;
|
||||
|
||||
public LayerDynamicPropertiesViewModel(string property,
|
||||
BindableCollection<GeneralHelpers.PropertyCollection> dataModelProps, LayerModel layer)
|
||||
{
|
||||
// Look for the existing property model
|
||||
LayerDynamicPropertiesModel = layer.LayerProperties.FirstOrDefault(lp => lp.LayerProperty == property);
|
||||
if (LayerDynamicPropertiesModel == null)
|
||||
{
|
||||
// If it doesn't exist, create a new one
|
||||
LayerDynamicPropertiesModel = new LayerDynamicPropertiesModel
|
||||
{
|
||||
LayerProperty = property,
|
||||
LayerPropertyType = LayerPropertyType.None
|
||||
};
|
||||
// Add it to the layer
|
||||
layer.LayerProperties.Add(LayerDynamicPropertiesModel);
|
||||
}
|
||||
|
||||
Name = property + ":";
|
||||
Targets = new BindableCollection<GeneralHelpers.PropertyCollection>();
|
||||
Targets.AddRange(dataModelProps.Where(p => p.Type == "Int32"));
|
||||
Sources = new BindableCollection<GeneralHelpers.PropertyCollection>();
|
||||
Sources.AddRange(dataModelProps.Where(p => p.Type == "Int32"));
|
||||
|
||||
PropertyChanged += OnPropertyChanged;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set
|
||||
{
|
||||
if (value == _name) return;
|
||||
_name = value;
|
||||
NotifyOfPropertyChange(() => Name);
|
||||
}
|
||||
}
|
||||
|
||||
public LayerDynamicPropertiesModel LayerDynamicPropertiesModel
|
||||
{
|
||||
get { return _layerDynamicPropertiesModel; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _layerDynamicPropertiesModel)) return;
|
||||
_layerDynamicPropertiesModel = value;
|
||||
NotifyOfPropertyChange(() => LayerDynamicPropertiesModel);
|
||||
}
|
||||
}
|
||||
|
||||
public BindableCollection<GeneralHelpers.PropertyCollection> Targets { get; set; }
|
||||
|
||||
public GeneralHelpers.PropertyCollection SelectedTarget
|
||||
{
|
||||
get { return _selectedTarget; }
|
||||
set
|
||||
{
|
||||
if (value.Equals(_selectedTarget)) return;
|
||||
_selectedTarget = value;
|
||||
NotifyOfPropertyChange(() => SelectedTarget);
|
||||
}
|
||||
}
|
||||
|
||||
public BindableCollection<GeneralHelpers.PropertyCollection> Sources { get; set; }
|
||||
|
||||
public GeneralHelpers.PropertyCollection SelectedSource
|
||||
{
|
||||
get { return _selectedSource; }
|
||||
set
|
||||
{
|
||||
if (value.Equals(_selectedSource)) return;
|
||||
_selectedSource = value;
|
||||
NotifyOfPropertyChange(() => SelectedSource);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the underlying model
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "SelectedTarget")
|
||||
LayerDynamicPropertiesModel.GameProperty = SelectedTarget.Path;
|
||||
if (e.PropertyName == "SelectedSource")
|
||||
LayerDynamicPropertiesModel.PercentageSource = SelectedSource.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,149 +1,150 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Media;
|
||||
using Artemis.DAL;
|
||||
using Artemis.KeyboardProviders;
|
||||
using Artemis.Models.Profiles;
|
||||
using Artemis.Utilities;
|
||||
using Artemis.ViewModels.LayerEditor;
|
||||
using Caliburn.Micro;
|
||||
|
||||
namespace Artemis.ViewModels
|
||||
{
|
||||
public class LayerEditorViewModel<T> : Screen
|
||||
{
|
||||
private readonly KeyboardProvider _activeKeyboard;
|
||||
private readonly BackgroundWorker _previewWorker;
|
||||
private readonly ProfileModel _profile;
|
||||
private readonly bool _wasEnabled;
|
||||
private LayerModel _layer;
|
||||
private LayerPropertiesModel _proposedProperties;
|
||||
|
||||
public LayerEditorViewModel(KeyboardProvider activeKeyboard, ProfileModel profile, LayerModel layer)
|
||||
{
|
||||
_activeKeyboard = activeKeyboard;
|
||||
_profile = profile;
|
||||
_wasEnabled = layer.Enabled;
|
||||
Layer = layer;
|
||||
|
||||
Layer.Enabled = false;
|
||||
|
||||
DataModelProps = new BindableCollection<GeneralHelpers.PropertyCollection>();
|
||||
ProposedProperties = new LayerPropertiesModel();
|
||||
DataModelProps.AddRange(GeneralHelpers.GenerateTypeMap<T>());
|
||||
|
||||
LayerConditionVms =
|
||||
new BindableCollection<LayerConditionViewModel<T>>(
|
||||
layer.LayerConditions.Select(c => new LayerConditionViewModel<T>(this, c, DataModelProps)));
|
||||
|
||||
_previewWorker = new BackgroundWorker {WorkerSupportsCancellation = true};
|
||||
_previewWorker.DoWork += PreviewWorkerOnDoWork;
|
||||
_previewWorker.RunWorkerAsync();
|
||||
|
||||
PropertyChanged += AnimationUiHandler;
|
||||
PreSelect();
|
||||
}
|
||||
|
||||
public BindableCollection<GeneralHelpers.PropertyCollection> DataModelProps { get; set; }
|
||||
|
||||
public BindableCollection<string> LayerTypes => new BindableCollection<string>();
|
||||
|
||||
public BindableCollection<LayerConditionViewModel<T>> LayerConditionVms { get; set; }
|
||||
|
||||
public LayerModel Layer
|
||||
{
|
||||
get { return _layer; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _layer)) return;
|
||||
_layer = value;
|
||||
NotifyOfPropertyChange(() => Layer);
|
||||
}
|
||||
}
|
||||
|
||||
public LayerPropertiesModel ProposedProperties
|
||||
{
|
||||
get { return _proposedProperties; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _proposedProperties)) return;
|
||||
_proposedProperties = value;
|
||||
NotifyOfPropertyChange(() => ProposedProperties);
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource LayerImage
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyboardRect = _activeKeyboard.KeyboardRectangle(4);
|
||||
|
||||
var visual = new DrawingVisual();
|
||||
using (var drawingContext = visual.RenderOpen())
|
||||
{
|
||||
// Setup the DrawingVisual's size
|
||||
drawingContext.PushClip(new RectangleGeometry(keyboardRect));
|
||||
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect);
|
||||
|
||||
// Draw the layer
|
||||
_layer.DrawPreview(drawingContext);
|
||||
|
||||
// Remove the clip
|
||||
drawingContext.Pop();
|
||||
}
|
||||
var image = new DrawingImage(visual.Drawing);
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
|
||||
{
|
||||
while (!_previewWorker.CancellationPending)
|
||||
{
|
||||
NotifyOfPropertyChange(() => LayerImage);
|
||||
Thread.Sleep(1000/25);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreSelect()
|
||||
{
|
||||
GeneralHelpers.CopyProperties(ProposedProperties, Layer.UserProps);
|
||||
}
|
||||
|
||||
private void AnimationUiHandler(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "_proposedProperties")
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddCondition()
|
||||
{
|
||||
var condition = new LayerConditionModel();
|
||||
Layer.LayerConditions.Add(condition);
|
||||
LayerConditionVms.Add(new LayerConditionViewModel<T>(this, condition, DataModelProps));
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
GeneralHelpers.CopyProperties(Layer.UserProps, ProposedProperties);
|
||||
ProfileProvider.AddOrUpdate(_profile);
|
||||
}
|
||||
|
||||
public void DeleteCondition(LayerConditionViewModel<T> layerConditionViewModel,
|
||||
LayerConditionModel layerConditionModel)
|
||||
{
|
||||
LayerConditionVms.Remove(layerConditionViewModel);
|
||||
Layer.LayerConditions.Remove(layerConditionModel);
|
||||
}
|
||||
|
||||
public override void CanClose(Action<bool> callback)
|
||||
{
|
||||
_previewWorker.CancelAsync();
|
||||
_layer.Enabled = _wasEnabled;
|
||||
base.CanClose(callback);
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Media;
|
||||
using Artemis.KeyboardProviders;
|
||||
using Artemis.Models.Profiles;
|
||||
using Artemis.Utilities;
|
||||
using Caliburn.Micro;
|
||||
|
||||
namespace Artemis.ViewModels.LayerEditor
|
||||
{
|
||||
public class LayerEditorViewModel<T> : Screen
|
||||
{
|
||||
private readonly KeyboardProvider _activeKeyboard;
|
||||
private readonly BackgroundWorker _previewWorker;
|
||||
private readonly bool _wasEnabled;
|
||||
private LayerModel _layer;
|
||||
private LayerPropertiesModel _proposedProperties;
|
||||
|
||||
public LayerEditorViewModel(KeyboardProvider activeKeyboard, LayerModel layer)
|
||||
{
|
||||
_activeKeyboard = activeKeyboard;
|
||||
_wasEnabled = layer.Enabled;
|
||||
|
||||
Layer = layer;
|
||||
Layer.Enabled = false;
|
||||
DataModelProps = new BindableCollection<GeneralHelpers.PropertyCollection>();
|
||||
ProposedProperties = new LayerPropertiesModel();
|
||||
DataModelProps.AddRange(GeneralHelpers.GenerateTypeMap<T>());
|
||||
LayerConditionVms = new BindableCollection<LayerConditionViewModel<T>>(
|
||||
layer.LayerConditions.Select(c => new LayerConditionViewModel<T>(this, c, DataModelProps)));
|
||||
HeightProperties = new LayerDynamicPropertiesViewModel("Height", DataModelProps, layer);
|
||||
WidthProperties = new LayerDynamicPropertiesViewModel("Width", DataModelProps, layer);
|
||||
OpacityProperties = new LayerDynamicPropertiesViewModel("Opacity", DataModelProps, layer);
|
||||
|
||||
_previewWorker = new BackgroundWorker {WorkerSupportsCancellation = true};
|
||||
_previewWorker.DoWork += PreviewWorkerOnDoWork;
|
||||
_previewWorker.RunWorkerAsync();
|
||||
|
||||
PropertyChanged += AnimationUiHandler;
|
||||
PreSelect();
|
||||
}
|
||||
|
||||
public LayerDynamicPropertiesViewModel OpacityProperties { get; set; }
|
||||
|
||||
public LayerDynamicPropertiesViewModel WidthProperties { get; set; }
|
||||
|
||||
public LayerDynamicPropertiesViewModel HeightProperties { get; set; }
|
||||
|
||||
public BindableCollection<GeneralHelpers.PropertyCollection> DataModelProps { get; set; }
|
||||
|
||||
public BindableCollection<string> LayerTypes => new BindableCollection<string>();
|
||||
|
||||
public BindableCollection<LayerConditionViewModel<T>> LayerConditionVms { get; set; }
|
||||
|
||||
public LayerModel Layer
|
||||
{
|
||||
get { return _layer; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _layer)) return;
|
||||
_layer = value;
|
||||
NotifyOfPropertyChange(() => Layer);
|
||||
}
|
||||
}
|
||||
|
||||
public LayerPropertiesModel ProposedProperties
|
||||
{
|
||||
get { return _proposedProperties; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _proposedProperties)) return;
|
||||
_proposedProperties = value;
|
||||
NotifyOfPropertyChange(() => ProposedProperties);
|
||||
}
|
||||
}
|
||||
|
||||
public ImageSource LayerImage
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyboardRect = _activeKeyboard.KeyboardRectangle(4);
|
||||
|
||||
var visual = new DrawingVisual();
|
||||
using (var drawingContext = visual.RenderOpen())
|
||||
{
|
||||
// Setup the DrawingVisual's size
|
||||
drawingContext.PushClip(new RectangleGeometry(keyboardRect));
|
||||
drawingContext.DrawRectangle(new SolidColorBrush(Color.FromArgb(0, 0, 0, 0)), null, keyboardRect);
|
||||
|
||||
// Draw the layer
|
||||
_layer.DrawPreview(drawingContext);
|
||||
|
||||
// Remove the clip
|
||||
drawingContext.Pop();
|
||||
}
|
||||
var image = new DrawingImage(visual.Drawing);
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private void PreviewWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
|
||||
{
|
||||
while (!_previewWorker.CancellationPending)
|
||||
{
|
||||
NotifyOfPropertyChange(() => LayerImage);
|
||||
Thread.Sleep(1000/25);
|
||||
}
|
||||
}
|
||||
|
||||
public void PreSelect()
|
||||
{
|
||||
GeneralHelpers.CopyProperties(ProposedProperties, Layer.UserProps);
|
||||
}
|
||||
|
||||
private void AnimationUiHandler(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "_proposedProperties")
|
||||
return;
|
||||
}
|
||||
|
||||
public void AddCondition()
|
||||
{
|
||||
var condition = new LayerConditionModel();
|
||||
Layer.LayerConditions.Add(condition);
|
||||
LayerConditionVms.Add(new LayerConditionViewModel<T>(this, condition, DataModelProps));
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
GeneralHelpers.CopyProperties(Layer.UserProps, ProposedProperties);
|
||||
}
|
||||
|
||||
public void DeleteCondition(LayerConditionViewModel<T> layerConditionViewModel,
|
||||
LayerConditionModel layerConditionModel)
|
||||
{
|
||||
LayerConditionVms.Remove(layerConditionViewModel);
|
||||
Layer.LayerConditions.Remove(layerConditionModel);
|
||||
}
|
||||
|
||||
public override void CanClose(Action<bool> callback)
|
||||
{
|
||||
_previewWorker.CancelAsync();
|
||||
_layer.Enabled = _wasEnabled;
|
||||
base.CanClose(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ using Artemis.KeyboardProviders;
|
||||
using Artemis.Managers;
|
||||
using Artemis.Models;
|
||||
using Artemis.Models.Profiles;
|
||||
using Artemis.ViewModels.LayerEditor;
|
||||
using Caliburn.Micro;
|
||||
using MahApps.Metro;
|
||||
|
||||
@ -78,6 +79,7 @@ namespace Artemis.ViewModels
|
||||
if (Equals(value, _selectedLayer)) return;
|
||||
_selectedLayer = value;
|
||||
NotifyOfPropertyChange(() => SelectedLayer);
|
||||
NotifyOfPropertyChange(() => CanRemoveLayer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +107,7 @@ namespace Artemis.ViewModels
|
||||
|
||||
NotifyOfPropertyChange(() => SelectedProfile);
|
||||
NotifyOfPropertyChange(() => CanAddLayer);
|
||||
NotifyOfPropertyChange(() => CanRemoveLayer);
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +172,7 @@ namespace Artemis.ViewModels
|
||||
{
|
||||
if (ActiveKeyboard?.PreviewSettings.Image == null)
|
||||
return null;
|
||||
|
||||
ActiveKeyboard.PreviewSettings.Image.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
@ -186,6 +190,7 @@ namespace Artemis.ViewModels
|
||||
public PreviewSettings? PreviewSettings => ActiveKeyboard?.PreviewSettings;
|
||||
|
||||
public bool CanAddLayer => _selectedProfile != null;
|
||||
public bool CanRemoveLayer => _selectedProfile != null && _selectedLayer != null;
|
||||
|
||||
private KeyboardProvider ActiveKeyboard => _mainManager.KeyboardManager.ActiveKeyboard;
|
||||
|
||||
@ -197,8 +202,13 @@ namespace Artemis.ViewModels
|
||||
|
||||
private void PropertyChangeHandler(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != "KeyboardPreview")
|
||||
NotifyOfPropertyChange(() => KeyboardPreview);
|
||||
if (e.PropertyName == "KeyboardPreview")
|
||||
return;
|
||||
|
||||
NotifyOfPropertyChange(() => KeyboardPreview);
|
||||
|
||||
if (SelectedProfile != null)
|
||||
ProfileProvider.AddOrUpdate(SelectedProfile);
|
||||
}
|
||||
|
||||
private void LoadProfiles()
|
||||
@ -247,7 +257,7 @@ namespace Artemis.ViewModels
|
||||
public void LayerEditor(LayerModel layer)
|
||||
{
|
||||
IWindowManager manager = new WindowManager();
|
||||
_editorVm = new LayerEditorViewModel<T>(ActiveKeyboard, SelectedProfile, layer);
|
||||
_editorVm = new LayerEditorViewModel<T>(ActiveKeyboard, layer);
|
||||
dynamic settings = new ExpandoObject();
|
||||
|
||||
settings.Title = "Artemis | Edit " + layer.Name;
|
||||
@ -272,6 +282,16 @@ namespace Artemis.ViewModels
|
||||
|
||||
SelectedProfile.Layers.Remove(_selectedLayer);
|
||||
Layers.Remove(_selectedLayer);
|
||||
|
||||
SelectedProfile.FixOrder();
|
||||
}
|
||||
|
||||
public void RemoveLayer(LayerModel layer)
|
||||
{
|
||||
SelectedProfile.Layers.Remove(layer);
|
||||
Layers.Remove(layer);
|
||||
|
||||
SelectedProfile.FixOrder();
|
||||
}
|
||||
|
||||
public void LayerUp()
|
||||
@ -324,8 +344,7 @@ namespace Artemis.ViewModels
|
||||
|
||||
var hoverLayer = SelectedProfile.Layers.OrderBy(l => l.Order).Where(l => l.Enabled)
|
||||
.FirstOrDefault(l => l.UserProps.GetRect(1).Contains(x, y));
|
||||
if (hoverLayer != null)
|
||||
SelectedLayer = hoverLayer;
|
||||
SelectedLayer = hoverLayer;
|
||||
}
|
||||
|
||||
public void MouseMoveKeyboardPreview(MouseEventArgs e)
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
<UserControl x:Class="Artemis.Views.LayerEditor.LayerDynamicPropertiesView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.Views.LayerEditor"
|
||||
xmlns:utilities="clr-namespace:Artemis.Utilities"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:profileEnumerations="clr-namespace:Artemis.Models.Profiles"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="40" d:DesignWidth="500">
|
||||
|
||||
<UserControl.Resources>
|
||||
<utilities:EnumDescriptionConverter x:Key="HEnumDescriptionConverter" />
|
||||
<ObjectDataProvider MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
x:Key="DynamicPropertyValues">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="profileEnumerations:LayerPropertyType" />
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<!-- Height -->
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock x:Name="Name" Grid.Column="0" Margin="10" FontSize="13.333" VerticalAlignment="Center" Height="18" />
|
||||
|
||||
<!-- Target property -->
|
||||
<ComboBox x:Name="Targets" Grid.Column="1" Margin="10,0" MaxDropDownHeight="125" VerticalAlignment="Center"
|
||||
Height="22">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid MinWidth="435">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Path=Display}" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Column="1" FontWeight="Bold" Text="{Binding Path=DisplayType}"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Dynamic type -->
|
||||
<ComboBox SelectedItem="{Binding Path=LayerDynamicPropertiesModel.LayerPropertyType}" Grid.Column="2"
|
||||
ItemsSource="{Binding Source={StaticResource DynamicPropertyValues}}"
|
||||
Margin="10,0" VerticalAlignment="Center" Height="22">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource HEnumDescriptionConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- PercentageOf property -->
|
||||
<ComboBox x:Name="Sources" Grid.Column="3" Margin="10,0" MaxDropDownHeight="125"
|
||||
VerticalAlignment="Center" Height="22">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid MinWidth="522">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" Text="{Binding Path=Display}" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Column="1" FontWeight="Bold" Text="{Binding Path=DisplayType}"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -1,29 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using MahApps.Metro.Controls;
|
||||
using ColorBox;
|
||||
|
||||
namespace Artemis.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LayerEditorView.xaml
|
||||
/// </summary>
|
||||
public partial class LayerEditorView : MetroWindow
|
||||
{
|
||||
public LayerEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Artemis.Views.LayerEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LayerDynamicPropertiesView.xaml
|
||||
/// </summary>
|
||||
public partial class LayerDynamicPropertiesView : UserControl
|
||||
{
|
||||
public LayerDynamicPropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,151 +1,141 @@
|
||||
<controls:MetroWindow x:Class="Artemis.Views.LayerEditorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:cal="http://www.caliburnproject.org"
|
||||
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
|
||||
xmlns:utilities="clr-namespace:Artemis.Utilities"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d"
|
||||
Title="Artemis | Edit Layer" Height="750" Width="630"
|
||||
xmlns:profileEnumerations="clr-namespace:Artemis.Models.Profiles"
|
||||
xmlns:ncore="http://schemas.ncore.com/wpf/xaml/colorbox"
|
||||
GlowBrush="{DynamicResource AccentColorBrush}" Icon="../logo.ico" ResizeMode="NoResize">
|
||||
<controls:MetroWindow.Resources>
|
||||
<utilities:EnumDescriptionConverter x:Key="HEnumDescriptionConverter" />
|
||||
<ObjectDataProvider MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
x:Key="AnimationEnumValues">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="profileEnumerations:LayerAnimation" />
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
</controls:MetroWindow.Resources>
|
||||
|
||||
<Grid Margin="10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="4" FontSize="20" Content="Basics" />
|
||||
|
||||
<!-- Layer name -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="10,12" FontSize="13.333" Text="Name:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<TextBox Grid.Row="1" Grid.Column="1" x:Name="Name" Margin="10" Text="{Binding Path=Layer.Name}" />
|
||||
|
||||
<!-- Layer type -->
|
||||
|
||||
<!-- Condition editor -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" FontSize="20" Content="Display if.." />
|
||||
<Border Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" BorderThickness="1"
|
||||
BorderBrush="{StaticResource GrayBrush7}" Margin="10,0" SnapsToDevicePixels="True">
|
||||
<ListBox Height="138" x:Name="LayerConditionVms" ScrollViewer.VerticalScrollBarVisibility="Visible">
|
||||
<ListBox.Template>
|
||||
<ControlTemplate>
|
||||
<ScrollViewer>
|
||||
<ItemsPresenter />
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</ListBox.Template>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<Button Grid.Row="4" Grid.Column="3" x:Name="AddCondition" Content="Add condition" VerticalAlignment="Center"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Right" Height="30"
|
||||
Margin="0,10,10,10" ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<!-- Advanced -->
|
||||
<Label Grid.Row="4" Grid.Column="0" FontSize="20" HorizontalAlignment="Left"
|
||||
Content="Advanced" Width="97" VerticalAlignment="Bottom" />
|
||||
|
||||
<!-- Height -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Margin="10" FontSize="13.333" Text="Height:" VerticalAlignment="Center"
|
||||
Height="18" />
|
||||
<TextBox Grid.Row="5" Grid.Column="1" Margin="10" Text="{Binding Path=ProposedProperties.Height}" />
|
||||
|
||||
<!-- Width -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="2" Margin="10" FontSize="13.333" Text="Width:" VerticalAlignment="Center"
|
||||
Height="18" />
|
||||
<TextBox Grid.Row="5" Grid.Column="3" Margin="10" Text="{Binding Path=ProposedProperties.Width}" />
|
||||
|
||||
<!-- Colors -->
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Margin="10,13,10,0" FontSize="13.333" Text="Color(s):"
|
||||
VerticalAlignment="Top" Height="18" />
|
||||
<Border Grid.Row="6" Grid.Column="1" Margin="10" BorderBrush="{StaticResource ControlBorderBrush}"
|
||||
BorderThickness="1" SnapsToDevicePixels="True" ToolTip="Click to edit">
|
||||
<ncore:ColorBox Brush="{Binding Path=ProposedProperties.Brush, Mode=TwoWay}" Height="24" />
|
||||
</Border>
|
||||
|
||||
<!-- ContainedBrush -->
|
||||
<TextBlock Grid.Row="6" Grid.Column="2" Margin="10" FontSize="13.333" Text="Contained colors:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<controls:ToggleSwitch IsChecked="{Binding Path=ProposedProperties.ContainedBrush, Mode=TwoWay}" Grid.Row="6"
|
||||
Grid.Column="3" OnLabel="Yes" OffLabel="No" Margin="10,1,5,1" VerticalAlignment="Center"
|
||||
Height="36" />
|
||||
|
||||
<!-- Animation -->
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Margin="10" FontSize="13.333" Text="Animation:"
|
||||
VerticalAlignment="Center"
|
||||
Height="18" />
|
||||
<ComboBox Grid.Row="7" Grid.Column="1" ItemsSource="{Binding Source={StaticResource AnimationEnumValues}}"
|
||||
Margin="10,10,10,0" SelectedItem="{Binding Path=ProposedProperties.Animation}"
|
||||
VerticalAlignment="Top" Height="22">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource HEnumDescriptionConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Animation Speed -->
|
||||
<TextBlock Grid.Row="7" Grid.Column="2" Margin="10" FontSize="13.333" Text="Animation speed:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<Slider x:Name="RotationSpeed" Grid.Row="7" Grid.Column="3" VerticalAlignment="Center"
|
||||
TickPlacement="None" TickFrequency="0.05"
|
||||
Value="{Binding Path=ProposedProperties.AnimationSpeed, Mode=TwoWay}" Minimum="0.05" Maximum="3"
|
||||
SmallChange="1" IsSnapToTickEnabled="True" Margin="10,12,10,2" Height="24" />
|
||||
|
||||
<!-- Opacity -->
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Margin="10" FontSize="13.333" Text="Opacity:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<Slider x:Name="Sensitivity" Grid.Row="8" Grid.Column="1" VerticalAlignment="Center"
|
||||
TickPlacement="BottomRight" TickFrequency="0.05"
|
||||
Value="{Binding Path=ProposedProperties.Opacity, Mode=TwoWay}" Minimum="0.0" Maximum="1.0"
|
||||
SmallChange="0.05" Margin="10,7" Height="24" />
|
||||
|
||||
<!-- Preview -->
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Margin="10,13,10,0" FontSize="13.333" Text="Preview:"
|
||||
VerticalAlignment="Top" Height="18" />
|
||||
<Border Grid.Row="9" Grid.Column="1" Grid.ColumnSpan="2" Margin="10" BorderThickness="1"
|
||||
BorderBrush="{StaticResource ControlBorderBrush}" SnapsToDevicePixels="True">
|
||||
<Image Source="{Binding LayerImage}" />
|
||||
</Border>
|
||||
|
||||
<Button Grid.Row="10" Grid.Column="0" x:Name="Apply" Content="Apply" VerticalAlignment="Bottom"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Left" Margin="10,0,0,20"
|
||||
Height="30" />
|
||||
<Button Grid.Row="10" Grid.Column="1" x:Name="PreSelect" Content="Reset" VerticalAlignment="Bottom"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Left" Margin="10,0,0,20"
|
||||
Height="30" />
|
||||
</Grid>
|
||||
|
||||
<controls:MetroWindow x:Class="Artemis.Views.LayerEditor.LayerEditorView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:utilities="clr-namespace:Artemis.Utilities"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
mc:Ignorable="d"
|
||||
Title="Artemis | Edit Layer" Height="750" Width="630"
|
||||
xmlns:profileEnumerations="clr-namespace:Artemis.Models.Profiles"
|
||||
xmlns:ncore="http://schemas.ncore.com/wpf/xaml/colorbox"
|
||||
GlowBrush="{DynamicResource AccentColorBrush}" Icon="../../Resources/bow.png" ResizeMode="NoResize">
|
||||
<controls:MetroWindow.Resources>
|
||||
<utilities:EnumDescriptionConverter x:Key="HEnumDescriptionConverter" />
|
||||
<ObjectDataProvider MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
x:Key="AnimationEnumValues">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="profileEnumerations:LayerAnimation" />
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
<ObjectDataProvider MethodName="GetValues"
|
||||
ObjectType="{x:Type sys:Enum}"
|
||||
x:Key="DynamicPropertyValues">
|
||||
<ObjectDataProvider.MethodParameters>
|
||||
<x:Type TypeName="profileEnumerations:LayerPropertyType" />
|
||||
</ObjectDataProvider.MethodParameters>
|
||||
</ObjectDataProvider>
|
||||
</controls:MetroWindow.Resources>
|
||||
|
||||
<Grid Margin="10,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
<ColumnDefinition Width="65*" />
|
||||
<ColumnDefinition Width="86*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header -->
|
||||
<Label Grid.Row="0" Grid.ColumnSpan="4" FontSize="20" Content="Basics" />
|
||||
|
||||
<!-- Layer name -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Margin="10,12" FontSize="13.333" Text="Name:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<TextBox Grid.Row="1" Grid.Column="1" x:Name="Name" Margin="10" Text="{Binding Path=Layer.Name}" />
|
||||
|
||||
<!-- Layer type -->
|
||||
|
||||
<!-- Condition editor -->
|
||||
<Label Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" FontSize="20" Content="Display if.." />
|
||||
<Border Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" BorderThickness="1"
|
||||
BorderBrush="{StaticResource GrayBrush7}" Margin="10,0" SnapsToDevicePixels="True">
|
||||
<ListBox Height="138" x:Name="LayerConditionVms" ScrollViewer.VerticalScrollBarVisibility="Visible">
|
||||
<ListBox.Template>
|
||||
<ControlTemplate>
|
||||
<ScrollViewer>
|
||||
<ItemsPresenter />
|
||||
</ScrollViewer>
|
||||
</ControlTemplate>
|
||||
</ListBox.Template>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<Button Grid.Row="4" Grid.Column="3" x:Name="AddCondition" Content="Add condition" VerticalAlignment="Center"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Right" Height="30"
|
||||
Margin="0,10,10,10" ScrollViewer.VerticalScrollBarVisibility="Auto" />
|
||||
|
||||
<!-- Advanced -->
|
||||
<Label Grid.Row="4" Grid.Column="0" FontSize="20" HorizontalAlignment="Left"
|
||||
Content="Advanced" Width="97" VerticalAlignment="Bottom" />
|
||||
|
||||
<!-- Colors -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Margin="10,13,10,0" FontSize="13.333" Text="Color(s):"
|
||||
VerticalAlignment="Top" Height="18" />
|
||||
<Border Grid.Row="5" Grid.Column="1" Margin="10" BorderBrush="{StaticResource ControlBorderBrush}"
|
||||
BorderThickness="1" SnapsToDevicePixels="True" ToolTip="Click to edit">
|
||||
<ncore:ColorBox Brush="{Binding Path=ProposedProperties.Brush, Mode=TwoWay}" Height="24" />
|
||||
</Border>
|
||||
|
||||
<!-- ContainedBrush -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="2" Margin="10" FontSize="13.333" Text="Contained colors:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<controls:ToggleSwitch IsChecked="{Binding Path=ProposedProperties.ContainedBrush, Mode=TwoWay}" Grid.Row="5"
|
||||
Grid.Column="3" OnLabel="Yes" OffLabel="No" Margin="10,1,5,1" VerticalAlignment="Center"
|
||||
Height="36" />
|
||||
|
||||
<!-- Animation -->
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Margin="10" FontSize="13.333" Text="Animation:"
|
||||
VerticalAlignment="Center"
|
||||
Height="18" />
|
||||
<ComboBox Grid.Row="6" Grid.Column="1" ItemsSource="{Binding Source={StaticResource AnimationEnumValues}}"
|
||||
Margin="10,10,10,0" SelectedItem="{Binding Path=ProposedProperties.Animation}"
|
||||
VerticalAlignment="Top" Height="22">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource HEnumDescriptionConverter}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<!-- Animation Speed -->
|
||||
<TextBlock Grid.Row="6" Grid.Column="2" Margin="10" FontSize="13.333" Text="Animation speed:"
|
||||
VerticalAlignment="Center" Height="18" />
|
||||
<Slider x:Name="RotationSpeed" Grid.Row="6" Grid.Column="3" VerticalAlignment="Center"
|
||||
TickPlacement="None" TickFrequency="0.05"
|
||||
Value="{Binding Path=ProposedProperties.AnimationSpeed, Mode=TwoWay}" Minimum="0.05" Maximum="3"
|
||||
SmallChange="1" IsSnapToTickEnabled="True" Margin="10,12,10,2" Height="24" />
|
||||
|
||||
<!-- Dynamic -->
|
||||
<Label Grid.Row="7" Grid.Column="0" FontSize="20" HorizontalAlignment="Left"
|
||||
Content="Dynamic" Width="97" VerticalAlignment="Bottom" />
|
||||
|
||||
<!-- Dynamic property views -->
|
||||
<ContentControl Grid.Row="8" Grid.Column="0" Grid.ColumnSpan="4" x:Name="HeightProperties" />
|
||||
<ContentControl Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="4" x:Name="WidthProperties" />
|
||||
<ContentControl Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="4" x:Name="OpacityProperties" />
|
||||
|
||||
<Button Grid.Row="12" Grid.Column="0" x:Name="Apply" Content="Apply" VerticalAlignment="Bottom"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Left" Margin="10,0,0,20"
|
||||
Height="30" />
|
||||
<Button Grid.Row="12" Grid.Column="1" x:Name="PreSelect" Content="Reset" VerticalAlignment="Bottom"
|
||||
Style="{DynamicResource SquareButtonStyle}" Width="95" HorizontalAlignment="Left" Margin="10,0,0,20"
|
||||
Height="30" />
|
||||
</Grid>
|
||||
|
||||
</controls:MetroWindow>
|
||||
15
Artemis/Artemis/Views/LayerEditor/LayerEditorView.xaml.cs
Normal file
15
Artemis/Artemis/Views/LayerEditor/LayerEditorView.xaml.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using MahApps.Metro.Controls;
|
||||
|
||||
namespace Artemis.Views.LayerEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LayerEditorView.xaml
|
||||
/// </summary>
|
||||
public partial class LayerEditorView : MetroWindow
|
||||
{
|
||||
public LayerEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@
|
||||
<ContextMenu
|
||||
cal:Action.TargetWithoutContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Rename" />
|
||||
<MenuItem Header="Delete" />
|
||||
<MenuItem Header="Delete" cal:Message.Attach="RemoveLayer($datacontext)"/>
|
||||
<MenuItem Header="Properties" cal:Message.Attach="LayerEditor($datacontext)" />
|
||||
</ContextMenu>
|
||||
</StackPanel.ContextMenu>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user