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

Merge branch 'development'

This commit is contained in:
Robert 2021-07-08 19:03:06 +02:00
commit 54f278c72f
21 changed files with 254 additions and 49 deletions

View File

@ -1,4 +1,5 @@
using System;
using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Conditions;
@ -69,7 +70,10 @@ namespace Artemis.Core
{
if (Entity.LeftPath != null)
LeftPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.LeftPath)
? new DataModelPath(ListPredicateWrapperDataModel.Create(
DataModelConditionList.ListType,
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
), Entity.LeftPath)
: null;
}
@ -82,7 +86,10 @@ namespace Artemis.Core
if (Entity.RightPath.WrapperType == PathWrapperType.List)
{
RightPath = DataModelConditionList.ListType != null
? new DataModelPath(ListPredicateWrapperDataModel.Create(DataModelConditionList.ListType), Entity.RightPath)
? new DataModelPath(ListPredicateWrapperDataModel.Create(
DataModelConditionList.ListType,
DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
), Entity.RightPath)
: null;
}
// Right side dynamic

View File

@ -1,11 +1,11 @@
using System;
using System.Reflection;
using Artemis.Core.Modules;
namespace Artemis.Core
{
internal class ListPredicateWrapperDataModel<T> : ListPredicateWrapperDataModel
{
[DataModelProperty(Name = "List item", Description = "The current item in the list")]
public T Value => (UntypedValue is T typedValue ? typedValue : default)!;
}
@ -26,15 +26,34 @@ namespace Artemis.Core
public object? UntypedValue { get; set; }
/// <summary>
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel"/> class
/// Gets or sets the name of the list item
/// </summary>
public static ListPredicateWrapperDataModel Create(Type type)
[DataModelIgnore]
public string? ItemName { get; set; }
#region Overrides of DataModel
/// <inheritdoc />
public override DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
{
object? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type));
if (!string.IsNullOrWhiteSpace(ItemName))
return new DataModelPropertyAttribute {Name = ItemName};
return base.GetPropertyDescription(propertyInfo);
}
#endregion
/// <summary>
/// Creates a new instance of the <see cref="ListPredicateWrapperDataModel" /> class
/// </summary>
public static ListPredicateWrapperDataModel Create(Type type, string? name = null)
{
ListPredicateWrapperDataModel? instance = Activator.CreateInstance(typeof(ListPredicateWrapperDataModel<>).MakeGenericType(type)) as ListPredicateWrapperDataModel;
if (instance == null)
throw new ArtemisCoreException($"Failed to create an instance of ListPredicateWrapperDataModel<T> for type {type.Name}");
return (ListPredicateWrapperDataModel) instance;
instance.ItemName = name;
return instance;
}
}
}

View File

@ -122,7 +122,7 @@ namespace Artemis.Core
return null;
// Static types may have one as an attribute
DataModelPropertyAttribute? attribute = (DataModelPropertyAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
DataModelPropertyAttribute? attribute = DataModelPath.Target?.GetPropertyDescription(propertyInfo);
if (attribute != null)
{
if (string.IsNullOrWhiteSpace(attribute.Name))
@ -211,7 +211,7 @@ namespace Artemis.Core
// A static segment just needs to access the property or filed
else if (Type == DataModelPathSegmentType.Static)
{
accessorExpression = _property != null
accessorExpression = _property != null
? Expression.Property(expression, _property)
: Expression.PropertyOrField(expression, Identifier);
}

View File

@ -25,8 +25,8 @@ namespace Artemis.Core
Scripts = new List<ProfileScript>();
ScriptConfigurations = new List<ScriptConfiguration>();
UndoStack = new Stack<string>();
RedoStack = new Stack<string>();
UndoStack = new MaxStack<string>(20);
RedoStack = new MaxStack<string>(20);
Load();
}
@ -75,8 +75,8 @@ namespace Artemis.Core
/// </summary>
public ProfileEntity ProfileEntity { get; internal set; }
internal Stack<string> UndoStack { get; set; }
internal Stack<string> RedoStack { get; set; }
internal MaxStack<string> UndoStack { get; set; }
internal MaxStack<string> RedoStack { get; set; }
/// <inheritdoc />
public override void Update(double deltaTime)

View File

@ -28,6 +28,11 @@ namespace Artemis.Core.Modules
/// </summary>
public string? Affix { get; set; }
/// <summary>
/// Gets or sets the name of list items, only applicable to enumerable data model properties
/// </summary>
public string? ListItemName { get; set; }
/// <summary>
/// Gets or sets an optional maximum value, this value is not enforced but used for percentage calculations.
/// </summary>

View File

@ -69,6 +69,15 @@ namespace Artemis.Core.Modules
return Module.HiddenProperties;
}
/// <summary>
/// Gets the property description of the provided property info
/// </summary>
/// <returns>If found, the property description attribute, otherwise <see langword="null"/>.</returns>
public virtual DataModelPropertyAttribute? GetPropertyDescription(PropertyInfo propertyInfo)
{
return (DataModelPropertyAttribute?) Attribute.GetCustomAttribute(propertyInfo, typeof(DataModelPropertyAttribute));
}
#region Dynamic children
/// <summary>

View File

@ -49,6 +49,7 @@ namespace Artemis.Core.Services
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
HotkeysEnabled = true;
inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
if (!_profileCategories.Any())
@ -470,7 +471,7 @@ namespace Artemis.Core.Services
// Keep the profile from being rendered by locking it
lock (profile)
{
if (!profile.UndoStack.Any())
if (profile.UndoStack.Count == 0)
{
_logger.Debug("Undo profile update - Failed, undo stack empty");
return false;
@ -496,7 +497,7 @@ namespace Artemis.Core.Services
// Keep the profile from being rendered by locking it
lock (profile)
{
if (!profile.RedoStack.Any())
if (profile.RedoStack.Count == 0)
{
_logger.Debug("Redo profile update - Failed, redo empty");
return false;

View File

@ -0,0 +1,118 @@
// Source: https://ntsblog.homedev.com.au/index.php/2010/05/06/c-stack-with-maximum-limit/
using System;
using System.Collections;
using System.Collections.Generic;
namespace Artemis.Core
{
/// <summary>
/// Generic stack implementation with a maximum limit
/// When something is pushed on the last item is removed from the list
/// </summary>
[Serializable]
internal class MaxStack<T>
{
#region Fields
private int _limit;
private LinkedList<T> _list;
#endregion
#region Constructors
public MaxStack(int maxSize)
{
_limit = maxSize;
_list = new LinkedList<T>();
}
#endregion
#region Public Stack Implementation
public void Push(T value)
{
if (_list.Count == _limit)
{
_list.RemoveLast();
}
_list.AddFirst(value);
}
public T Pop()
{
if (_list.Count > 0)
{
T value = _list.First.Value;
_list.RemoveFirst();
return value;
}
else
{
throw new InvalidOperationException("The Stack is empty");
}
}
public T Peek()
{
if (_list.Count > 0)
{
T value = _list.First.Value;
return value;
}
else
{
throw new InvalidOperationException("The Stack is empty");
}
}
public void Clear()
{
_list.Clear();
}
public int Count
{
get { return _list.Count; }
}
/// <summary>
/// Checks if the top object on the stack matches the value passed in
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public bool IsTop(T value)
{
bool result = false;
if (this.Count > 0)
{
result = Peek().Equals(value);
}
return result;
}
public bool Contains(T value)
{
bool result = false;
if (this.Count > 0)
{
result = _list.Contains(value);
}
return result;
}
public IEnumerator GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
}
}

View File

@ -30,12 +30,14 @@ namespace Artemis.UI.Shared.Input
private bool _isDataModelViewModelOpen;
private bool _isEnabled = true;
private string _placeholder = "Select a property";
private readonly PluginSetting<bool> _showFullPath;
internal DataModelDynamicViewModel(List<Module> modules, ISettingsService settingsService, IDataModelUIService dataModelUIService)
{
_modules = modules;
_dataModelUIService = dataModelUIService;
_updateTimer = new Timer(500);
_showFullPath = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
ExtraDataModelViewModels = new BindableCollection<DataModelPropertiesViewModel>();
ShowDataModelValues = settingsService.GetSetting<bool>("ProfileEditor.ShowDataModelValues");
@ -127,6 +129,8 @@ namespace Artemis.UI.Shared.Input
/// </summary>
public PluginSetting<bool> ShowDataModelValues { get; }
public PluginSetting<bool> ShowFullPath { get; }
/// <summary>
/// Gets or sets root the data model view model
/// </summary>
@ -177,7 +181,15 @@ namespace Artemis.UI.Shared.Input
/// <summary>
/// Gets the display name of the currently selected property
/// </summary>
public string? DisplayValue => DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
public string? DisplayValue
{
get
{
if (_showFullPath.Value)
return DisplayPath;
return DataModelPath?.GetPropertyDescription()?.Name ?? DataModelPath?.Segments.LastOrDefault()?.Identifier;
}
}
/// <summary>
/// Gets the human readable path of the currently selected property
@ -190,7 +202,8 @@ namespace Artemis.UI.Shared.Input
return "Click to select a property";
if (!DataModelPath.IsValid)
return "Invalid path";
return string.Join(" ", DataModelPath.Segments.Select(s => s.GetPropertyDescription()?.Name ?? s.Identifier));
return string.Join(" ", DataModelPath.Segments.Where(s => s.GetPropertyDescription()!= null).Select(s => s.GetPropertyDescription()!.Name));
}
}
@ -244,8 +257,10 @@ namespace Artemis.UI.Shared.Input
ExtraDataModelViewModels.CollectionChanged += ExtraDataModelViewModelsOnCollectionChanged;
_updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
_showFullPath.SettingChanged += ShowFullPathOnSettingChanged;
}
private void ExecuteSelectPropertyCommand(object? context)
{
if (context is not DataModelVisualizationViewModel selected)
@ -271,7 +286,8 @@ namespace Artemis.UI.Shared.Input
_updateTimer.Stop();
_updateTimer.Dispose();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
_showFullPath.SettingChanged -= ShowFullPathOnSettingChanged;
DataModelViewModel?.Dispose();
DataModelPath?.Dispose();
}
@ -330,6 +346,11 @@ namespace Artemis.UI.Shared.Input
}
}
private void ShowFullPathOnSettingChanged(object? sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(DisplayValue));
}
#endregion
#region Events

View File

@ -16,9 +16,9 @@ namespace Artemis.UI.Shared
private int _index;
private Type? _listType;
internal DataModelListPropertiesViewModel(Type listType) : base(null, null, null)
internal DataModelListPropertiesViewModel(Type listType, string? name) : base(null, null, null)
{
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType);
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name);
DataModel = _listPredicateWrapper;
ListType = listType;
}

View File

@ -14,17 +14,17 @@ namespace Artemis.UI.Shared
private int _index;
private Type? _listType;
internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel) : base(null, null, null)
internal DataModelListPropertyViewModel(Type listType, DataModelDisplayViewModel displayViewModel, string? name) : base(null, null, null)
{
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType);
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name);
DataModel = _listPredicateWrapper;
ListType = listType;
DisplayViewModel = displayViewModel;
}
internal DataModelListPropertyViewModel(Type listType) : base(null, null, null)
internal DataModelListPropertyViewModel(Type listType, string? name) : base(null, null, null)
{
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType);
_listPredicateWrapper = ListPredicateWrapperDataModel.Create(listType, name);
DataModel = _listPredicateWrapper;
ListType = listType;
}

View File

@ -91,7 +91,7 @@ namespace Artemis.UI.Shared
DataModelVisualizationViewModel? child;
if (ListChildren.Count <= index)
{
child = CreateListChild(dataModelUIService, item.GetType());
child = CreateListChild(dataModelUIService, item.GetType(), DataModelPath?.GetPropertyDescription()?.ListItemName);
if (child == null)
continue;
ListChildren.Add(child);
@ -130,18 +130,18 @@ namespace Artemis.UI.Shared
return $"[List] {DisplayPath ?? Path} - {ListCount} item(s)";
}
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType)
private DataModelVisualizationViewModel? CreateListChild(IDataModelUIService dataModelUIService, Type listType, string? name)
{
// If a display VM was found, prefer to use that in any case
DataModelDisplayViewModel? typeViewModel = dataModelUIService.GetDataModelDisplayViewModel(listType, PropertyDescription);
if (typeViewModel != null)
return new DataModelListPropertyViewModel(listType, typeViewModel);
return new DataModelListPropertyViewModel(listType, typeViewModel, name);
// For primitives, create a property view model, it may be null that is fine
if (listType.IsPrimitive || listType.IsEnum || listType == typeof(string))
return new DataModelListPropertyViewModel(listType);
return new DataModelListPropertyViewModel(listType, name);
// For other value types create a child view model
if (listType.IsClass || listType.IsStruct())
return new DataModelListPropertiesViewModel(listType);
return new DataModelListPropertiesViewModel(listType, name);
return null;
}

View File

@ -122,7 +122,7 @@ namespace Artemis.UI.Shared.Services
public void ShowExceptionDialog(string message, Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
_windowManager.ShowDialog(new ExceptionViewModel(message, exception));
Execute.OnUIThread(() => _windowManager.ShowDialog(new ExceptionViewModel(message, exception)));
}
private IKernel GetBestKernel()

View File

@ -24,7 +24,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
: base(dataModelConditionListPredicate, modules, profileEditorService, dataModelUIService, conditionOperatorService, settingsService)
{
_dataModelUIService = dataModelUIService;
DataModelPathSegment dataModelPathSegment = dataModelConditionListPredicate.LeftPath.Segments.ToList()[1];
var segmentDescription = dataModelPathSegment.GetPropertyDescription();
LeftSideColor = new SolidColorBrush(Color.FromRgb(71, 108, 188));
}
@ -47,7 +48,7 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
public override void Evaluate()
{
throw new NotImplementedException();
}
public override void UpdateModules()
@ -88,7 +89,8 @@ namespace Artemis.UI.Screens.ProfileEditor.Conditions
private DataModelPropertiesViewModel GetListDataModel()
{
ListPredicateWrapperDataModel wrapper = ListPredicateWrapperDataModel.Create(
DataModelConditionListPredicate.DataModelConditionList.ListType
DataModelConditionListPredicate.DataModelConditionList.ListType!,
DataModelConditionListPredicate.DataModelConditionList.ListPath?.GetPropertyDescription()?.ListItemName
);
return wrapper.CreateViewModel(_dataModelUIService, new DataModelUpdateConfiguration(true));

View File

@ -4,7 +4,6 @@ using System.ComponentModel;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.ProfileEditor.Conditions;
using Artemis.UI.Shared;
@ -91,6 +90,8 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
protected override void OnInitialActivate()
{
_profileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged;
Update(_profileEditorService.SelectedProfileElement);
base.OnInitialActivate();
}
@ -101,6 +102,11 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
}
private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
{
Update(e.RenderProfileElement);
}
private void Update(RenderProfileElement renderProfileElement)
{
if (RenderProfileElement != null)
{
@ -109,26 +115,26 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
RenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
}
RenderProfileElement = e.RenderProfileElement;
RenderProfileElement = renderProfileElement;
NotifyOfPropertyChange(nameof(DisplayContinuously));
NotifyOfPropertyChange(nameof(AlwaysFinishTimeline));
NotifyOfPropertyChange(nameof(ConditionBehaviourEnabled));
if (e.RenderProfileElement == null)
if (renderProfileElement == null)
{
ActiveItem = null;
return;
}
// Ensure the layer has a root display condition group
if (e.RenderProfileElement.DisplayCondition == null)
e.RenderProfileElement.DisplayCondition = new DataModelConditionGroup(null);
if (renderProfileElement.DisplayCondition == null)
renderProfileElement.DisplayCondition = new DataModelConditionGroup(null);
List<Module> modules = new();
if (_profileEditorService.SelectedProfileConfiguration?.Module != null)
modules.Add(_profileEditorService.SelectedProfileConfiguration.Module);
ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(e.RenderProfileElement.DisplayCondition, ConditionGroupType.General, modules);
ActiveItem = _dataModelConditionsVmFactory.DataModelConditionGroupViewModel(renderProfileElement.DisplayCondition, ConditionGroupType.General, modules);
ActiveItem.IsRootGroup = true;
DisplayStartHint = !RenderProfileElement.DisplayCondition.Children.Any();
@ -138,7 +144,7 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
RenderProfileElement.DisplayCondition.ChildRemoved += DisplayConditionOnChildrenModified;
RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
}
private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyOfPropertyChange(nameof(DisplayContinuously));

View File

@ -122,7 +122,9 @@
<MenuItem Header="Display Data Model Values"
IsCheckable="True"
IsChecked="{Binding ShowDataModelValues.Value}"/>
<MenuItem Header="Display Full Condition Paths"
IsCheckable="True"
IsChecked="{Binding ShowFullPaths.Value}"/>
<MenuItem Header="Apply All Data Bindings During Edit"
ToolTip="If enabled, updates all data bindings instead of only the one you are editing"
IsCheckable="True"

View File

@ -118,6 +118,7 @@ namespace Artemis.UI.Screens.ProfileEditor
public PluginSetting<GridLength> ElementPropertiesWidth => _settingsService.GetSetting("ProfileEditor.ElementPropertiesWidth", new GridLength(545));
public PluginSetting<bool> StopOnFocusLoss => _settingsService.GetSetting("ProfileEditor.StopOnFocusLoss", true);
public PluginSetting<bool> ShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<bool> ShowFullPaths => _settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
public PluginSetting<bool> FocusSelectedLayer => _settingsService.GetSetting("ProfileEditor.FocusSelectedLayer", true);
public PluginSetting<bool> AlwaysApplyDataBindings => _settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true);
@ -192,6 +193,7 @@ namespace Artemis.UI.Screens.ProfileEditor
{
StopOnFocusLoss.AutoSave = true;
ShowDataModelValues.AutoSave = true;
ShowFullPaths.AutoSave = true;
FocusSelectedLayer.AutoSave = true;
AlwaysApplyDataBindings.AutoSave = true;
@ -205,6 +207,7 @@ namespace Artemis.UI.Screens.ProfileEditor
{
StopOnFocusLoss.AutoSave = false;
ShowDataModelValues.AutoSave = false;
ShowFullPaths.AutoSave = false;
FocusSelectedLayer.AutoSave = false;
AlwaysApplyDataBindings.AutoSave = false;

View File

@ -26,7 +26,14 @@
<DockPanel>
<controls:AppBar Type="Dense" Title="{Binding ActiveItem.Plugin.Info.Name}" DockPanel.Dock="Top" Margin="-18 0 0 0" ShowShadow="False">
<controls:AppBar.AppIcon>
<shared:ArtemisIcon Icon="{Binding Icon}"></shared:ArtemisIcon>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Margin="0 5 0 0"
Width="20"
Height="20"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
</controls:AppBar.AppIcon>
</controls:AppBar>

View File

@ -1,4 +1,5 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared;
using Stylet;
@ -11,10 +12,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
_configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Icon = configurationViewModel.Plugin.Info.Icon;
Plugin = configurationViewModel.Plugin;
}
public object Icon { get; }
public Plugin Plugin { get; }
protected override void OnInitialActivate()
{

View File

@ -256,7 +256,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<StackPanel Grid.Row="0" Margin="0 0 0 4">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Activation conditions
</TextBlock>

View File

@ -8,6 +8,7 @@ using Artemis.UI.Screens.Settings.Device;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using RGB.NET.Core;
using Stylet;
using KeyboardLayoutType = Artemis.Core.KeyboardLayoutType;
namespace Artemis.UI.Services
@ -62,8 +63,11 @@ namespace Artemis.UI.Services
private async void WindowServiceOnMainWindowOpened(object sender, EventArgs e)
{
List<ArtemisDevice> devices = _rgbService.Devices.Where(device => DeviceNeedsLayout(device) && !_ignoredDevices.Contains(device)).ToList();
foreach (ArtemisDevice artemisDevice in devices)
await RequestLayoutInput(artemisDevice);
await Execute.OnUIThreadAsync(async () =>
{
foreach (ArtemisDevice artemisDevice in devices)
await RequestLayoutInput(artemisDevice);
});
}
private async void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e)
@ -77,7 +81,7 @@ namespace Artemis.UI.Services
return;
}
await RequestLayoutInput(e.Device);
await Execute.OnUIThreadAsync(async () => await RequestLayoutInput(e.Device));
}
#endregion