Meta - Replace WPF projects with Avalonia projects

This commit is contained in:
Robert committed 2022-03-27 11:55:26 +02:00
1 parent fcf0376a9a
commit d7f3ee1190
1110 files changed
+10872 -52240

No files matched your search

@@ -1,83 +0,0 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Debug.DebugView"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:debug="clr-namespace:Artemis.UI.Screens.Settings.Debug"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
mc:Ignorable="d"
Title="Artemis debugger"
TitleBarIcon="{svgc:SvgImage Source=/Resources/Images/Logo/bow-white.svg}"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
FadeContentIfInactive="False"
Width="1200"
Height="800"
d:DesignHeight="800" d:DesignWidth="800" d:DataContext="{d:DesignInstance debug:DebugViewModel}"
Icon="/Resources/Images/Logo/bow.ico"
Topmost="{Binding StayOnTopSetting.Value}">
<materialDesign:DialogHost IsTabStop="False" Focusable="False" Identifier="DebuggerDialog" DialogTheme="Inherit">
<DockPanel>
<mde:AppBar Type="Dense" Title="Debugger" DockPanel.Dock="Top" Margin="-18 0 0 0">
<mde:AppBar.AppIcon>
<materialDesign:PackIcon Kind="Matrix" Width="20" Height="28" />
</mde:AppBar.AppIcon>
<materialDesign:PopupBox DockPanel.Dock="Right" PlacementMode="BottomAndAlignRightEdges" StaysOpen="False">
<StackPanel>
<Button Command="{s:Action ToggleStayOnTop}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}"
Margin="0 0 10 0"
IsChecked="{Binding StayOnTopSetting.Value}" />
<TextBlock>Stay on top</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ForceGarbageCollection}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<materialDesign:PackIcon Kind="DeleteEmpty" />
<TextBlock Margin="10 0">Force garbage collection</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Elevate}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<materialDesign:PackIcon Kind="ShieldHalfFull" />
<TextBlock Margin="10 0">Elevate permissions</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Drop}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<materialDesign:PackIcon Kind="ChevronDoubleDown" />
<TextBlock Margin="10 0">Drop permissions</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Restart}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<materialDesign:PackIcon Kind="Restart" />
<TextBlock Margin="10 0">Restart</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</mde:AppBar>
<TabControl ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName"
Style="{StaticResource MaterialDesignTabControl}">
<TabControl.ContentTemplate>
<DataTemplate>
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect FadeIn}">
<ContentControl s:View.Model="{Binding}" TextElement.Foreground="{DynamicResource MaterialDesignBody}" Margin="10" />
</materialDesign:TransitioningContent>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</materialDesign:DialogHost>
</mde:MaterialWindow>
@@ -1,71 +0,0 @@
using System;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Settings.Debug.Tabs;
using Artemis.UI.Screens.Settings.Debug.Tabs.Performance;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug
{
public class DebugViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly ICoreService _coreService;
public DebugViewModel(
ISettingsService settingsService,
ICoreService coreService,
RenderDebugViewModel renderDebugViewModel,
DataModelDebugViewModel dataModelDebugViewModel,
LogsDebugViewModel logsDebugViewModel,
PerformanceDebugViewModel performanceDebugViewModel)
{
_coreService = coreService;
Items.Add(renderDebugViewModel);
Items.Add(dataModelDebugViewModel);
Items.Add(logsDebugViewModel);
Items.Add(performanceDebugViewModel);
ActiveItem = renderDebugViewModel;
StayOnTopSetting = settingsService.GetSetting("Debugger.StayOnTop", false);
}
public PluginSetting<bool> StayOnTopSetting { get; }
public string Title => "Debugger";
public bool CanElevate => !_coreService.IsElevated;
public bool CanDrop => _coreService.IsElevated;
public void ToggleStayOnTop()
{
StayOnTopSetting.Value = !StayOnTopSetting.Value;
}
protected override void OnClose()
{
StayOnTopSetting.Save();
base.OnClose();
}
public void ForceGarbageCollection()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
public void Elevate()
{
Core.Utilities.Restart(true, TimeSpan.FromMilliseconds(500), "--force-elevation");
}
public void Drop()
{
Core.Utilities.Restart(false, TimeSpan.Zero);
}
public void Restart()
{
Core.Utilities.Restart(_coreService.IsElevated, TimeSpan.FromMilliseconds(500));
}
}
}
@@ -1,178 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.DataModelDebugView"
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.UI.Screens.Settings.Debug.Tabs"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:wpf="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:dataModel="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DataModelDebugViewModel}">
<UserControl.Resources>
<dataModel:TypeToStringConverter x:Key="TypeToStringConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0 0 0 5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="0.5*" />
</Grid.ColumnDefinitions>
<!-- Property searching (nyi) -->
<wpf:PackIcon Grid.Column="0" Kind="Search" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="1" wpf:HintAssist.Hint="Search property" VerticalAlignment="Center" Margin="5 0" IsEnabled="False" />
<!-- Module filtering -->
<TextBlock Grid.Row="0" Grid.Column="3" VerticalAlignment="Center">Filter module</TextBlock>
<ToggleButton Grid.Row="0"
Grid.Column="4"
VerticalAlignment="Center"
Margin="5 0"
Style="{StaticResource MaterialDesignSwitchToggleButton}"
IsChecked="{Binding IsModuleFilterEnabled}" />
<ComboBox Grid.Row="0"
Grid.Column="5"
VerticalAlignment="Center"
wpf:HintAssist.Hint="Select a module"
IsEditable="True"
TextSearch.TextPath="DisplayName"
DisplayMemberPath="DisplayName"
Margin="5 0 0 0"
IsEnabled="{Binding IsModuleFilterEnabled}"
SelectedItem="{Binding SelectedModule}"
ItemsSource="{Binding Modules}" />
<StackPanel Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Orientation="Horizontal"
Margin="0 10 0 0"
ToolTip="Check to update values every half second instead of realtime">
<TextBlock VerticalAlignment="Center">Slow updates</TextBlock>
<ToggleButton VerticalAlignment="Center" Margin="5 0" Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding SlowUpdates}" />
</StackPanel>
</Grid>
<TreeView Grid.Row="1"
ItemsSource="{Binding MainDataModel.Children}"
HorizontalContentAlignment="Stretch"
VirtualizingStackPanel.IsVirtualizing="True">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource MaterialDesignTreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
</Style>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}" ItemsSource="{Binding Children}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding DisplayValue}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding CountDisplay, Mode=OneWay}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Value description -->
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<!-- Value display -->
<ContentControl Grid.Column="2" s:View.Model="{Binding DisplayViewModel}" FontFamily="Consolas" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListPropertyViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Value description -->
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding ListType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" ToolTip="{Binding PropertyDescription.Description}">
<Run>List item [</Run><Run Text="{Binding Index, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<!-- Value display -->
<ContentControl Grid.Column="2" s:View.Model="{Binding DisplayViewModel}" FontFamily="Consolas" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}" ItemsSource="{Binding Children}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Margin="0 0 5 0" FontWeight="Bold">
<Run>[</Run><Run Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip="{Binding PropertyDescription.Description}" />
<TextBlock Grid.Column="2"
Text="{Binding CountDisplay, Mode=OneWay}"
FontFamily="Consolas"
HorizontalAlignment="Right" />
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type dataModel:DataModelListPropertiesViewModel}" ItemsSource="{Binding DisplayViewModel.Children}">
<TextBlock>
<Run>List item [</Run><Run Text="{Binding Index, Mode=OneWay}" /><Run>]</Run>
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</UserControl>
@@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Core.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
public sealed class DataModelDebugViewModel : Screen, IDisposable
{
private readonly IDataModelUIService _dataModelUIService;
private readonly IPluginManagementService _pluginManagementService;
private readonly Timer _updateTimer;
private bool _isModuleFilterEnabled;
private DataModelPropertiesViewModel _mainDataModel;
private string _propertySearch;
private Module _selectedModule;
private bool _slowUpdates;
public DataModelDebugViewModel(IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
{
DisplayName = "DATA MODEL";
_dataModelUIService = dataModelUIService;
_pluginManagementService = pluginManagementService;
_updateTimer = new Timer(25);
Modules = new BindableCollection<Module>();
}
public DataModelPropertiesViewModel MainDataModel
{
get => _mainDataModel;
set => SetAndNotify(ref _mainDataModel, value);
}
public string PropertySearch
{
get => _propertySearch;
set => SetAndNotify(ref _propertySearch, value);
}
public bool SlowUpdates
{
get => _slowUpdates;
set
{
SetAndNotify(ref _slowUpdates, value);
_updateTimer.Interval = _slowUpdates ? 500 : 25;
}
}
public BindableCollection<Module> Modules { get; }
public Module SelectedModule
{
get => _selectedModule;
set
{
if (!SetAndNotify(ref _selectedModule, value)) return;
GetDataModel();
}
}
public bool IsModuleFilterEnabled
{
get => _isModuleFilterEnabled;
set
{
SetAndNotify(ref _isModuleFilterEnabled, value);
if (!IsModuleFilterEnabled)
SelectedModule = null;
else
GetDataModel();
}
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnClose()
{
_updateTimer.Dispose();
base.OnClose();
}
#endregion
protected override void OnActivate()
{
GetDataModel();
_updateTimer.Start();
_updateTimer.Elapsed += OnUpdateTimerOnElapsed;
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += OnPluginFeatureToggled;
PopulateModules();
base.OnActivate();
}
protected override void OnDeactivate()
{
_updateTimer.Stop();
_updateTimer.Elapsed -= OnUpdateTimerOnElapsed;
MainDataModel?.Dispose();
MainDataModel = null;
_pluginManagementService.PluginFeatureEnabled -= OnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled -= OnPluginFeatureToggled;
base.OnDeactivate();
}
private void OnPluginFeatureToggled(object sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature is Module)
PopulateModules();
}
private void OnUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
{
if (MainDataModel == null)
return;
lock (MainDataModel)
{
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
}
}
private void GetDataModel()
{
MainDataModel = SelectedModule != null
? _dataModelUIService.GetPluginDataModelVisualization(new List<Module>() {SelectedModule}, false)
: _dataModelUIService.GetMainDataModelVisualization();
}
private void PopulateModules()
{
Modules.Clear();
Modules.AddRange(_pluginManagementService.GetFeaturesOfType<Module>().Where(p => p.IsEnabled).OrderBy(m => m.Info.Name));
if (SelectedModule == null)
_dataModelUIService.UpdateModules(MainDataModel);
else if (!SelectedModule.IsEnabled)
SelectedModule = null;
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
_updateTimer?.Dispose();
}
#endregion
}
}
@@ -1,47 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.LogsDebugView"
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.UI.Screens.Settings.Debug.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:LogsDebugViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<materialDesign:Card Grid.Row="0" Grid.ColumnSpan="2" Margin="0 0 0 5" Background="#FF424242">
<FlowDocumentScrollViewer Document="{Binding LogsDocument}" FontFamily="Consolas" HorizontalScrollBarVisibility="Auto">
<FlowDocumentScrollViewer.Resources>
<Style TargetType="FlowDocument">
<Setter Property="TextElement.FontFamily" Value="Consolas" />
<Setter Property="TextElement.FontSize" Value="12" />
<Setter Property="PageWidth" Value="2024" />
</Style>
<Style TargetType="Paragraph">
<Setter Property="Margin" Value="0 1" />
</Style>
</FlowDocumentScrollViewer.Resources>
</FlowDocumentScrollViewer>
</materialDesign:Card>
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" Margin="0 0 5 0" VerticalAlignment="Center">
<materialDesign:PackIcon Kind="Text" Margin="0 0 0 -3"></materialDesign:PackIcon>
When reporting errors please don't take a screenshot of the logs, instead upload the full log or select &amp; copy a part of it, thanks!
</TextBlock>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 5">
<Button Style="{StaticResource MaterialDesignRaisedButton}" Command="{s:Action UploadLogs}" Width="150" Margin="0 0 5 0">
UPLOAD LOGS
</Button>
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowLogsFolder}" Width="150" Margin="5 0 0 0">
SHOW LOGS
</Button>
</StackPanel>
</Grid>
</UserControl>
@@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
/// <summary>
/// Interaction logic for LogsDebugView.xaml
/// </summary>
public partial class LogsDebugView : UserControl
{
public LogsDebugView()
{
InitializeComponent();
}
}
}
@@ -1,127 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Serilog.Events;
using Serilog.Formatting.Display;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
public class LogsDebugViewModel : Screen
{
private readonly IDialogService _dialogService;
private readonly MessageTemplateTextFormatter _formatter;
private ScrollViewer _scrollViewer;
public LogsDebugViewModel(IDialogService dialogService)
{
DisplayName = "LOGS";
_dialogService = dialogService;
_formatter = new MessageTemplateTextFormatter(
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"
);
}
public FlowDocument LogsDocument { get; } = new();
public void ShowLogsFolder()
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
public async Task UploadLogs()
{
bool confirmed = await _dialogService.ShowConfirmDialogAt(
"DebuggerDialog",
"Upload logs",
"Automatically uploading logs is not yet implemented.\r\n\r\n" +
"To manually upload a log simply drag the log file from the logs folder\r\n" +
"into Discord or the GitHub issue textbox, depending on what you're using.",
"OPEN LOGS FOLDER",
"CANCEL");
if (confirmed)
ShowLogsFolder();
}
private Paragraph CreateLogEventParagraph(LogEvent logEvent)
{
Paragraph paragraph = new(new Run(RenderLogEvent(logEvent)))
{
// But mah MVVM
Foreground = logEvent.Level switch
{
LogEventLevel.Verbose => new SolidColorBrush(Colors.White),
LogEventLevel.Debug => new SolidColorBrush(Color.FromRgb(216, 216, 216)),
LogEventLevel.Information => new SolidColorBrush(Color.FromRgb(93, 201, 255)),
LogEventLevel.Warning => new SolidColorBrush(Color.FromRgb(255, 177, 53)),
LogEventLevel.Error => new SolidColorBrush(Color.FromRgb(255, 63, 63)),
LogEventLevel.Fatal => new SolidColorBrush(Colors.Red),
_ => throw new ArgumentOutOfRangeException()
}
};
return paragraph;
}
private string RenderLogEvent(LogEvent logEvent)
{
using StringWriter writer = new();
_formatter.Format(logEvent, writer);
return writer.ToString().Trim();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
LogsDocument.Blocks.AddRange(LogStore.Events.Select(e => CreateLogEventParagraph(e)));
LogStore.EventAdded += LogStoreOnEventAdded;
base.OnActivate();
}
private void LogStoreOnEventAdded(object sender, LogEventEventArgs e)
{
Execute.PostToUIThread(() =>
{
LogsDocument.Blocks.Add(CreateLogEventParagraph(e.LogEvent));
while (LogsDocument.Blocks.Count > 500)
LogsDocument.Blocks.Remove(LogsDocument.Blocks.FirstBlock);
if (_scrollViewer != null && Math.Abs(_scrollViewer.VerticalOffset - _scrollViewer.ScrollableHeight) < 10)
_scrollViewer.ScrollToBottom();
});
}
/// <inheritdoc />
protected override void OnDeactivate()
{
LogStore.EventAdded -= LogStoreOnEventAdded;
LogsDocument.Blocks.Clear();
base.OnDeactivate();
}
/// <inheritdoc />
protected override void OnViewLoaded()
{
ScrollViewer scrollViewer = VisualTreeUtilities.FindChild<ScrollViewer>(View, null);
_scrollViewer = scrollViewer;
_scrollViewer?.ScrollToBottom();
base.OnViewLoaded();
}
#endregion
}
}
@@ -1,60 +0,0 @@
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
{
public class PerformanceDebugMeasurementViewModel : PropertyChangedBase
{
private string _average;
private string _last;
private string _max;
private string _min;
private string _percentile;
public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
{
Measurement = measurement;
}
public ProfilingMeasurement Measurement { get; }
public string Last
{
get => _last;
set => SetAndNotify(ref _last, value);
}
public string Average
{
get => _average;
set => SetAndNotify(ref _average, value);
}
public string Min
{
get => _min;
set => SetAndNotify(ref _min, value);
}
public string Max
{
get => _max;
set => SetAndNotify(ref _max, value);
}
public string Percentile
{
get => _percentile;
set => SetAndNotify(ref _percentile, value);
}
public void Update()
{
Last = Measurement.GetLast().TotalMilliseconds + " ms";
Average = Measurement.GetAverage().TotalMilliseconds + " ms";
Min = Measurement.GetMin().TotalMilliseconds + " ms";
Max = Measurement.GetMax().TotalMilliseconds + " ms";
Percentile = Measurement.GetPercentile(0.95).TotalMilliseconds + " ms";
}
}
}
@@ -1,33 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugPluginView"
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.UI.Screens.Settings.Debug.Tabs.Performance"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PerformanceDebugPluginViewModel}">
<materialDesign:Card Margin="0 5" Padding="10">
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<shared:ArtemisIcon Grid.Column="0" Icon="{Binding Plugin.Info.ResolvedIcon}" Width="24" Height="24" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="{Binding Plugin.Info.Name}" />
</Grid>
<ItemsControl ItemsSource="{Binding Profilers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</materialDesign:Card>
</UserControl>
@@ -1,28 +0,0 @@
using System.Linq;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
{
public class PerformanceDebugPluginViewModel : Screen
{
public PerformanceDebugPluginViewModel(Plugin plugin)
{
Plugin = plugin;
}
public Plugin Plugin { get; }
public BindableCollection<PerformanceDebugProfilerViewModel> Profilers { get; } = new();
public void Update()
{
foreach (Profiler pluginProfiler in Plugin.Profilers.Where(p => p.Measurements.Any()))
if (Profilers.All(p => p.Profiler != pluginProfiler))
Profilers.Add(new PerformanceDebugProfilerViewModel(pluginProfiler));
foreach (PerformanceDebugProfilerViewModel profilerViewModel in Profilers)
profilerViewModel.Update();
}
}
}
@@ -1,34 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugProfilerView"
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.UI.Screens.Settings.Debug.Tabs.Performance"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PerformanceDebugProfilerViewModel}">
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" Text="{Binding Profiler.Name}" Margin="10 10 0 0" />
<DataGrid ItemsSource="{Binding Measurements}"
d:DataContext="{d:DesignInstance Type={x:Type local:PerformanceDebugMeasurementViewModel}}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="16 5 5 5"
materialDesign:DataGridAssist.ColumnHeaderPadding="16 5 5 5"
CanUserResizeRows="False"
Margin="10 5 10 10">
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding Measurement.Identifier}" Header="Identifier" />
<materialDesign:DataGridTextColumn Binding="{Binding Last}" Header="Last" />
<materialDesign:DataGridTextColumn Binding="{Binding Min}" Header="Min" />
<materialDesign:DataGridTextColumn Binding="{Binding Max}" Header="Max" />
<materialDesign:DataGridTextColumn Binding="{Binding Average}" Header="Average" />
<materialDesign:DataGridTextColumn Binding="{Binding Percentile}" Header="95th percentile" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
@@ -1,30 +0,0 @@
using System.Linq;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
{
public class PerformanceDebugProfilerViewModel : Screen
{
public Profiler Profiler { get; }
public PerformanceDebugProfilerViewModel(Profiler profiler)
{
Profiler = profiler;
}
public BindableCollection<PerformanceDebugMeasurementViewModel> Measurements { get; } = new();
public void Update()
{
foreach ((string _, ProfilingMeasurement measurement) in Profiler.Measurements)
{
if (Measurements.All(m => m.Measurement != measurement))
Measurements.Add(new PerformanceDebugMeasurementViewModel(measurement));
}
foreach (PerformanceDebugMeasurementViewModel profilingMeasurementViewModel in Measurements)
profilingMeasurementViewModel.Update();
}
}
}
@@ -1,35 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.Performance.PerformanceDebugView"
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.UI.Screens.Settings.Debug.Tabs.Performance"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PerformanceDebugViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" TextWrapping="Wrap">
In this window you can see how much CPU time different plugin features are taking.
If you are having performance issues, below you can find out which plugin might be the culprit.
</TextBlock>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Hidden" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<ItemsControl ItemsSource="{Binding Items}" Margin="0 0 10 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<materialDesign:TransitioningContent OpeningEffect="{materialDesign:TransitionEffect SlideInFromLeft}"
OpeningEffectsOffset="{materialDesign:IndexedItemOffsetMultiplier 0:0:0.05}">
<ContentControl s:View.Model="{Binding}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</materialDesign:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
@@ -1,35 +0,0 @@
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.UI.Screens.Settings.Debug.Tabs.Performance
{
/// <summary>
/// Interaction logic for PerformanceDebugView.xaml
/// </summary>
public partial class PerformanceDebugView : UserControl
{
public PerformanceDebugView()
{
InitializeComponent();
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
ScrollViewer scv = (ScrollViewer)sender;
scv.ScrollToVerticalOffset(scv.VerticalOffset - e.Delta);
e.Handled = true;
}
}
}
@@ -1,77 +0,0 @@
using System.Linq;
using System.Timers;
using Artemis.Core;
using Artemis.Core.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
{
public class PerformanceDebugViewModel : Conductor<PerformanceDebugPluginViewModel>.Collection.AllActive
{
private readonly IPluginManagementService _pluginManagementService;
private readonly Timer _updateTimer;
public PerformanceDebugViewModel(IPluginManagementService pluginManagementService)
{
_pluginManagementService = pluginManagementService;
_updateTimer = new Timer(500);
DisplayName = "PERFORMANCE";
_updateTimer.Elapsed += UpdateTimerOnElapsed;
}
private void UpdateTimerOnElapsed(object sender, ElapsedEventArgs e)
{
foreach (PerformanceDebugPluginViewModel viewModel in Items)
viewModel.Update();
}
private void FeatureToggled(object sender, PluginFeatureEventArgs e)
{
Items.Clear();
PopulateItems();
}
private void PluginToggled(object sender, PluginEventArgs e)
{
Items.Clear();
PopulateItems();
}
private void PopulateItems()
{
Items.AddRange(_pluginManagementService.GetAllPlugins()
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
.OrderBy(p => p.Info.Name)
.Select(p => new PerformanceDebugPluginViewModel(p)));
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
PopulateItems();
_updateTimer.Start();
_pluginManagementService.PluginDisabled += PluginToggled;
_pluginManagementService.PluginDisabled += PluginToggled;
_pluginManagementService.PluginFeatureEnabled += FeatureToggled;
_pluginManagementService.PluginFeatureDisabled += FeatureToggled;
base.OnActivate();
}
/// <inheritdoc />
protected override void OnDeactivate()
{
_updateTimer.Stop();
_pluginManagementService.PluginDisabled -= PluginToggled;
_pluginManagementService.PluginDisabled -= PluginToggled;
_pluginManagementService.PluginFeatureEnabled -= FeatureToggled;
_pluginManagementService.PluginFeatureDisabled -= FeatureToggled;
Items.Clear();
base.OnDeactivate();
}
#endregion
}
}
@@ -1,50 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Debug.Tabs.RenderDebugView"
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.UI.Screens.Settings.Debug.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance local:RenderDebugViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" TextWrapping="Wrap">
In this window you can view the inner workings of Artemis.
Please note that having this window open can have a performance impact on your system.
</TextBlock>
<Grid Grid.Row="1" Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
This image shows what is being rendered and dispatched to RGB.NET
</TextBlock>
<TextBlock Grid.Column="1" HorizontalAlignment="Right" Margin="0,0,5,0">
<Run Text="FPS: " />
<Run FontWeight="Bold" Text="{Binding CurrentFps}" />
<Run Text="at" />
<Run Text="{Binding RenderWidth}" /><Run Text="x" /><Run Text="{Binding RenderHeight}" />
<Run Text="- Renderer:"></Run>
<Run Text="{Binding Renderer}"></Run>
</TextBlock>
</Grid>
<materialDesign:Card Grid.Row="2" Margin="0 5" Background="{StaticResource Checkerboard}">
<Image Source="{Binding CurrentFrame}" />
</materialDesign:Card>
<StackPanel Grid.Row="3" HorizontalAlignment="Right" Margin="0 5">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action SaveFrame}">SAVE FRAME</Button>
</StackPanel>
</Grid>
</UserControl>
@@ -1,154 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Timers;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Artemis.Core;
using Artemis.Core.Services;
using Ookii.Dialogs.Wpf;
using SkiaSharp;
using SkiaSharp.Views.WPF;
using Stylet;
namespace Artemis.UI.Screens.Settings.Debug.Tabs
{
public class RenderDebugViewModel : Screen
{
private readonly ICoreService _coreService;
private readonly Timer _fpsTimer;
private double _currentFps;
private WriteableBitmap _currentFrame;
private int _renderWidth;
private int _renderHeight;
private string _frameTargetPath;
private string _renderer;
private int _frames;
public RenderDebugViewModel(ICoreService coreService)
{
DisplayName = "RENDERING";
_coreService = coreService;
_fpsTimer = new Timer(1000);
_fpsTimer.Start();
}
public WriteableBitmap CurrentFrame
{
get => _currentFrame;
set => SetAndNotify(ref _currentFrame, value);
}
public double CurrentFps
{
get => _currentFps;
set => SetAndNotify(ref _currentFps, value);
}
public int RenderWidth
{
get => _renderWidth;
set => SetAndNotify(ref _renderWidth, value);
}
public int RenderHeight
{
get => _renderHeight;
set => SetAndNotify(ref _renderHeight, value);
}
public string Renderer
{
get => _renderer;
set => SetAndNotify(ref _renderer, value);
}
public void SaveFrame()
{
VistaSaveFileDialog dialog = new VistaSaveFileDialog {Filter = "Portable network graphic (*.png)|*.png", Title = "Save render frame"};
dialog.FileName = $"Artemis frame {DateTime.Now:yyyy-dd-M--HH-mm-ss}.png";
bool? result = dialog.ShowDialog();
if (result == true)
{
if (dialog.FileName.EndsWith(".png"))
_frameTargetPath = dialog.FileName;
else
_frameTargetPath = dialog.FileName + ".png";
}
}
protected override void OnActivate()
{
_coreService.FrameRendered += CoreServiceOnFrameRendered;
_fpsTimer.Elapsed += FpsTimerOnElapsed;
base.OnActivate();
}
protected override void OnDeactivate()
{
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
_fpsTimer.Elapsed -= FpsTimerOnElapsed;
base.OnDeactivate();
}
protected override void OnClose()
{
_fpsTimer.Dispose();
base.OnClose();
}
private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e)
{
_frames++;
using SKImage skImage = e.Texture.Surface.Snapshot();
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
if (_frameTargetPath != null)
{
using (SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100))
{
using (FileStream stream = File.OpenWrite(_frameTargetPath))
{
data.SaveTo(stream);
}
}
_frameTargetPath = null;
}
RenderHeight = bitmapInfo.Height;
RenderWidth = bitmapInfo.Width;
Execute.OnUIThreadSync(() =>
{
// ReSharper disable twice CompareOfFloatsByEqualityOperator
if (CurrentFrame == null || CurrentFrame.Width != bitmapInfo.Width || CurrentFrame.Height != bitmapInfo.Height)
{
CurrentFrame = e.Texture.Surface.Snapshot().ToWriteableBitmap();
return;
}
CurrentFrame.Lock();
using (SKPixmap pixmap = new(bitmapInfo, CurrentFrame.BackBuffer, CurrentFrame.BackBufferStride))
{
// ReSharper disable once AccessToDisposedClosure - Looks fine
skImage.ReadPixels(pixmap, 0, 0);
}
CurrentFrame.AddDirtyRect(new Int32Rect(0, 0, CurrentFrame.PixelWidth, CurrentFrame.PixelHeight));
CurrentFrame.Unlock();
});
}
private void FpsTimerOnElapsed(object sender, ElapsedEventArgs e)
{
CurrentFps = _frames;
// Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
Renderer = $"HighAccuracyTimers: {Stopwatch.IsHighResolution}";
_frames = 0;
}
}
}
@@ -1,160 +0,0 @@
<mde:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Device.DeviceDialogView"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mde="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:device="clr-namespace:Artemis.UI.Screens.Settings.Device"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
mc:Ignorable="d"
Title="{Binding DisplayName}"
TitleBarIcon="{svgc:SvgImage Source=/Resources/Images/Logo/bow-white.svg}"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
FadeContentIfInactive="False"
Width="1400"
Height="800"
d:DesignHeight="800" d:DesignWidth="800"
d:DataContext="{d:DesignInstance device:DeviceDialogViewModel}"
Icon="/Resources/Images/Logo/bow.ico">
<mde:MaterialWindow.Resources>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
</mde:MaterialWindow.Resources>
<mde:MaterialWindow.InputBindings>
<KeyBinding Command="{s:Action ClearSelection}" Key="Escape" />
</mde:MaterialWindow.InputBindings>
<materialDesign:DialogHost IsTabStop="False"
Focusable="False"
Identifier="DeviceDialog"
DialogTheme="Inherit"
SnackbarMessageQueue="{Binding DeviceMessageQueue}">
<DockPanel>
<mde:AppBar Type="Dense"
Title="{Binding Device.RgbDevice.DeviceInfo.Model}"
ShowShadow="True"
DockPanel.Dock="Top"
Margin="-18 0 0 0">
<mde:AppBar.AppIcon>
<materialDesign:PackIcon Kind="HammerWrench" Width="20" Height="28" />
</mde:AppBar.AppIcon>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Identify" Command="{s:Action IdentifyDevice}">
<materialDesign:PackIcon Kind="AlarmLight" />
</Button>
<materialDesign:PopupBox PlacementMode="BottomAndAlignRightEdges" StaysOpen="False">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Plugin" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action OpenImageDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Image" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open layout image directory</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action ReloadLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload layout</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ExportLayout}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Xml" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Export layout</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</mde:AppBar>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Name="DeviceDisplayGrid" Grid.Column="0">
<Grid.Background>
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="0 0 25 25" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Grid Width="20" Height="20">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
<Rectangle Grid.Row="0" Grid.Column="1" />
<Rectangle Grid.Row="1" Grid.Column="0" />
<Rectangle Grid.Row="1" Grid.Column="1" Fill="Black" Opacity="0.15" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
<shared:DeviceVisualizer Device="{Binding Device}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ShowColors="True"
LedClicked="{s:Action OnLedClicked}"
Margin="20"/>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Visibility="{Binding Device.Layout.RgbLayout.Author, Converter={StaticResource NullToVisibilityConverter}}"
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
Margin="15">
Device layout by <Run FontWeight="Bold" Text="{Binding Device.Layout.RgbLayout.Author}"/>
</TextBlock>
</Grid>
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" Panel.ZIndex="3" />
<materialDesign:Card Grid.Column="2"
materialDesign:ShadowAssist.ShadowDepth="Depth3"
Background="{DynamicResource MaterialDesignPaper}">
<Grid>
<TabControl
Style="{StaticResource MaterialDesignTabControl}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<materialDesign:Snackbar x:Name="DeviceSnackbar"
MessageQueue="{Binding DeviceMessageQueue}"
materialDesign:SnackbarMessage.InlineActionButtonMaxHeight="80"
materialDesign:SnackbarMessage.ContentMaxHeight="200" />
</Grid>
</materialDesign:Card>
</Grid>
</DockPanel>
</materialDesign:DialogHost>
</mde:MaterialWindow>
@@ -1,261 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Input;
using System.Xml.Serialization;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Shared;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Ookii.Dialogs.Wpf;
using RGB.NET.Core;
using RGB.NET.Layout;
using SkiaSharp;
using Stylet;
using Constants = Artemis.Core.Constants;
namespace Artemis.UI.Screens.Settings.Device
{
public class DeviceDialogViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService;
private readonly IDeviceDebugVmFactory _factory;
private readonly IRgbService _rgbService;
private SnackbarMessageQueue _deviceMessageQueue;
private BindableCollection<ArtemisLed> _selectedLeds;
private ArtemisDevice _device;
public DeviceDialogViewModel(ArtemisDevice device,
ICoreService coreService,
IDeviceService deviceService,
IRgbService rgbService,
IDialogService dialogService,
IDeviceDebugVmFactory factory)
{
_coreService = coreService;
_deviceService = deviceService;
_rgbService = rgbService;
_dialogService = dialogService;
_factory = factory;
PanZoomViewModel = new PanZoomViewModel();
SelectedLeds = new BindableCollection<ArtemisLed>();
Initialize(device);
}
private void Initialize(ArtemisDevice device)
{
if (SelectedLeds.Any())
SelectedLeds.Clear();
if (Device != null)
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
Device = device;
Device.DeviceUpdated += DeviceOnDeviceUpdated;
int activeTabIndex = 0;
if (Items.Any())
{
activeTabIndex = Items.IndexOf(ActiveItem);
Items.Clear();
}
Items.Add(_factory.DevicePropertiesTabViewModel(Device));
if (Device.DeviceType == RGBDeviceType.Keyboard)
Items.Add(_factory.InputMappingsTabViewModel(Device));
Items.Add(_factory.DeviceInfoTabViewModel(Device));
Items.Add(_factory.DeviceLedsTabViewModel(Device));
ActiveItem = Items[activeTabIndex];
DisplayName = $"{Device.RgbDevice.DeviceInfo.Model} | Artemis";
}
public ArtemisDevice Device
{
get => _device;
set => SetAndNotify(ref _device, value);
}
public PanZoomViewModel PanZoomViewModel { get; }
public SnackbarMessageQueue DeviceMessageQueue
{
get => _deviceMessageQueue;
set => SetAndNotify(ref _deviceMessageQueue, value);
}
public bool CanExportLayout => Device.Layout?.IsValid ?? false;
public BindableCollection<ArtemisLed> SelectedLeds
{
get => _selectedLeds;
set => SetAndNotify(ref _selectedLeds, value);
}
public bool CanOpenImageDirectory => Device.Layout?.Image != null;
public void OnLedClicked(object sender, LedClickedEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
SelectedLeds.Clear();
SelectedLeds.Add(e.Led);
}
protected override void OnInitialActivate()
{
_coreService.FrameRendering += CoreServiceOnFrameRendering;
_rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
DeviceMessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
base.OnInitialActivate();
}
private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e)
{
if (e.Device != Device && e.Device.Identifier == Device.Identifier)
Execute.OnUIThread(() => Initialize(e.Device));
}
protected override void OnClose()
{
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
_rgbService.DeviceAdded -= RgbServiceOnDeviceAdded;
Device.DeviceUpdated -= DeviceOnDeviceUpdated;
base.OnClose();
}
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
{
if (SelectedLeds == null || !SelectedLeds.Any())
return;
using SKPaint highlightPaint = new() {Color = SKColors.White};
using SKPaint dimPaint = new() {Color = new SKColor(0, 0, 0, 192)};
foreach (ArtemisLed artemisLed in Device.Leds)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, SelectedLeds.Contains(artemisLed) ? highlightPaint : dimPaint);
}
private void DeviceOnDeviceUpdated(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CanExportLayout));
}
// ReSharper disable UnusedMember.Global
#region Command handlers
public void ClearSelection()
{
SelectedLeds.Clear();
}
public void IdentifyDevice()
{
_deviceService.IdentifyDevice(Device);
}
public void OpenPluginDirectory()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.DeviceProvider.Plugin.Directory.FullName);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
public void OpenImageDirectory()
{
if (!CanOpenImageDirectory)
return;
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.GetDirectoryName(Device.Layout.Image.AbsolutePath));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's image folder for you", e);
}
}
public void ReloadLayout()
{
_rgbService.ApplyBestDeviceLayout(Device);
}
public void ExportLayout()
{
if (Device.Layout == null)
return;
VistaFolderBrowserDialog dialog = new()
{
Description = "Select layout export target folder",
UseDescriptionForTitle = true,
ShowNewFolderButton = true,
SelectedPath = Path.Combine(Constants.DataFolder, "user layouts")
};
bool? result = dialog.ShowDialog();
if (result != true)
return;
string directory = Path.Combine(
dialog.SelectedPath,
Device.RgbDevice.DeviceInfo.Manufacturer,
Device.DeviceType.ToString()
);
string filePath = Path.Combine(directory, Device.DeviceProvider.GetDeviceLayoutName(Device));
Core.Utilities.CreateAccessibleDirectory(directory);
// XML
XmlSerializer serializer = new(typeof(DeviceLayout));
using StreamWriter writer = new(filePath);
serializer.Serialize(writer, Device.Layout!.RgbLayout);
// Device images
if (!Uri.IsWellFormedUriString(Device.Layout.FilePath, UriKind.Absolute))
return;
Uri targetDirectory = new(directory + "/", UriKind.Absolute);
Uri sourceDirectory = new(Path.GetDirectoryName(Device.Layout.FilePath)! + "/", UriKind.Absolute);
Uri deviceImageTarget = new(targetDirectory, Device.Layout.LayoutCustomDeviceData.DeviceImage);
// Create folder (if needed) and copy image
Core.Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(deviceImageTarget.LocalPath)!);
if (Device.Layout.Image != null && File.Exists(Device.Layout.Image.LocalPath) && !File.Exists(deviceImageTarget.LocalPath))
File.Copy(Device.Layout.Image.LocalPath, deviceImageTarget.LocalPath);
foreach (ArtemisLedLayout ledLayout in Device.Layout.Leds)
{
if (ledLayout.LayoutCustomLedData.LogicalLayouts == null)
continue;
// Only the image of the current logical layout is available as an URI, iterate each layout and find the images manually
foreach (LayoutCustomLedDataLogicalLayout logicalLayout in ledLayout.LayoutCustomLedData.LogicalLayouts)
{
Uri image = new(sourceDirectory, logicalLayout.Image);
Uri imageTarget = new(targetDirectory, logicalLayout.Image);
// Create folder (if needed) and copy image
Core.Utilities.CreateAccessibleDirectory(Path.GetDirectoryName(imageTarget.LocalPath)!);
if (File.Exists(image.LocalPath) && !File.Exists(imageTarget.LocalPath))
File.Copy(image.LocalPath, imageTarget.LocalPath);
}
}
}
#endregion
// ReSharper restore UnusedMember.Global
}
}
@@ -1,187 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Device.DeviceLayoutDialogView"
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.UI.Screens.Settings.Device"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mde="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml"
mc:Ignorable="d"
d:DesignHeight="1200" d:DesignWidth="1200"
d:DataContext="{d:DesignInstance local:DeviceLayoutDialogViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="16" Width="1200" Height="800" Visibility="{Binding SelectPhysicalLayout, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Select a physical layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap">
Artemis couldn't automatically determine the physical layout of your <Run Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />. <LineBreak />
In order for Artemis to know which keys are on your keyboard and where they're located, select the matching layout below.
<LineBreak />
<LineBreak />
P.S. Don't worry about missing special keys like num keys/function keys or macro keys, they aren't important here.
</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1" Margin="0 25">
<WrapPanel HorizontalAlignment="Center">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Foreground="{DynamicResource MaterialDesignBody}"
Command="{s:Action ApplyPhysicalLayout}"
CommandParameter="ISO"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="550"
Height="280">
<StackPanel>
<Image Source="/Resources/Images/PhysicalLayouts/iso.png" />
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 10 0 0">
ISO
</TextBlock>
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap">
Most commonly used in the EU (tall enter)
</TextBlock>
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}"
Foreground="{DynamicResource MaterialDesignBody}"
Command="{s:Action ApplyPhysicalLayout}"
CommandParameter="ANSI"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="550"
Height="280">
<StackPanel>
<Image Source="/Resources/Images/PhysicalLayouts/ansi.png" />
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 10 0 0">
ANSI
</TextBlock>
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap">
Most commonly used in the US (short enter)
</TextBlock>
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}"
Foreground="{DynamicResource MaterialDesignBody}"
Command="{s:Action ApplyPhysicalLayout}"
CommandParameter="ABNT"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="550"
Height="280">
<StackPanel>
<Image Source="/Resources/Images/PhysicalLayouts/abnt.png" />
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 10 0 0">
ABNT
</TextBlock>
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap">
Most commonly used in Brazil/Portugal (based on ISO)
</TextBlock>
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}"
Foreground="{DynamicResource MaterialDesignBody}"
Command="{s:Action ApplyPhysicalLayout}"
CommandParameter="KS"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="550"
Height="280">
<StackPanel>
<Image Source="/Resources/Images/PhysicalLayouts/ks.png" />
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 10 0 0">
KS
</TextBlock>
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap">
Most commonly used in South Korea
</TextBlock>
</StackPanel>
</Button>
<Button Style="{StaticResource MaterialDesignFlatButton}"
Foreground="{DynamicResource MaterialDesignBody}"
Command="{s:Action ApplyPhysicalLayout}"
CommandParameter="JIS"
HorizontalAlignment="Right"
Margin="0 0 10 0"
Width="550"
Height="280">
<StackPanel>
<Image Source="/Resources/Images/PhysicalLayouts/jis.png" />
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="0 10 0 0">
JIS
</TextBlock>
<TextBlock TextAlignment="Center" Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap">
Most commonly used in Japan (based on ISO)
</TextBlock>
</StackPanel>
</Button>
</WrapPanel>
</ScrollViewer>
</Grid>
<StackPanel Grid.Row="0" Margin="16" Width="800" Visibility="{Binding SelectPhysicalLayout, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
<StackPanel>
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Select a logical layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap">
Artemis couldn't automatically determine the logical layout of your <Run Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName, Mode=OneWay}" />. <LineBreak /><LineBreak />
While not as important as the physical layout, setting the correct logical layout will allow Artemis to show the right keycaps (if a matching layout file is present)
</TextBlock>
</StackPanel>
<mde:Autocomplete Style="{StaticResource MaterialDesignAutocomplete}"
AutocompleteSource="{Binding Path=AutocompleteSource}"
SelectedItem="{Binding SelectedRegion, Mode=TwoWay}"
SearchOnInitialFocus="True"
Hint="Select a logical layout"
Margin="0,16,0,0"
FontSize="15"
Foreground="{DynamicResource MaterialDesignBody}"
Background="{DynamicResource MaterialDesignPaper}">
<mde:Autocomplete.ItemTemplate>
<DataTemplate>
<TextBlock FontSize="14" VerticalAlignment="Center">
<Run Text="{Binding EnglishName, Mode=OneWay}" />
(<Run FontWeight="SemiBold" Text="{Binding TwoLetterISORegionName, Mode=OneWay}" />)
</TextBlock>
</DataTemplate>
</mde:Autocomplete.ItemTemplate>
</mde:Autocomplete>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="16">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Focusable="False"
IsCancel="True"
Command="{s:Action Cancel}"
Content="CANCEL" />
<Button x:Name="ConfirmButton"
Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True"
Focusable="True"
Command="{s:Action Confirm}"
Content="CONFIRM" />
</StackPanel>
</Grid>
</UserControl>
@@ -1,128 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using MaterialDesignExtensions.Model;
namespace Artemis.UI.Screens.Settings.Device
{
public class DeviceLayoutDialogViewModel : DialogViewModelBase
{
private readonly IRgbService _rgbService;
private bool _selectPhysicalLayout;
private RegionInfo _selectedRegion;
public DeviceLayoutDialogViewModel(ArtemisDevice device, IRgbService rgbService, IDialogService dialogService)
{
_rgbService = rgbService;
Device = device;
SelectPhysicalLayout = !device.DeviceProvider.CanDetectPhysicalLayout;
try
{
AutocompleteSource = new RegionInfoAutocompleteSource();
}
catch (Exception e)
{
dialogService.ShowExceptionDialog("Failed to get region information for keyboard layout selection", e);
Session?.Close(false);
}
}
public ArtemisDevice Device { get; }
public RegionInfoAutocompleteSource AutocompleteSource { get; }
public RegionInfo SelectedRegion
{
get => _selectedRegion;
set
{
SetAndNotify(ref _selectedRegion, value);
NotifyOfPropertyChange(nameof(CanConfirm));
}
}
public bool SelectPhysicalLayout
{
get => _selectPhysicalLayout;
set => SetAndNotify(ref _selectPhysicalLayout, value);
}
public bool CanConfirm => SelectedRegion != null;
public void ApplyPhysicalLayout(string physicalLayout)
{
Device.PhysicalLayout = Enum.Parse<KeyboardLayoutType>(physicalLayout);
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
SelectPhysicalLayout = false;
}
private void ApplyLogicalLayout(string logicalLayout)
{
Device.LogicalLayout = logicalLayout;
_rgbService.SaveDevice(Device);
_rgbService.ApplyBestDeviceLayout(Device);
}
public void Confirm()
{
if (!CanConfirm || Session == null || Session.IsEnded)
return;
ApplyLogicalLayout(SelectedRegion.TwoLetterISORegionName);
Session?.Close(true);
}
}
public class RegionInfoAutocompleteSource : IAutocompleteSource<RegionInfo>
{
private const int LOCALE_NEUTRAL = 0x0000;
private const int LOCALE_CUSTOM_DEFAULT = 0x0c00;
private const int LOCALE_INVARIANT = 0x007F;
public List<RegionInfo> Regions { get; set; }
public RegionInfoAutocompleteSource()
{
// RegionInfo does not support some LCIDs and they show up with certain locale settings
Regions = CultureInfo.GetCultures(CultureTypes.SpecificCultures)
.Where(c => c.LCID != LOCALE_INVARIANT && c.LCID != LOCALE_NEUTRAL && c.LCID != LOCALE_CUSTOM_DEFAULT)
.Select(c => new RegionInfo(c.LCID))
.GroupBy(r => r.EnglishName)
.Select(g => g.First())
.OrderBy(r => r.EnglishName)
.ToList();
}
IEnumerable<RegionInfo> IAutocompleteSource<RegionInfo>.Search(string searchTerm)
{
if (searchTerm == null)
return Regions;
searchTerm = searchTerm.ToLower();
return Regions.Where(r => r.EnglishName.ToLower().Contains(searchTerm) ||
r.NativeName.ToLower().Contains(searchTerm) ||
r.TwoLetterISORegionName.ToLower().Contains(searchTerm));
}
public IEnumerable Search(string searchTerm)
{
if (searchTerm == null)
return Regions;
searchTerm = searchTerm.ToLower();
return Regions.Where(r => r.EnglishName.ToLower().Contains(searchTerm) ||
r.NativeName.ToLower().Contains(searchTerm) ||
r.TwoLetterISORegionName.ToLower().Contains(searchTerm));
}
}
}
@@ -1,132 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceInfoTabView"
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:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance tabs:DeviceInfoTabViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- First row -->
<StackPanel HorizontalAlignment="Stretch" Grid.Column="0" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Top" Margin="15 15 7.5 0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device name</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Manufacturer</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Device type</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.DeviceType}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<StackPanel Visibility="{Binding IsKeyboard, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Physical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.PhysicalLayout}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Top" Margin="7.5 15 15 0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Size (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Size}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Location (1px = 1mm)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Location}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Rotation (degrees)</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.RgbDevice.Rotation.Degrees}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<StackPanel Visibility="{Binding IsKeyboard, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Logical layout</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.LogicalLayout}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
</StackPanel>
</StackPanel>
<!-- Second row -->
<StackPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="15 0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Default layout file path</TextBlock>
<Button Grid.Column="1"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Copy path to clipboard"
Width="24"
Height="24"
Command="{s:Action CopyToClipboard}"
CommandParameter="{Binding DefaultLayoutPath}">
<materialDesign:PackIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding DefaultLayoutPath}" />
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="0 5" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Image file path</TextBlock>
<Button Grid.Column="1" Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Copy path to clipboard"
Width="24"
Height="24"
Command="{s:Action CopyToClipboard}"
CommandParameter="{Binding Device.Layout.Image.LocalPath}">
<materialDesign:PackIcon Kind="ContentCopy" Width="18" Height="18" />
</Button>
</Grid>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
TextWrapping="Wrap"
Text="{Binding Device.Layout.Image.LocalPath}" />
</StackPanel>
</Grid>
</UserControl>
@@ -1,39 +0,0 @@
using System.Windows;
using Artemis.Core;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class DeviceInfoTabViewModel : Screen
{
private string _defaultLayoutPath;
public DeviceInfoTabViewModel(ArtemisDevice device)
{
Device = device;
DisplayName = "INFO";
}
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
public ArtemisDevice Device { get; }
public string DefaultLayoutPath
{
get => _defaultLayoutPath;
set => SetAndNotify(ref _defaultLayoutPath, value);
}
public void CopyToClipboard(string content)
{
Clipboard.SetText(content);
((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Copied path to clipboard.");
}
protected override void OnInitialActivate()
{
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
base.OnInitialActivate();
}
}
}
@@ -1,44 +0,0 @@
<UserControl
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:tabs="clr-namespace:Artemis.UI.Screens.Settings.Device.Tabs"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DeviceLedsTabView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type tabs:DeviceLedsTabViewModel}}">
<UserControl.Resources>
<converters:UriToFileNameConverter x:Key="UriToFileNameConverter" />
<converters:LedIdToStringConverter x:Key="LedIdToStringConverter" />
</UserControl.Resources>
<Grid>
<DataGrid ItemsSource="{Binding LedViewModels}"
d:DataContext="{d:DesignInstance Type={x:Type tabs:DeviceLedsTabLedViewModel}}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
materialDesign:DataGridAssist.CellPadding="16 5 5 5"
materialDesign:DataGridAssist.ColumnHeaderPadding="16 5 5 5"
CanUserResizeRows="False"
VirtualizingStackPanel.VirtualizationMode="Standard"
Margin="10">
<DataGrid.Resources>
<Style TargetType="DataGridRow" BasedOn="{StaticResource MaterialDesignDataGridRow}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Id, Converter={StaticResource LedIdToStringConverter}, Mode=OneWay}" Header="LED ID" Width="Auto" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Color}" Header="Color (ARGB)" Width="Auto" CanUserSort="False" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.Layout.Image, Converter={StaticResource UriToFileNameConverter}, Mode=OneWay}" Header="Image file" CanUserSort="False" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Shape}" Header="Shape" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.Size}" Header="Size" Width="Auto" CanUserSort="False" />
<materialDesign:DataGridTextColumn Binding="{Binding ArtemisLed.RgbLed.CustomData}" Header="LED data" Width="Auto" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
@@ -1,90 +0,0 @@
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class DeviceLedsTabViewModel : Screen
{
public DeviceLedsTabViewModel(ArtemisDevice device)
{
Device = device;
DisplayName = "LEDS";
LedViewModels = new BindableCollection<DeviceLedsTabLedViewModel>();
}
public ArtemisDevice Device { get; }
public BindableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateSelectedLeds();
}
private void UpdateSelectedLeds()
{
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
deviceLedsTabLedViewModel.Update();
}
#region Overrides of Screen
protected override void OnActivate()
{
BindableCollection<ArtemisLed> selectedLeds = ((DeviceDialogViewModel) Parent).SelectedLeds;
LedViewModels.Clear();
LedViewModels.AddRange(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, selectedLeds)));
selectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
base.OnActivate();
}
protected override void OnDeactivate()
{
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
base.OnDeactivate();
}
#endregion
}
public class DeviceLedsTabLedViewModel : PropertyChangedBase
{
private readonly BindableCollection<ArtemisLed> _selectedLeds;
private bool _isSelected;
public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, BindableCollection<ArtemisLed> selectedLeds)
{
_selectedLeds = selectedLeds;
ArtemisLed = artemisLed;
Update();
}
public ArtemisLed ArtemisLed { get; }
public bool IsSelected
{
get => _isSelected;
set
{
if (!SetAndNotify(ref _isSelected, value)) return;
Apply();
}
}
public void Update()
{
IsSelected = _selectedLeds.Contains(ArtemisLed);
}
public void Apply()
{
if (IsSelected && !_selectedLeds.Contains(ArtemisLed))
_selectedLeds.Add(ArtemisLed);
else if (!IsSelected && _selectedLeds.Contains(ArtemisLed))
_selectedLeds.Remove(ArtemisLed);
}
}
}
@@ -1,270 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Device.Tabs.DevicePropertiesTabView"
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.UI.Screens.Settings.Device.Tabs"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DevicePropertiesTabViewModel}">
<UserControl.Resources>
<shared:SKColorToColorConverter x:Key="SKColorToColorConverter" />
</UserControl.Resources>
<!-- Body -->
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Categories
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Artemis uses categories to determine where the layers of imported profiles are applied to. <LineBreak />
You can hover over a category for a more detailed description.
</TextBlock>
<StackPanel Orientation="Horizontal">
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasDeskCategory}"
ToolTip="A device acting as desk ornamentation such as a LED strip"
Content="Desk" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasMonitorCategory}"
ToolTip="A device attached to the monitor such as ambilight LEDs"
Content="Monitor" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
ToolTip="A device inside your computer case"
IsChecked="{Binding HasCaseCategory}"
Content="Case" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasRoomCategory}"
ToolTip="A device elsewhere in the room"
Content="Room" />
<CheckBox Style="{StaticResource MaterialDesignFilterChipOutlineCheckBox}"
IsChecked="{Binding HasPeripheralsCategory}"
ToolTip="A peripheral such as a mouse or keyboard"
Content="Peripheral" />
</StackPanel>
<Grid Margin="0 25">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="40" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Surface properties
</TextBlock>
<TextBox materialDesign:HintAssist.Hint="X-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding X, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFilledTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Y-coordinate"
materialDesign:TextFieldAssist.SuffixText="mm"
Text="{Binding Y, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFilledTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Scale"
materialDesign:TextFieldAssist.SuffixText="times"
Text="{Binding Scale, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFilledTextBox}"
Margin="0 5" />
<TextBox materialDesign:HintAssist.Hint="Rotation"
materialDesign:TextFieldAssist.SuffixText="deg"
Text="{Binding Rotation, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource MaterialDesignFilledTextBox}"
Margin="0 5 0 12" />
</StackPanel>
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" Fill="{StaticResource MaterialDesignTextBoxBorder}" Width="1" Margin="0 0 0 5" />
<StackPanel Grid.Column="2">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Color calibration
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Use the sliders below to adjust the colors of your device so that it matches your other devices.
</TextBlock>
<Grid Margin="0 3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="R" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding RedScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFilledTextBox}"
Width="70"
Text="{Binding RedScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
materialDesign:HintAssist.IsFloating="False" />
</Grid>
<Grid Margin="0 3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="G" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding GreenScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFilledTextBox}"
Width="70"
Text="{Binding GreenScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
materialDesign:HintAssist.IsFloating="False" />
</Grid>
<Grid Margin="0 2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="B" VerticalAlignment="Center" />
<Slider Grid.Column="1"
Minimum="0"
Maximum="200"
ValueChanged="{s:Action ApplyScaling}"
Value="{Binding BlueScale, UpdateSourceTrigger=PropertyChanged}"
Margin="10"
Ticks="100"
VerticalAlignment="Center" />
<TextBox Grid.Column="2"
VerticalAlignment="Center"
Style="{StaticResource MaterialDesignFilledTextBox}"
Width="70"
Text="{Binding BlueScale, StringFormat={}{0:0.0}, UpdateSourceTrigger=PropertyChanged}"
materialDesign:TextFieldAssist.SuffixText="%"
materialDesign:HintAssist.IsFloating="False" />
</Grid>
<Grid Margin="0 3 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding DisplayOnDevices}"
Content="Show preview"
VerticalAlignment="Center" />
<shared:ColorPicker Grid.Column="1"
HorizontalAlignment="Right"
Color="{Binding CurrentColor, Converter={StaticResource SKColorToColorConverter}}"
TextBoxStyle="{StaticResource MaterialDesignFilledTextBox}"
VerticalAlignment="Center" />
</Grid>
</StackPanel>
</Grid>
<!-- Layout -->
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Layout
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window.
</TextBlock>
<CheckBox Margin="0 0 0 5" Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding Device.DisableDefaultLayout, Delay=300}">
<CheckBox.Content>
<TextBlock Margin="0 -5 0 0">
Don't load default layout
<materialDesign:PackIcon Kind="HelpCircle"
ToolTip="With this enabled Artemis will not load a layout for this device unless you specifically provide one." />
</TextBlock>
</CheckBox.Content>
</CheckBox>
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding Device.CustomLayoutPath}"
VerticalAlignment="Center"
materialDesign:TextFieldAssist.HasClearButton="True"
IsReadOnly="True"
PreviewMouseLeftButtonUp="{s:Action BrowseCustomLayout}">
<materialDesign:HintAssist.Hint>
<StackPanel Orientation="Horizontal" Margin="-2 0 0 0">
<materialDesign:PackIcon Kind="Xml" Width="20" />
<TextBlock>Custom layout path</TextBlock>
</StackPanel>
</materialDesign:HintAssist.Hint>
</TextBox>
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap"
TextAlignment="Justify">
Select a custom layout below if you want to change the appearance and/or LEDs of this device.
For info on how to create layouts, check out
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts">
this wiki article
<b:Interaction.Behaviors>
<shared:OpenInBrowser />
</b:Interaction.Behaviors>
</Hyperlink>
.
</TextBlock>
</StackPanel>
<!-- Buttons -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Style="{StaticResource MaterialDesignOutlinedButton}"
Margin="0 8 8 0"
Command="{s:Action SelectPhysicalLayout}"
ToolTip="Restart device setup, allowing you to select a new physical and logical layout">
RESTART SETUP
</Button>
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Reset}">
RESET
</Button>
<Button Style="{StaticResource MaterialDesignRaisedButton}" IsDefault="True" Margin="0 8 8 0" Command="{s:Action Apply}">
APPLY
</Button>
</StackPanel>
</Grid>
</Grid>
</UserControl>
@@ -1,278 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Input;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Ookii.Dialogs.Wpf;
using RGB.NET.Core;
using SkiaSharp;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class DevicePropertiesTabViewModel : Screen
{
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService;
private float _blueScale;
private SKColor _currentColor;
private bool _displayOnDevices;
private float _greenScale;
private List<DeviceCategory> _categories;
private float _initialBlueScale;
private float _initialGreenScale;
private float _initialRedScale;
private float _redScale;
private int _rotation;
private float _scale;
private int _x;
private int _y;
public DevicePropertiesTabViewModel(ArtemisDevice device,
ICoreService coreService,
IRgbService rgbService,
IDialogService dialogService,
IModelValidator<DevicePropertiesTabViewModel> validator) : base(validator)
{
_coreService = coreService;
_rgbService = rgbService;
_dialogService = dialogService;
Device = device;
DisplayName = "PROPERTIES";
}
public ArtemisDevice Device { get; }
public int X
{
get => _x;
set => SetAndNotify(ref _x, value);
}
public int Y
{
get => _y;
set => SetAndNotify(ref _y, value);
}
public float Scale
{
get => _scale;
set => SetAndNotify(ref _scale, value);
}
public int Rotation
{
get => _rotation;
set => SetAndNotify(ref _rotation, value);
}
public float RedScale
{
get => _redScale;
set => SetAndNotify(ref _redScale, value);
}
public float GreenScale
{
get => _greenScale;
set => SetAndNotify(ref _greenScale, value);
}
public float BlueScale
{
get => _blueScale;
set => SetAndNotify(ref _blueScale, value);
}
public SKColor CurrentColor
{
get => _currentColor;
set => SetAndNotify(ref _currentColor, value);
}
public bool DisplayOnDevices
{
get => _displayOnDevices;
set => SetAndNotify(ref _displayOnDevices, value);
}
// This solution won't scale well but I don't expect there to be many more categories.
// If for some reason there will be, dynamically creating a view model per category may be more appropriate
public bool HasDeskCategory
{
get => GetCategory(DeviceCategory.Desk);
set => SetCategory(DeviceCategory.Desk, value);
}
public bool HasMonitorCategory
{
get => GetCategory(DeviceCategory.Monitor);
set => SetCategory(DeviceCategory.Monitor, value);
}
public bool HasCaseCategory
{
get => GetCategory(DeviceCategory.Case);
set => SetCategory(DeviceCategory.Case, value);
}
public bool HasRoomCategory
{
get => GetCategory(DeviceCategory.Room);
set => SetCategory(DeviceCategory.Room, value);
}
public bool HasPeripheralsCategory
{
get => GetCategory(DeviceCategory.Peripherals);
set => SetCategory(DeviceCategory.Peripherals, value);
}
public void ApplyScaling()
{
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
_rgbService.FlushLeds = true;
}
public void BrowseCustomLayout(object sender, MouseEventArgs e)
{
if (e.OriginalSource is Button)
{
Device.CustomLayoutPath = null;
((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Cleared imported layout.");
return;
}
VistaOpenFileDialog dialog = new();
dialog.Filter = "Layout files (*.xml)|*.xml";
dialog.Title = "Select device layout file";
bool? result = dialog.ShowDialog();
if (result == true)
{
Device.CustomLayoutPath = dialog.FileName;
((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue($"Imported layout from {dialog.FileName}.");
}
}
public async Task SelectPhysicalLayout()
{
await _dialogService.ShowDialogAt<DeviceLayoutDialogViewModel>("DeviceDialog", new Dictionary<string, object> {{"device", Device}});
}
public async Task Apply()
{
await ValidateAsync();
if (HasErrors)
return;
_coreService.ProfileRenderingDisabled = true;
await Task.Delay(100);
Device.X = X;
Device.Y = Y;
Device.Scale = Scale;
Device.Rotation = Rotation;
Device.RedScale = RedScale / 100f;
Device.GreenScale = GreenScale / 100f;
Device.BlueScale = BlueScale / 100f;
Device.Categories.Clear();
foreach (DeviceCategory deviceCategory in _categories)
Device.Categories.Add(deviceCategory);
_rgbService.SaveDevice(Device);
_coreService.ProfileRenderingDisabled = false;
}
public void Reset()
{
HasDeskCategory = Device.Categories.Contains(DeviceCategory.Desk);
HasMonitorCategory = Device.Categories.Contains(DeviceCategory.Monitor);
HasCaseCategory = Device.Categories.Contains(DeviceCategory.Case);
HasRoomCategory = Device.Categories.Contains(DeviceCategory.Room);
HasPeripheralsCategory = Device.Categories.Contains(DeviceCategory.Peripherals);
Device.RedScale = _initialRedScale;
Device.GreenScale = _initialGreenScale;
Device.BlueScale = _initialBlueScale;
}
protected override void OnActivate()
{
X = (int) Device.X;
Y = (int) Device.Y;
Scale = Device.Scale;
Rotation = (int) Device.Rotation;
RedScale = Device.RedScale * 100f;
GreenScale = Device.GreenScale * 100f;
BlueScale = Device.BlueScale * 100f;
//we need to store the initial values to be able to restore them when the user clicks "Cancel"
_initialRedScale = Device.RedScale;
_initialGreenScale = Device.GreenScale;
_initialBlueScale = Device.BlueScale;
_categories = new List<DeviceCategory>(Device.Categories);
CurrentColor = SKColors.White;
_coreService.FrameRendering += OnFrameRendering;
Device.PropertyChanged += DeviceOnPropertyChanged;
base.OnActivate();
}
protected override void OnDeactivate()
{
_coreService.FrameRendering -= OnFrameRendering;
Device.PropertyChanged -= DeviceOnPropertyChanged;
base.OnDeactivate();
}
private bool GetCategory(DeviceCategory category)
{
return _categories.Contains(category);
}
private void SetCategory(DeviceCategory category, bool value)
{
if (value && !_categories.Contains(category))
_categories.Add(category);
else
_categories.Remove(category);
NotifyOfPropertyChange($"Has{category}Category");
}
#region Event handlers
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
{
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
}
}
private void OnFrameRendering(object sender, FrameRenderingEventArgs e)
{
if (!_displayOnDevices)
return;
using SKPaint overlayPaint = new()
{
Color = CurrentColor
};
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
}
#endregion
}
}
@@ -1,83 +0,0 @@
<UserControl
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.UI.Screens.Settings.Device.Tabs"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:Shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" x:Class="Artemis.UI.Screens.Settings.Device.Tabs.InputMappingsTabView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:InputMappingsTabViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<Shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
<Style x:Key="CenteredTextColumn" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
<Run Text="Introduction" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" TextWrapping="Wrap">
In some cases you may want Artemis to map key presses to different LEDs.
This is useful when your logical layout swaps keys around (like Hungarian layouts where the Z and Y keys are swapped).<LineBreak /><LineBreak />
In this tab you can set up these custom input mappings, simply click on a LED and press a key, Artemis will from then on consider that LED pressed whenever you press the same key again.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Margin="0 20" Visibility="{Binding SelectedLed, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextAlignment="Center">Select a LED in the preview on the left side to get started...</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Margin="0 20" Visibility="{Binding SelectedLed, Converter={StaticResource NullToVisibilityConverter}}">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextAlignment="Center">
<Run Text="Current target LED: " /><Run Text="{Binding SelectedLed.RgbLed.Id, Mode=OneWay}" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" TextAlignment="Center">
<Run Text="Press the key you want to remap" /><Run Text=" " /><Run FontWeight="Bold" Text="{Binding SelectedLed.RgbLed.Id, Mode=OneWay}" /><Run Text=" " /><Run Text="to..." />
</TextBlock>
</StackPanel>
<DataGrid Grid.Row="2"
ItemsSource="{Binding InputMappings}"
CanUserSortColumns="True"
IsReadOnly="True"
CanUserAddRows="False"
AutoGenerateColumns="False"
CanUserResizeRows="False"
materialDesign:DataGridAssist.CellPadding="16 5 5 5"
materialDesign:DataGridAssist.ColumnHeaderPadding="16 5 5 5"
VirtualizingPanel.VirtualizationMode="Standard"
Margin="10">
<DataGrid.Columns>
<materialDesign:DataGridTextColumn Binding="{Binding Item1.RgbLed.Id}" ElementStyle="{StaticResource CenteredTextColumn}" Header="Original LED ID" Width="*" />
<materialDesign:DataGridTextColumn Binding="{Binding Item2.RgbLed.Id}" ElementStyle="{StaticResource CenteredTextColumn}" Header="Remapped LED ID" Width="*" />
<DataGridTemplateColumn Width="45" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{s:Action DeleteMapping}"
CommandParameter="{Binding}"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Delete mapping"
Width="25"
Height="25">
<materialDesign:PackIcon Kind="Delete" Width="18" Height="18" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
@@ -1,103 +0,0 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Exceptions;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class InputMappingsTabViewModel : Screen
{
private readonly IRgbService _rgbService;
private readonly IInputService _inputService;
private ArtemisLed _selectedLed;
public InputMappingsTabViewModel(ArtemisDevice device, IRgbService rgbService, IInputService inputService)
{
if (device.DeviceType != RGBDeviceType.Keyboard)
throw new ArtemisUIException("The input mappings tab only supports keyboards");
_rgbService = rgbService;
_inputService = inputService;
Device = device;
DisplayName = "INPUT MAPPINGS";
InputMappings = new BindableCollection<Tuple<ArtemisLed, ArtemisLed>>();
}
public ArtemisDevice Device { get; }
public ArtemisLed SelectedLed
{
get => _selectedLed;
set => SetAndNotify(ref _selectedLed, value);
}
public BindableCollection<Tuple<ArtemisLed, ArtemisLed>> InputMappings { get; }
public void DeleteMapping(Tuple<ArtemisLed, ArtemisLed> inputMapping)
{
Device.InputMappings.Remove(inputMapping.Item1);
UpdateInputMappings();
}
private void InputServiceOnKeyboardKeyUp(object sender, ArtemisKeyboardKeyEventArgs e)
{
if (SelectedLed == null || e.Led == null)
return;
// Locate the original LED the same way the InputService did it, but supply false to Device.GetLed
bool foundLedId = InputKeyUtilities.KeyboardKeyLedIdMap.TryGetValue(e.Key, out LedId ledId);
if (!foundLedId)
return;
ArtemisLed artemisLed = Device.GetLed(ledId, false);
if (artemisLed == null)
return;
// Apply the new LED mapping
Device.InputMappings[SelectedLed] = artemisLed;
_rgbService.SaveDevice(Device);
((DeviceDialogViewModel) Parent).SelectedLeds.Clear();
UpdateInputMappings();
}
private void UpdateInputMappings()
{
if (InputMappings.Any())
InputMappings.Clear();
InputMappings.AddRange(Device.InputMappings.Select(m => new Tuple<ArtemisLed, ArtemisLed>(m.Key, m.Value)));
}
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
SelectedLed = ((DeviceDialogViewModel) Parent).SelectedLeds.FirstOrDefault();
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
UpdateInputMappings();
_inputService.KeyboardKeyUp += InputServiceOnKeyboardKeyUp;
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged += SelectedLedsOnCollectionChanged;
base.OnActivate();
}
/// <inheritdoc />
protected override void OnDeactivate()
{
InputMappings.Clear();
_inputService.KeyboardKeyUp -= InputServiceOnKeyboardKeyUp;
((DeviceDialogViewModel) Parent).SelectedLeds.CollectionChanged -= SelectedLedsOnCollectionChanged;
base.OnDeactivate();
}
#endregion
}
}
@@ -1,19 +0,0 @@
using FluentValidation;
namespace Artemis.UI.Screens.Settings.Device.Tabs
{
public class DevicePropertiesTabViewModelValidator : AbstractValidator<DevicePropertiesTabViewModel>
{
public DevicePropertiesTabViewModelValidator()
{
RuleFor(m => m.X).GreaterThanOrEqualTo(0).WithMessage("X-coordinate must be 0 or greater");
RuleFor(m => m.Y).GreaterThanOrEqualTo(0).WithMessage("Y-coordinate must be 0 or greater");
RuleFor(m => m.Scale).GreaterThanOrEqualTo(0.2f).WithMessage("Scale must be 0.2 or greater");
RuleFor(m => m.Rotation).GreaterThanOrEqualTo(0).WithMessage("Rotation must be 0 or greater");
RuleFor(m => m.Rotation).LessThanOrEqualTo(359).WithMessage("Rotation must be 359 or less");
}
}
}
@@ -1,67 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Dialogs.UpdateDialogView"
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:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="16">
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
Update available
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
TextWrapping="Wrap">
A new Artemis update is available! 🥳<LineBreak />
You are currently running build <Run Text="{Binding CurrentBuild, Mode=OneWay}" /> while the latest build is <Run Text="{Binding LatestBuild, Mode=OneWay}" />. <LineBreak /> <LineBreak />
Updating Artemis will give you the latest bug(fixes), features and improvements.
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 20">
Changes
</TextBlock>
<StackPanel Visibility="{Binding RetrievingChanges, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
HorizontalAlignment="Center"
Margin="0 10">
Retrieving changes...
</TextBlock>
<ProgressBar IsIndeterminate="True" />
</StackPanel>
<Grid Visibility="{Binding RetrievingChanges, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
<ScrollViewer MaxHeight="400" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Changes}" Visibility="{Binding HasChanges, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Foreground="{DynamicResource MaterialDesignBodyLight}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<TextBlock Visibility="{Binding HasChanges, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}">
Couldn't retrieve changes, sorry :(
</TextBlock>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
<Button Style="{StaticResource MaterialDesignFlatButton}"
Focusable="False"
IsCancel="True"
Command="{s:Action Cancel}"
Content="LATER" />
<Button x:Name="ConfirmButton"
Style="{StaticResource MaterialDesignFlatButton}"
IsDefault="True"
Focusable="True"
Command="{s:Action Update}"
Content="UPDATE" />
</StackPanel>
</StackPanel>
</UserControl>
@@ -1,128 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Services;
using Artemis.UI.Services.Models.UpdateService;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings.Dialogs
{
public class UpdateDialogViewModel : DialogViewModelBase
{
private readonly IDialogService _dialogService;
// Based on https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#skipping-ci-for-individual-commits
private readonly string[] _excludedCommitMessages =
{
"[skip ci]",
"[ci skip]",
"skip-checks: true",
"skip-checks:true",
"[skip azurepipelines]",
"[azurepipelines skip]",
"[skip azpipelines]",
"[azpipelines skip]",
"[skip azp]",
"[azp skip]",
"***NO_CI***"
};
private readonly IMessageService _messageService;
private readonly IUpdateService _updateService;
private bool _canUpdate = true;
private bool _hasChanges;
private bool _retrievingChanges;
public UpdateDialogViewModel(DevOpsBuild buildInfo, IUpdateService updateService, IDialogService dialogService, IMessageService messageService)
{
_updateService = updateService;
_dialogService = dialogService;
_messageService = messageService;
CurrentBuild = Constants.BuildInfo.BuildNumberDisplay;
LatestBuild = buildInfo.BuildNumber;
Changes = new BindableCollection<string>();
Task.Run(GetBuildChanges);
}
public string CurrentBuild { get; }
public string LatestBuild { get; }
public BindableCollection<string> Changes { get; }
public bool RetrievingChanges
{
get => _retrievingChanges;
set => SetAndNotify(ref _retrievingChanges, value);
}
public bool HasChanges
{
get => _hasChanges;
set => SetAndNotify(ref _hasChanges, value);
}
public bool CanUpdate
{
get => _canUpdate;
set => SetAndNotify(ref _canUpdate, value);
}
public async Task Update()
{
try
{
CanUpdate = false;
await _updateService.ApplyUpdate();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("An exception occurred while applying the update", e);
}
finally
{
CanUpdate = true;
}
Session.Close(true);
}
private async Task GetBuildChanges()
{
try
{
RetrievingChanges = true;
Task<DevOpsBuild> currentTask = _updateService.GetBuildInfo(1, CurrentBuild);
Task<DevOpsBuild> latestTask = _updateService.GetBuildInfo(1, LatestBuild);
DevOpsBuild current = await currentTask;
DevOpsBuild latest = await latestTask;
if (current != null && latest != null)
{
GitHubDifference difference = await _updateService.GetBuildDifferences(current, latest);
// Only take commits with one parents (no merges)
Changes.Clear();
Changes.AddRange(difference.Commits.Where(c => c.Parents.Count == 1)
.SelectMany(c => c.Commit.Message.Split("\n"))
.Select(m => m.Trim())
.Where(m => !string.IsNullOrWhiteSpace(m) && !_excludedCommitMessages.Contains(m))
.OrderBy(m => m)
);
HasChanges = Changes.Any();
}
}
catch (Exception e)
{
_messageService.ShowMessage($"Failed to retrieve build changes - {e.Message}");
}
finally
{
RetrievingChanges = false;
}
}
}
}
@@ -1,47 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.SettingsTabsView"
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.UI.Screens.Settings"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:SettingsTabsViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<TabControl Style="{StaticResource MaterialDesignAppBarTabControl}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding ActiveItem}"
DisplayMemberPath="DisplayName">
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}"
VerticalContentAlignment="Stretch"
HorizontalContentAlignment="Stretch"
IsTabStop="False"
TextElement.Foreground="{DynamicResource MaterialDesignBody}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<!-- Bug: materialDesign:RippleAssist.RippleOnTop doesn't look as nice but otherwise it doesn't work at all, not sure why -->
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
VerticalAlignment="Top"
HorizontalAlignment="Right"
ToolTip="Open debugger"
Command="{s:Action ShowDebugger}"
materialDesign:RippleAssist.RippleOnTop="True">
<materialDesign:PackIcon Kind="Matrix" />
</Button>
</Grid>
</UserControl>
@@ -1,37 +0,0 @@
using Artemis.UI.Screens.Settings.Tabs.About;
using Artemis.UI.Screens.Settings.Tabs.Devices;
using Artemis.UI.Screens.Settings.Tabs.General;
using Artemis.UI.Screens.Settings.Tabs.Plugins;
using Artemis.UI.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings
{
public class SettingsTabsViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly IDebugService _debugService;
public SettingsTabsViewModel(
GeneralSettingsTabViewModel generalSettingsTabViewModel,
PluginSettingsTabViewModel pluginSettingsTabViewModel,
DeviceSettingsTabViewModel deviceSettingsTabViewModel,
AboutTabViewModel aboutTabViewModel,
IDebugService debugService)
{
_debugService = debugService;
DisplayName = "Settings";
Items.Add(generalSettingsTabViewModel);
Items.Add(pluginSettingsTabViewModel);
Items.Add(deviceSettingsTabViewModel);
Items.Add(aboutTabViewModel);
ActiveItem = generalSettingsTabViewModel;
}
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
}
}
@@ -0,0 +1,16 @@
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Settings.SettingsView">
<Border Classes="router-container">
<TabControl Margin="12" Items="{Binding SettingTabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
</Border>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class SettingsView : ReactiveUserControl<SettingsViewModel>
{
public SettingsView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -1,12 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance settings:SettingsViewModel}"
d:DesignHeight="600" d:DesignWidth="600">
<ContentControl s:View.Model="{Binding ActiveItem}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</UserControl>
@@ -1,17 +1,26 @@
using Stylet;
using System.Collections.ObjectModel;
using Artemis.UI.Shared;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class SettingsViewModel : MainScreenViewModel
{
public SettingsViewModel(SettingsTabsViewModel settingsTabsViewModel)
public SettingsViewModel(IScreen hostScreen,
GeneralTabViewModel generalTabViewModel,
PluginsTabViewModel pluginsTabViewModel,
DevicesTabViewModel devicesTabViewModel,
AboutTabViewModel aboutTabViewModel) : base(hostScreen, "settings")
{
DisplayName = "Settings";
settingsTabsViewModel.ConductWith(this);
ActiveItem = settingsTabsViewModel;
SettingTabs = new ObservableCollection<ActivatableViewModelBase>
{
generalTabViewModel,
pluginsTabViewModel,
devicesTabViewModel,
aboutTabViewModel
};
}
public SettingsTabsViewModel ActiveItem { get; }
public ObservableCollection<ActivatableViewModelBase> SettingTabs { get; }
}
}
@@ -1,436 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.About.AboutTabView"
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.UI.Screens.Settings.Tabs.About"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:AboutTabViewModel}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="15" MaxWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{svgc:SvgImage Source=/Resources/Images/Logo/bow.svg}" Width="60" Height="80" Margin="0 0 20 0" />
<TextBlock Grid.Row="0" Grid.Column="1" Style="{StaticResource MaterialDesignHeadline2TextBlock}">
Artemis 2
</TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" Orientation="Horizontal">
<Button
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View website"
Command="{s:Action OpenUrl}"
CommandParameter="https://artemis-rgb.com">
<materialDesign:PackIcon Kind="Web" />
</Button>
<Button
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View GitHub repository"
Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/Artemis-RGB/Artemis">
<materialDesign:PackIcon Kind="Github" />
</Button>
<Button
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View Wiki"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com"
Margin="0 0 -10 0">
<materialDesign:PackIcon Kind="BookOpenOutline" />
</Button>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
Foreground="{DynamicResource MaterialDesignBodyLight}"
Text="{Binding Version}" />
<TextBlock Grid.Row="1"
Grid.Column="2"
Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
<Hyperlink Style="{StaticResource ArtemisHyperlink}" RequestNavigate="{s:Action OpenHyperlink}" NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0
</Hyperlink>
</TextBlock>
</Grid>
<materialDesign:Card Margin="0 25 0 0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
RenderOptions.BitmapScalingMode="HighQuality">
<Ellipse.Fill>
<ImageBrush ImageSource="https://avatars.githubusercontent.com/u/8858506" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Padding="0">
Robert Beekman
</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Padding="0">
Project owner, main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-10">
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View GitHub profile"
Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/RobertWasTaken/">
<materialDesign:PackIcon Kind="Github" Width="20" Height="20" />
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
RenderOptions.BitmapScalingMode="HighQuality">
<Ellipse.Fill>
<ImageBrush ImageSource="https://avatars.githubusercontent.com/u/1094841" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Padding="0">
Darth Affe
</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Padding="0">
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/DarthAffe/RGB.NET">
RGB.NET
</Hyperlink>
developer, main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-10">
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View GitHub profile"
Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/DarthAffe/">
<materialDesign:PackIcon Kind="Github" Width="20" Height="20" />
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
RenderOptions.BitmapScalingMode="HighQuality">
<Ellipse.Fill>
<ImageBrush ImageSource="https://avatars.githubusercontent.com/u/29486064" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Padding="0">
Diogo 'DrMeteor' Trindade
</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Padding="0">
Main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-10">
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View GitHub profile"
Command="{s:Action OpenUrl}"
CommandParameter="https://github.com/diogotr7/">
<materialDesign:PackIcon Kind="Github" Width="20" Height="20" />
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
RenderOptions.BitmapScalingMode="HighQuality">
<Ellipse.Fill>
<ImageBrush ImageSource="https://i.imgur.com/8mPWY1j.png" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0"
Grid.Column="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Padding="0">
Kai Werling
</TextBlock>
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Padding="0">
Graphics design
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-10">
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="View website"
Command="{s:Action OpenUrl}"
CommandParameter="https://kwer.online/">
<materialDesign:PackIcon Kind="Web" Width="20" Height="20" />
</Button>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="AccountHeart" Width="30" Height="30" />
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="5 3 0 0">
Special thanks
</TextBlock>
</StackPanel>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" LineHeight="25" Margin="10 0 0 0">
- The various people creating PRs to Artemis.Plugins and the main repository<LineBreak />
- All the people on Discord providing feedback and testing
</TextBlock>
<StackPanel Orientation="Horizontal" Margin="0 20 0 0">
<materialDesign:PackIcon Kind="LibraryShelves" Width="30" Height="30" />
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" Margin="5 3 0 0">
External libraries
</TextBlock>
</StackPanel>
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" LineHeight="25" Margin="10 0 0 0">
- EmbedIO
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://unosquare.github.io/embedio/">
https://unosquare.github.io/embedio/
</Hyperlink>
<LineBreak />
- FluentValidation
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://fluentvalidation.net/">
https://fluentvalidation.net/
</Hyperlink>
<LineBreak />
- Furl.Http
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://flurl.dev//">
https://flurl.dev/
</Hyperlink>
<LineBreak />
- gong-wpf-dragdrop
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/punker76/gong-wpf-dragdrop">
https://github.com/punker76/gong-wpf-dragdrop
</Hyperlink>
<LineBreak />
- Hardcodet.NotifyIcon.Wpf.NetCore
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/HavenDV/H.NotifyIcon.WPF">
https://github.com/HavenDV/H.NotifyIcon.WPF
</Hyperlink>
<LineBreak />
- Humanizer
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/Humanizr/Humanizer">
https://github.com/Humanizr/Humanizer
</Hyperlink>
<LineBreak />
- LiteDB
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://www.litedb.org/">
https://www.litedb.org/
</Hyperlink>
<LineBreak />
- MaterialDesignThemes
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit">
https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit
</Hyperlink>
<LineBreak />
- MaterialDesignExtensions
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://spiegelp.github.io/MaterialDesignExtensions/">
https://spiegelp.github.io/MaterialDesignExtensions/
</Hyperlink>
<LineBreak />
- McMaster.NETCore.Plugins
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins">
https://github.com/natemcmaster/DotNetCorePlugins
</Hyperlink>
<LineBreak />
- Newtonsoft.Json
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://www.newtonsoft.com/json">
https://www.newtonsoft.com/json
</Hyperlink>
<LineBreak />
- Ninject
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="http://www.ninject.org/">
http://www.ninject.org/
</Hyperlink>
<LineBreak />
- NoStringEvaluating
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/KovtunV/NoStringEvaluating">
https://github.com/KovtunV/NoStringEvaluating
</Hyperlink>
<LineBreak />
- Ookii.Dialogs.Wpf
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/ookii-dialogs/ookii-dialogs-wpf">
https://github.com/ookii-dialogs/ookii-dialogs-wpf
</Hyperlink>
<LineBreak />
- RawInput.Sharp
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/mfakane/rawinput-sharp">
https://github.com/mfakane/rawinput-sharp
</Hyperlink>
<LineBreak />
- RGB.NET
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/DarthAffe/RGB.NET">
https://github.com/DarthAffe/RGB.NET
</Hyperlink>
<LineBreak />
- Serilog
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://serilog.net/">
https://serilog.net/
</Hyperlink>
<LineBreak />
- SkiaSharp
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/mono/SkiaSharp/">
https://github.com/mono/SkiaSharp
</Hyperlink>
<LineBreak />
- Stylet
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://github.com/canton7/Stylet">
https://github.com/canton7/Stylet
</Hyperlink>
<LineBreak />
- Unclassified.NetRevisionTask
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
RequestNavigate="{s:Action OpenHyperlink}"
NavigateUri="https://unclassified.software/en/apps/netrevisiontask">
https://unclassified.software/en/apps/netrevisiontask
</Hyperlink>
</TextBlock>
</StackPanel>
</materialDesign:Card>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -1,46 +0,0 @@
using System.Reflection;
using System.Windows.Navigation;
using Artemis.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.About
{
public class AboutTabViewModel : Screen
{
private string _version;
public AboutTabViewModel()
{
DisplayName = "ABOUT";
}
public string Version
{
get => _version;
set => SetAndNotify(ref _version, value);
}
public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
{
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
}
public void OpenUrl(string url)
{
Core.Utilities.OpenUrl(url);
}
#region Overrides of Screen
/// <inheritdoc />
protected override void OnActivate()
{
AssemblyInformationalVersionAttribute versionAttribute = typeof(RootViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Version = $"Version {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
base.OnActivate();
}
#endregion
}
}
@@ -0,0 +1,270 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="1400"
x:Class="Artemis.UI.Screens.Settings.AboutTabView">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="15" MaxWidth="800">
<Grid RowDefinitions="*,*" ColumnDefinitions="Auto,*,Auto">
<Image Grid.Column="0" Grid.RowSpan="2" Width="140" Height="140" VerticalAlignment="Center">
<Image.Source>
<svg:SvgImage Source="/Assets/Images/Logo/bow.svg" />
</Image.Source>
</Image>
<TextBlock Grid.Row="0" Grid.Column="1" FontSize="36" VerticalAlignment="Bottom">
Artemis 2
</TextBlock>
<StackPanel Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Orientation="Horizontal">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://artemis-rgb.com">
<avalonia:MaterialIcon Kind="Web" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub repository" NavigateUri="https://github.com/Artemis-RGB/Artemis">
<avalonia:MaterialIcon Kind="Github" />
</controls:HyperlinkButton>
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View Wiki" NavigateUri="https://wiki.artemis-rgb.com">
<avalonia:MaterialIcon Kind="BookOpenOutline" />
</controls:HyperlinkButton>
</StackPanel>
<TextBlock Grid.Row="1"
Grid.Column="1"
VerticalAlignment="Top"
Foreground="{DynamicResource MaterialDesignBodyLight}"
Text="{Binding Version}" />
<controls:HyperlinkButton Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Top"
NavigateUri="https://github.com/Artemis-RGB/Artemis/blob/master/LICENSE">
PolyForm Noncommercial License 1.0.0
</controls:HyperlinkButton>
</Grid>
<Border Classes="card" Margin="0 20 0 10">
<StackPanel>
<Grid RowDefinitions="*,*,*" ColumnDefinitions="Auto,*">
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
IsVisible="{Binding RobertProfileImage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse.Fill>
<ImageBrush Source="{Binding RobertProfileImage}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0">
Robert Beekman
</TextBlock>
<TextBlock Classes="subtitle" Grid.Column="1" Grid.Row="1" Padding="0">
Project owner, main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/RobertBeekman/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
IsVisible="{Binding DarthAffeProfileImage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse.Fill>
<ImageBrush Source="{Binding DarthAffeProfileImage}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0">
Darth Affe
</TextBlock>
<TextBlock Classes="subtitle" Grid.Column="1" Grid.Row="1" Padding="0">
RGB.NET, main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/DarthAffe/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*,*" ColumnDefinitions="Auto,*">
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
IsVisible="{Binding DrMeteorProfileImage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse.Fill>
<ImageBrush Source="{Binding DrMeteorProfileImage}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0">
Diogo 'DrMeteor' Trindade
</TextBlock>
<TextBlock Classes="subtitle" Grid.Column="1" Grid.Row="1" Padding="0">
Main contributor
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View GitHub profile" NavigateUri="https://github.com/diogotr7/">
<avalonia:MaterialIcon Kind="Github" Width="20" Height="20" />
</controls:HyperlinkButton>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*,*" ColumnDefinitions="Auto,*">
<Ellipse Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
VerticalAlignment="Top"
Height="75"
Width="75"
Margin="0 0 15 0"
IsVisible="{Binding KaiProfileImage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<Ellipse.Fill>
<ImageBrush Source="{Binding KaiProfileImage}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Row="0" Grid.Column="1" Padding="0">
Kai Werling
</TextBlock>
<TextBlock Classes="subtitle" Grid.Column="1" Grid.Row="1" Padding="0">
Graphics design
</TextBlock>
<StackPanel Grid.Column="1" Grid.Row="2" Orientation="Horizontal" Margin="-6">
<controls:HyperlinkButton Classes="icon-button" ToolTip.Tip="View website" NavigateUri="https://kwer.online/">
<avalonia:MaterialIcon Kind="Web" Width="20" Height="20" />
</controls:HyperlinkButton>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<Border Classes="card" Margin="0 10">
<StackPanel>
<StackPanel Margin="0 0 0 12" Orientation="Horizontal">
<avalonia:MaterialIcon Kind="UserHeart" Height="35" Width="35" Margin="0 0 10 0" />
<TextBlock Classes="h4">
Special Thanks
</TextBlock>
</StackPanel>
<TextBlock>
- The various people creating PRs to Artemis.Plugins and the main repository
</TextBlock>
<TextBlock>
- All the people on Discord providing feedback and testing
</TextBlock>
</StackPanel>
</Border>
<Border Classes="card" Margin="0 10">
<StackPanel>
<StackPanel Margin="0 0 0 12" Orientation="Horizontal">
<avalonia:MaterialIcon Kind="Dependency" Height="35" Width="35" Margin="0 0 10 0" />
<TextBlock Classes="h4">
External Libraries
</TextBlock>
</StackPanel>
<Grid ColumnDefinitions="Auto,*">
<Grid.Styles>
<Style Selector="TextBlock.library-name">
<Setter Property="Margin" Value="0 7 15 6"/>
<Setter Property="FontWeight" Value="600"/>
</Style>
</Grid.Styles>
<StackPanel Grid.Row="0" Grid.Column="0">
<TextBlock Classes="library-name">Avalonia</TextBlock>
<TextBlock Classes="library-name">FluentAvalonia</TextBlock>
<TextBlock Classes="library-name">EmbedIO</TextBlock>
<TextBlock Classes="library-name">Furl.Http</TextBlock>
<TextBlock Classes="library-name">Humanizer</TextBlock>
<TextBlock Classes="library-name">LiteDB</TextBlock>
<TextBlock Classes="library-name">McMaster.NETCore.Plugins</TextBlock>
<TextBlock Classes="library-name">Newtonsoft.Json</TextBlock>
<TextBlock Classes="library-name">Ninject</TextBlock>
<TextBlock Classes="library-name">RGB.NET</TextBlock>
<TextBlock Classes="library-name">Serilog</TextBlock>
<TextBlock Classes="library-name">SkiaSharp</TextBlock>
<TextBlock Classes="library-name">Unclassified.NetRevisionTask</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<controls:HyperlinkButton NavigateUri="https://avaloniaui.net/">
https://avaloniaui.net/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/amwx/FluentAvalonia">
https://github.com/amwx/FluentAvalonia
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://unosquare.github.io/embedio/">
https://unosquare.github.io/embedio/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://flurl.dev/">
https://flurl.dev/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/Humanizr/Humanizer">
https://github.com/Humanizr/Humanizer
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://www.litedb.org/">
https://www.litedb.org/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/natemcmaster/DotNetCorePlugins">
https://github.com/natemcmaster/DotNetCorePlugins
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://www.newtonsoft.com/json">
https://www.newtonsoft.com/json
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="http://www.ninject.org/">
http://www.ninject.org/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/DarthAffe/RGB.NET">
https://github.com/DarthAffe/RGB.NET
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://serilog.net/">
https://serilog.net/
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://github.com/mono/SkiaSharp">
https://github.com/mono/SkiaSharp
</controls:HyperlinkButton>
<controls:HyperlinkButton NavigateUri="https://unclassified.software/en/apps/netrevisiontask">
https://unclassified.software/en/apps/netrevisiontask
</controls:HyperlinkButton>
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class AboutTabView : ReactiveUserControl<AboutTabViewModel>
{
public AboutTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,76 @@
using System;
using System.Reactive.Disposables;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Shared;
using Avalonia.Media.Imaging;
using Flurl.Http;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class AboutTabViewModel : ActivatableViewModelBase
{
private Bitmap? _darthAffeProfileImage;
private Bitmap? _drMeteorProfileImage;
private Bitmap? _kaiProfileImage;
private Bitmap? _robertProfileImage;
private string? _version;
public AboutTabViewModel()
{
DisplayName = "About";
this.WhenActivated((CompositeDisposable _) => Task.Run(Activate));
}
public string? Version
{
get => _version;
set => RaiseAndSetIfChanged(ref _version, value);
}
public Bitmap? RobertProfileImage
{
get => _robertProfileImage;
set => RaiseAndSetIfChanged(ref _robertProfileImage, value);
}
public Bitmap? DarthAffeProfileImage
{
get => _darthAffeProfileImage;
set => RaiseAndSetIfChanged(ref _darthAffeProfileImage, value);
}
public Bitmap? DrMeteorProfileImage
{
get => _drMeteorProfileImage;
set => RaiseAndSetIfChanged(ref _drMeteorProfileImage, value);
}
public Bitmap? KaiProfileImage
{
get => _kaiProfileImage;
set => RaiseAndSetIfChanged(ref _kaiProfileImage, value);
}
private async Task Activate()
{
AssemblyInformationalVersionAttribute? versionAttribute = typeof(AboutTabViewModel).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
Version = $"Version {versionAttribute?.InformationalVersion} build {Constants.BuildInfo.BuildNumberDisplay}";
try
{
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
RobertProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/8858506".GetStreamAsync());
DarthAffeProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/1094841".GetStreamAsync());
DrMeteorProfileImage = new Bitmap(await "https://avatars.githubusercontent.com/u/29486064".GetStreamAsync());
KaiProfileImage = new Bitmap(await "https://i.imgur.com/8mPWY1j.png".GetStreamAsync());
}
catch (Exception)
{
// ignored, unluckyyyy
}
}
}
}
@@ -1,35 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Devices.DeviceSettingsTabView"
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.UI.Screens.Settings.Tabs.Devices"
xmlns:s="https://github.com/canton7/Stylet"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:DeviceSettingsTabViewModel}">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="0 12 0 0">
<DockPanel Margin="15" MaxWidth="1230" HorizontalAlignment="Center">
<StackPanel DockPanel.Dock="Top">
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}">Device management</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Below you view and manage the devices that were detected by Artemis. <LineBreak/>
Disabling a device will cause it to stop updating. Some SDKs will even go back to using manufacturer lighting (Artemis restart may be required).
</TextBlock>
</StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}" Margin="5" HorizontalAlignment="Left" VerticalAlignment="Top" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DockPanel>
</ScrollViewer>
</UserControl>
@@ -1,88 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Devices
{
public class DeviceSettingsTabViewModel : Conductor<DeviceSettingsViewModel>.Collection.AllActive
{
private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService;
private readonly ISettingsVmFactory _settingsVmFactory;
private bool _confirmedDisable;
public DeviceSettingsTabViewModel(IRgbService rgbService, IDialogService dialogService, ISettingsVmFactory settingsVmFactory)
{
DisplayName = "DEVICES";
_rgbService = rgbService;
_dialogService = dialogService;
_settingsVmFactory = settingsVmFactory;
}
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)
return true;
bool confirmed = await _dialogService.ShowConfirmDialog(
"Disabling device",
"Disabling a device will cause it to stop updating. " +
"\r\nSome SDKs will even go back to using manufacturer lighting (Artemis restart may be required)."
);
if (confirmed)
_confirmedDisable = true;
return confirmed;
}
private void RgbServiceOnDeviceRemoved(object sender, DeviceEventArgs e)
{
DeviceSettingsViewModel viewModel = Items.FirstOrDefault(i => i.Device == e.Device);
if (viewModel != null)
Items.Remove(viewModel);
}
private void RgbServiceOnDeviceAdded(object sender, DeviceEventArgs e)
{
Items.Add(_settingsVmFactory.CreateDeviceSettingsViewModel(e.Device));
}
#region Overrides of AllActive
/// <inheritdoc />
protected override void OnActivate()
{
_rgbService.DeviceAdded += RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved;
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
if (Items.Any())
Items.Clear();
await Task.Delay(200);
List<DeviceSettingsViewModel> instances = _rgbService.Devices.Select(d => _settingsVmFactory.CreateDeviceSettingsViewModel(d)).ToList();
foreach (DeviceSettingsViewModel deviceSettingsViewModel in instances)
Items.Add(deviceSettingsViewModel);
});
base.OnActivate();
}
/// <inheritdoc />
protected override void OnClose()
{
_rgbService.DeviceAdded -= RgbServiceOnDeviceAdded;
_rgbService.DeviceRemoved -= RgbServiceOnDeviceRemoved;
base.OnClose();
}
#endregion
}
}
@@ -1,86 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Devices.DeviceSettingsView"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Devices"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
d:DataContext="{d:DesignInstance devices:DeviceSettingsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Button.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.TextBlock.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<materialDesign:Card Width="200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="140" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="{DynamicResource MaterialDesignPaper}" />
<shared:DeviceVisualizer VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10"
Device="{Binding Device}"
RenderOptions.BitmapScalingMode="HighQuality"
Grid.Row="0" />
<Button Grid.Row="0"
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="0 0 16 -20"
ToolTip="Identify"
Command="{s:Action IdentifyDevice}">
<materialDesign:PackIcon Kind="AlarmLight" />
</Button>
<StackPanel Grid.Row="1" Margin="8 24 8 0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
<TextBlock TextWrapping="Wrap" Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
<Run Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer, Mode=OneWay}" />
-
<Run Text="{Binding Device.DeviceType, Mode=OneWay}" />
</TextBlock>
</StackPanel>
<StackPanel HorizontalAlignment="Right" Grid.Row="2" Orientation="Horizontal" Margin="8">
<CheckBox Style="{StaticResource MaterialDesignAccentCheckBox}" IsChecked="{Binding IsDeviceEnabled}">
Device enabled
</CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}" Padding="2 0 2 0" Foreground="{StaticResource MaterialDesignBody}">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action DetectInput}" ToolTip="Teach Artemis which keypresses and/or button presses belong to this device">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="GestureDoubleTap" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Identify input</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action ViewProperties}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Gear" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Device properties</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</Grid>
</materialDesign:Card>
</UserControl>
@@ -1,107 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.SurfaceEditor.Dialogs;
using Artemis.UI.Shared.Services;
using Humanizer;
using RGB.NET.Core;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Devices
{
public class DeviceSettingsViewModel : Screen
{
private readonly IDeviceDebugVmFactory _deviceDebugVmFactory;
private readonly IDeviceService _deviceService;
private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService;
private readonly IWindowManager _windowManager;
public DeviceSettingsViewModel(ArtemisDevice device,
IDeviceService deviceService,
IDialogService dialogService,
IWindowManager windowManager,
IDeviceDebugVmFactory deviceDebugVmFactory,
IRgbService rgbService)
{
_deviceService = deviceService;
_dialogService = dialogService;
_windowManager = windowManager;
_deviceDebugVmFactory = deviceDebugVmFactory;
_rgbService = rgbService;
Device = device;
Type = Device.DeviceType.ToString().Humanize();
Name = Device.RgbDevice.DeviceInfo.Model;
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
}
public ArtemisDevice Device { get; }
public string Type { get; }
public string Name { get; }
public string Manufacturer { get; }
public bool CanDetectInput => Device.DeviceType == RGBDeviceType.Keyboard ||
Device.DeviceType == RGBDeviceType.Mouse;
public bool IsDeviceEnabled
{
get => Device.IsEnabled;
set { Task.Run(() => UpdateIsDeviceEnabled(value)); }
}
public void IdentifyDevice()
{
_deviceService.IdentifyDevice(Device);
}
public void OpenPluginDirectory()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Device.DeviceProvider.Plugin.Directory.FullName);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
public async Task DetectInput()
{
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(
new Dictionary<string, object> {{"device", Device}}
);
if ((bool) madeChanges)
_rgbService.SaveDevice(Device);
}
public void ViewProperties()
{
_windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDialogViewModel(Device));
}
private async Task UpdateIsDeviceEnabled(bool value)
{
if (!value)
value = !await ((DeviceSettingsTabViewModel) Parent).ShowDeviceDisableDialog();
if (value)
_rgbService.EnableDevice(Device);
else
_rgbService.DisableDevice(Device);
NotifyOfPropertyChange(nameof(IsDeviceEnabled));
SaveDevice();
}
private void SaveDevice()
{
_rgbService.SaveDevice(Device);
}
}
}
@@ -0,0 +1,33 @@
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Settings.DevicesTabView">
<Grid MaxWidth="1050">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel>
<TextBlock Classes="h4">Device management</TextBlock>
<TextBlock>
Below you view and manage the devices that were detected by Artemis.
</TextBlock>
<TextBlock>
Disabling a device will cause it to stop updating. Some SDKs will even go back to using manufacturer lighting (Artemis restart may be required).
</TextBlock>
</StackPanel>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl Items="{Binding Devices}" Margin="-5 0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class DevicesTabView : ReactiveUserControl<DevicesTabViewModel>
{
public DevicesTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Device;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Threading;
using DynamicData;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class DevicesTabViewModel : ActivatableViewModelBase
{
private readonly IDeviceVmFactory _deviceVmFactory;
private readonly IRgbService _rgbService;
private readonly IWindowService _windowService;
private bool _confirmedDisable;
public DevicesTabViewModel(IRgbService rgbService, IWindowService windowService, IDeviceVmFactory deviceVmFactory)
{
DisplayName = "Devices";
_rgbService = rgbService;
_windowService = windowService;
_deviceVmFactory = deviceVmFactory;
Devices = new ObservableCollection<DeviceSettingsViewModel>();
this.WhenActivated(disposables =>
{
GetDevices();
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceAdded += x, x => rgbService.DeviceAdded -= x)
.Subscribe(d => AddDevice(d.EventArgs.Device))
.DisposeWith(disposables);
Observable.FromEventPattern<DeviceEventArgs>(x => rgbService.DeviceRemoved += x, x => rgbService.DeviceRemoved -= x)
.Subscribe(d => RemoveDevice(d.EventArgs.Device))
.DisposeWith(disposables);
});
}
private void GetDevices()
{
Devices.Clear();
Dispatcher.UIThread.InvokeAsync(() =>
{
Devices.AddRange(_rgbService.Devices.Select(d => _deviceVmFactory.DeviceSettingsViewModel(d, this)));
}, DispatcherPriority.Background);
}
public ObservableCollection<DeviceSettingsViewModel> Devices { get; }
public async Task<bool> ShowDeviceDisableDialog()
{
if (_confirmedDisable)
return true;
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Disabling device",
"Disabling a device will cause it to stop updating. " +
"\r\nSome SDKs will even go back to using manufacturer lighting (Artemis restart may be required)."
);
if (confirmed)
_confirmedDisable = true;
return confirmed;
}
private void AddDevice(ArtemisDevice device)
{
// If the device was only enabled, don't add it
if (Devices.Any(d => d.Device == device))
return;
Devices.Add(_deviceVmFactory.DeviceSettingsViewModel(device, this));
}
private void RemoveDevice(ArtemisDevice device)
{
// If the device was only disabled don't remove it
if (_rgbService.Devices.Contains(device))
return;
DeviceSettingsViewModel? viewModel = Devices.FirstOrDefault(i => i.Device == device);
if (viewModel != null)
Devices.Remove(viewModel);
}
}
}
@@ -1,497 +0,0 @@
<UserControl
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.UI.Screens.Settings.Tabs.General"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:dataTemplateSelectors="clr-namespace:Artemis.UI.DataTemplateSelectors"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:Converters="clr-namespace:Artemis.UI.Converters" x:Class="Artemis.UI.Screens.Settings.Tabs.General.GeneralSettingsTabView"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance {x:Type local:GeneralSettingsTabViewModel}}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Artemis.UI;component/ResourceDictionaries/LayerBrushDescriptors.xaml" />
</ResourceDictionary.MergedDictionaries>
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
<Converters:NormalizedPercentageConverter x:Key="NormalizedPercentageConverter" />
</ResourceDictionary>
</UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="15" MaxWidth="800">
<!-- General settings -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
<Run Text="General" />
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Start up with Windows</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding UIAutoRun.Value}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Start up with Windows minimized</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}"
IsChecked="{Binding UIShowOnStartup.Value, Converter={StaticResource InverseBooleanConverter}}"
IsEnabled="{Binding UIAutoRun.Value}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Startup delay</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
<Run Text="Set the amount of seconds to wait before running Artemis with Windows." /><LineBreak />
<Run Text="If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value." />
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding UIAutoRunDelay.Value}"
IsEnabled="{Binding UIAutoRun.Value}"
Width="100"
materialDesign:TextFieldAssist.SuffixText="sec"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">Color scheme</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Pick between a light and dark color scheme, the automatic option copies your Windows settings.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedValue="{Binding UIColorScheme.Value}"
ItemsSource="{Binding ColorSchemes}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Log level
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the logging level, a higher logging level will result in more log files.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedValue="{Binding CoreLoggingLevel.Value}"
ItemsSource="{Binding LogLevels}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Logs
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where logs are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowLogsFolder}" Width="150" Content="SHOW LOGS" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
<!-- Web server settings -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
Web server
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Web server port
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
<Run Text="Artemis runs a local web server that can be used to externally interact with the application." /><LineBreak />
<Run Text="This web server can only be accessed by applications running on your own computer, e.g. supported games." />
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}"
Text="{Binding WebServerPort.Value}"
Width="100"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
<!-- Update settings -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
<Run Text="Updating" />
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Check for updates
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding UICheckForUpdates.Value}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Update
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Use the button on the right to check for updates now.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action OfferUpdatesIfFound}" Width="150" Content="CHECK NOW" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
<!-- Profile editor settings -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
<Run Text="Profile editor" />
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Show condition data model values
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
While selecting a condition target, show the current values of the data model.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleButton Style="{StaticResource MaterialDesignSwitchToggleButton}" IsChecked="{Binding ProfileEditorShowDataModelValues.Value}" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Default brush
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the default brush that is applied to new layers
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="150"
materialDesign:ComboBoxAssist.ClassicMode="True"
materialDesign:ValidationAssist.UsePopup="True"
materialDesign:HintAssist.IsFloating="false"
HorizontalAlignment="Left"
ItemsSource="{Binding LayerBrushDescriptors}"
SelectedValue="{Binding SelectedLayerBrushDescriptor}"
ItemTemplateSelector="{dataTemplateSelectors:ComboBoxTemplateSelector DropdownItemsTemplate={StaticResource ExtendedLayerBrushDescriptorTemplate}, SelectedItemTemplate={StaticResource SimpleLayerBrushDescriptorTemplate}}" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
<!-- Rendering settings -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
Rendering
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Preferred render method
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedItem="{Binding CorePreferredGraphicsContext.Value}"
materialDesign:HintAssist.IsFloating="false">
<system:String>Software</system:String>
<system:String>Vulkan</system:String>
</ComboBox>
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Render scale
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedItem="{Binding SelectedRenderScale}"
ItemsSource="{Binding RenderScales}"
DisplayMemberPath="Item1"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Target frame rate
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" TextWrapping="Wrap">
Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations. <LineBreak/>
The options past 45 FPS are mostly useless unless you are using a custom device.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Style="{StaticResource MaterialDesignFilledComboBox}"
Width="100"
SelectedItem="{Binding SelectedTargetFrameRate}"
ItemsSource="{Binding TargetFrameRates}"
DisplayMemberPath="Item1"
materialDesign:HintAssist.IsFloating="false" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
<!-- Tools -->
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="0 15">
Tools
</TextBlock>
<materialDesign:Card materialDesign:ShadowAssist.ShadowDepth="Depth1" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel Margin="15">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
<Run Text="Setup wizard" />
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
<Run Text="Opens the startup wizard usually shown when Artemis first starts." />
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowSetupWizard}" Width="150" Content="SHOW WIZARD" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Debugger
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Use the debugger to see the raw image Artemis is rendering on the surface.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowDebugger}" Width="150" Content="SHOW DEBUGGER" />
</StackPanel>
</Grid>
<Separator Style="{StaticResource MaterialDesignSeparator}" Margin="-15 5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Style="{StaticResource MaterialDesignTextBlock}">
Application files
</TextBlock>
<TextBlock Style="{StaticResource MaterialDesignTextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}">
Opens the directory where application files like plugins and settings are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Style="{StaticResource MaterialDesignOutlinedButton}" Command="{s:Action ShowDataFolder}" Width="150" Content="SHOW APP FILES" />
</StackPanel>
</Grid>
</StackPanel>
</materialDesign:Card>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -1,264 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.UI.Screens.StartupWizard;
using Artemis.UI.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Utilities;
using Ninject;
using Serilog.Events;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.General
{
public class GeneralSettingsTabViewModel : Screen
{
private readonly IDebugService _debugService;
private readonly PluginSetting<LayerBrushReference> _defaultLayerBrushDescriptor;
private readonly IDialogService _dialogService;
private readonly IKernel _kernel;
private readonly IMessageService _messageService;
private readonly IRegistrationService _registrationService;
private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService;
private readonly IWindowManager _windowManager;
private bool _canOfferUpdatesIfFound = true;
public GeneralSettingsTabViewModel(
IKernel kernel,
IWindowManager windowManager,
IDialogService dialogService,
IDebugService debugService,
ISettingsService settingsService,
IUpdateService updateService,
IPluginManagementService pluginManagementService,
IRegistrationService registrationService,
IMessageService messageService
)
{
DisplayName = "GENERAL";
_kernel = kernel;
_windowManager = windowManager;
_dialogService = dialogService;
_debugService = debugService;
_settingsService = settingsService;
_updateService = updateService;
_registrationService = registrationService;
_messageService = messageService;
RenderScales = new BindableCollection<Tuple<string, double>>
{
new("25%", 0.25),
new("50%", 0.5),
new("100%", 1)
};
TargetFrameRates = new BindableCollection<Tuple<string, int>>
{
new("10 FPS", 10),
new("20 FPS", 20),
new("30 FPS", 30),
new("45 FPS", 45),
new("60 FPS (lol)", 60),
new("144 FPS (omegalol)", 144)
};
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
LayerBrushDescriptors = new BindableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
}
public BindableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
public LayerBrushDescriptor SelectedLayerBrushDescriptor
{
get => LayerBrushDescriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(_defaultLayerBrushDescriptor.Value));
set => _defaultLayerBrushDescriptor.Value = new LayerBrushReference(value);
}
public BindableCollection<ValueDescription> LogLevels { get; }
public BindableCollection<ValueDescription> ColorSchemes { get; }
public BindableCollection<Tuple<string, double>> RenderScales { get; }
public BindableCollection<Tuple<string, int>> TargetFrameRates { get; }
public Tuple<string, double> SelectedRenderScale
{
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Item2 - CoreRenderScale.Value) < 0.01);
set => CoreRenderScale.Value = value.Item2;
}
public Tuple<string, int> SelectedTargetFrameRate
{
get => TargetFrameRates.FirstOrDefault(s => s.Item2 == CoreTargetFrameRate.Value);
set => CoreTargetFrameRate.Value = value.Item2;
}
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
public PluginSetting<ApplicationColorScheme> UIColorScheme => _settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic);
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
private void UIAutoRunOnSettingChanged(object sender, EventArgs e)
{
Task.Run(() => ApplyAutorun(false));
}
private void UIAutoRunDelayOnSettingChanged(object sender, EventArgs e)
{
Task.Run(() => ApplyAutorun(true));
}
private void CorePreferredGraphicsContextOnSettingChanged(object sender, EventArgs e)
{
_registrationService.ApplyPreferredGraphicsContext();
}
private void ApplyAutorun(bool recreate)
{
if (!UIAutoRun.Value)
UIShowOnStartup.Value = true;
// Remove the old auto-run method of placing a shortcut in shell:startup
string autoRunFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), "Artemis.lnk");
if (File.Exists(autoRunFile))
File.Delete(autoRunFile);
if (Constants.BuildInfo.IsLocalBuild)
return;
// Create or remove the task if necessary
try
{
bool taskCreated = false;
if (!recreate)
taskCreated = SettingsUtilities.IsAutoRunTaskCreated();
if (UIAutoRun.Value && !taskCreated)
SettingsUtilities.CreateAutoRunTask(TimeSpan.FromSeconds(UIAutoRunDelay.Value));
else if (!UIAutoRun.Value && taskCreated)
SettingsUtilities.RemoveAutoRunTask();
}
catch (Exception e)
{
Execute.PostToUIThread(() => _dialogService.ShowExceptionDialog("An exception occured while trying to apply the auto run setting", e));
throw;
}
}
#region View methods
public bool CanOfferUpdatesIfFound
{
get => _canOfferUpdatesIfFound;
set => SetAndNotify(ref _canOfferUpdatesIfFound, value);
}
public void ShowDebugger()
{
_debugService.ShowDebugger();
}
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void ShowSetupWizard()
{
_windowManager.ShowDialog(_kernel.Get<StartupWizardViewModel>());
}
public void ShowDataFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Constants.DataFolder);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the data folder for you", e);
}
}
public async void OfferUpdatesIfFound()
{
if (!CanOfferUpdatesIfFound)
return;
CanOfferUpdatesIfFound = false;
try
{
bool updateFound = await _updateService.OfferUpdateIfFound();
if (!updateFound)
_messageService.ShowMessage("You are already running the latest Artemis build. (☞゚ヮ゚)☞");
}
catch (Exception exception)
{
_messageService.ShowMessage($"Failed to check for updates: {exception.Message}");
}
finally
{
CanOfferUpdatesIfFound = true;
}
}
#endregion
#region Overrides of Screen
protected override void OnInitialActivate()
{
Task.Run(() => ApplyAutorun(false));
UIAutoRun.SettingChanged += UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged += UIAutoRunDelayOnSettingChanged;
CorePreferredGraphicsContext.SettingChanged += CorePreferredGraphicsContextOnSettingChanged;
base.OnInitialActivate();
}
protected override void OnClose()
{
UIAutoRun.SettingChanged -= UIAutoRunOnSettingChanged;
UIAutoRunDelay.SettingChanged -= UIAutoRunDelayOnSettingChanged;
CorePreferredGraphicsContext.SettingChanged -= CorePreferredGraphicsContextOnSettingChanged;
_settingsService.SaveAllSettings();
base.OnClose();
}
#endregion
}
public enum ApplicationColorScheme
{
Light,
Dark,
Automatic
}
}
@@ -0,0 +1,360 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:settings="clr-namespace:Artemis.UI.Screens.Settings"
mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="2400"
x:Class="Artemis.UI.Screens.Settings.GeneralTabView"
x:DataType="settings:GeneralTabViewModel">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Margin="15" MaxWidth="1000">
<!-- General settings -->
<TextBlock Classes="h4" Margin="0 15">
General
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Startup delay</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Set the amount of seconds to wait before auto-running Artemis.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" Orientation="Horizontal">
<TextBox Text="{CompiledBinding UIAutoRunDelay.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" Width="120" />
<TextBlock VerticalAlignment="Center" TextAlignment="Right" Width="30">sec</TextBlock>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>Color scheme</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Pick between a light and dark color scheme, the automatic option copies your system settings.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:EnumComboBox Width="150" Value="{CompiledBinding UIColorScheme.Value}" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Log level
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the logging level, a higher logging level will result in more log files.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:EnumComboBox Width="150" Value="{CompiledBinding CoreLoggingLevel.Value}" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Logs
</TextBlock>
<TextBlock Classes="subtitle">
Opens the directory where logs are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowLogs}" Width="150" Content="Show logs" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Web server settings -->
<TextBlock Classes="h4" Margin="0 15">
Web server
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Web server port
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Artemis runs a local web server that can be used to externally interact with the application.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
This web server can only be accessed by applications running on your own computer, e.g. supported games.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<TextBox Text="{CompiledBinding WebServerPort.Value}" Width="150" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Update settings -->
<TextBlock Classes="h4" Margin="0 15">
Updating
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Check for updates
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If enabled, we'll check for updates on startup and periodically while running.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Update
</TextBlock>
<TextBlock Classes="subtitle">
Use the button on the right to check for updates now.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{Binding OfferUpdatesIfFound}" Width="150" Content="Check now" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Profile editor settings -->
<TextBlock Classes="h4" Margin="0 15">
Profile editor
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Show condition data model values
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
While selecting a condition target, show the current values of the data model.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Default brush
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the default brush that is applied to new layers
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<StackPanel.Styles>
<Style Selector="ComboBox.brush /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<StackPanel Orientation="Horizontal">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" Height="20" Width="20" VerticalAlignment="Center" Margin="0 0 5 0"/>
<TextBlock Text="{CompiledBinding DisplayName}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Styles>
<ComboBox Classes="brush"
Width="200"
HorizontalAlignment="Left"
Items="{CompiledBinding LayerBrushDescriptors}"
SelectedItem="{CompiledBinding SelectedLayerBrushDescriptor}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type layerBrushes:LayerBrushDescriptor}">
<Grid ColumnDefinitions="30,*" RowDefinitions="Auto,Auto">
<avalonia:MaterialIcon Grid.Row="0"
Grid.RowSpan="2"
Kind="{CompiledBinding Icon}"
Height="20"
Width="20"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="{CompiledBinding DisplayName}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Classes="subtitle" Grid.Row="1" Grid.Column="1" Text="{CompiledBinding Description}" TextWrapping="Wrap" MaxWidth="350" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Rendering settings -->
<TextBlock Classes="h4" Margin="0 15">
Rendering
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Preferred render method
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Software-based rendering is done purely on the CPU while Vulkan uses GPU-acceleration.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Width="150"
SelectedItem="{CompiledBinding CorePreferredGraphicsContext.Value}"
Items="{CompiledBinding GraphicsContexts}"/>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Render scale
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the resolution Artemis renders at, higher scale means more CPU-usage, especially on large surfaces.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Width="150"
SelectedItem="{CompiledBinding SelectedRenderScale}"
Items="{CompiledBinding RenderScales}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding [0]}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Target frame rate
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Sets the FPS Artemis tries to render at, higher FPS means more CPU-usage but smoother animations.
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
The options past 45 FPS are mostly useless unless you are using a custom device.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ComboBox Width="150"
SelectedItem="{CompiledBinding SelectedTargetFrameRate}"
Items="{CompiledBinding TargetFrameRates}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding [0]}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- Tools -->
<TextBlock Classes="h4" Margin="0 15">
Tools
</TextBlock>
<Border Classes="card" VerticalAlignment="Stretch" Margin="0,0,5,0">
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Setup wizard
</TextBlock>
<TextBlock Classes="subtitle">
Opens the startup wizard usually shown when Artemis first starts.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowSetupWizard}" Width="150" Content="Show wizard" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Debugger
</TextBlock>
<TextBlock Classes="subtitle">
Use the debugger to see the raw image Artemis is rendering on the surface.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowDebugger}" Width="150" Content="Show debugger" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock>
Application files
</TextBlock>
<TextBlock Classes="subtitle">
Opens the directory where application files like plugins and settings are stored.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<Button Command="{CompiledBinding ShowDataFolder}" Width="150" Content="Show app files" />
</StackPanel>
</Grid>
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class GeneralTabView : ReactiveUserControl<GeneralTabViewModel>
{
public GeneralTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reactive;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.LayerBrushes;
using Artemis.Core.Services;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared;
using Avalonia;
using FluentAvalonia.Styling;
using ReactiveUI;
using Serilog.Events;
namespace Artemis.UI.Screens.Settings
{
public class GeneralTabViewModel : ActivatableViewModelBase
{
private readonly PluginSetting<LayerBrushReference> _defaultLayerBrushDescriptor;
private readonly ISettingsService _settingsService;
private readonly IDebugService _debugService;
private readonly FluentAvaloniaTheme _fluentAvaloniaTheme;
public GeneralTabViewModel(ISettingsService settingsService, IPluginManagementService pluginManagementService, IDebugService debugService)
{
DisplayName = "General";
_settingsService = settingsService;
_debugService = debugService;
_fluentAvaloniaTheme = AvaloniaLocator.Current.GetService<FluentAvaloniaTheme>(); ;
List<LayerBrushProvider> layerBrushProviders = pluginManagementService.GetFeaturesOfType<LayerBrushProvider>();
LayerBrushDescriptors = new ObservableCollection<LayerBrushDescriptor>(layerBrushProviders.SelectMany(l => l.LayerBrushDescriptors));
_defaultLayerBrushDescriptor = _settingsService.GetSetting("ProfileEditor.DefaultLayerBrushDescriptor", new LayerBrushReference
{
LayerBrushProviderId = "Artemis.Plugins.LayerBrushes.Color.ColorBrushProvider-92a9d6ba",
BrushType = "SolidBrush"
});
ShowLogs = ReactiveCommand.Create(ExecuteShowLogs);
CheckForUpdate = ReactiveCommand.CreateFromTask(ExecuteCheckForUpdate);
ShowSetupWizard = ReactiveCommand.Create(ExecuteShowSetupWizard);
ShowDebugger = ReactiveCommand.Create(ExecuteShowDebugger);
ShowDataFolder = ReactiveCommand.Create(ExecuteShowDataFolder);
UIColorScheme.SettingChanged += UIColorSchemeOnSettingChanged;
}
private void UIColorSchemeOnSettingChanged(object? sender, EventArgs e)
{
if (UIColorScheme.Value == ApplicationColorScheme.Automatic)
_fluentAvaloniaTheme.RequestedTheme = null;
else if (UIColorScheme.Value == ApplicationColorScheme.Light)
_fluentAvaloniaTheme.RequestedTheme = "Light";
else if (UIColorScheme.Value == ApplicationColorScheme.Dark)
_fluentAvaloniaTheme.RequestedTheme = "Dark";
else
_fluentAvaloniaTheme.RequestedTheme = _fluentAvaloniaTheme.RequestedTheme;
}
public ReactiveCommand<Unit, Unit> ShowLogs { get; }
public ReactiveCommand<Unit, Unit> CheckForUpdate { get; }
public ReactiveCommand<Unit, Unit> ShowSetupWizard { get; }
public ReactiveCommand<Unit, Unit> ShowDebugger { get; }
public ReactiveCommand<Unit, Unit> ShowDataFolder { get; }
public ObservableCollection<LayerBrushDescriptor> LayerBrushDescriptors { get; }
public ObservableCollection<string> GraphicsContexts { get; } = new()
{
"Software",
"Vulkan"
};
public ObservableCollection<(string, double)> RenderScales { get; } = new()
{
new ValueTuple<string, double>("25%", 0.25),
new ValueTuple<string, double>("50%", 0.5),
new ValueTuple<string, double>("100%", 1)
};
public ObservableCollection<(string, int)> TargetFrameRates { get; } = new()
{
new ValueTuple<string, int>("10 FPS", 10),
new ValueTuple<string, int>("20 FPS", 20),
new ValueTuple<string, int>("30 FPS", 30),
new ValueTuple<string, int>("45 FPS", 45),
new ValueTuple<string, int>("60 FPS (lol)", 60),
new ValueTuple<string, int>("144 FPS (omegalol)", 144)
};
public LayerBrushDescriptor? SelectedLayerBrushDescriptor
{
get => LayerBrushDescriptors.FirstOrDefault(d => d.MatchesLayerBrushReference(_defaultLayerBrushDescriptor.Value));
set
{
if (value != null) _defaultLayerBrushDescriptor.Value = new LayerBrushReference(value);
}
}
public (string, double)? SelectedRenderScale
{
get => RenderScales.FirstOrDefault(s => Math.Abs(s.Item2 - CoreRenderScale.Value) < 0.01);
set
{
if (value != null) CoreRenderScale.Value = value.Value.Item2;
}
}
public (string, int)? SelectedTargetFrameRate
{
get => TargetFrameRates.FirstOrDefault(s => s.Item2 == CoreTargetFrameRate.Value);
set
{
if (value != null) CoreTargetFrameRate.Value = value.Value.Item2;
}
}
public PluginSetting<bool> UIAutoRun => _settingsService.GetSetting("UI.AutoRun", false);
public PluginSetting<int> UIAutoRunDelay => _settingsService.GetSetting("UI.AutoRunDelay", 15);
public PluginSetting<bool> UIShowOnStartup => _settingsService.GetSetting("UI.ShowOnStartup", true);
public PluginSetting<bool> UICheckForUpdates => _settingsService.GetSetting("UI.CheckForUpdates", true);
public PluginSetting<ApplicationColorScheme> UIColorScheme => _settingsService.GetSetting("UI.ColorScheme", ApplicationColorScheme.Automatic);
public PluginSetting<bool> ProfileEditorShowDataModelValues => _settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
public PluginSetting<LogEventLevel> CoreLoggingLevel => _settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Information);
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Vulkan");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
#region General
private void ExecuteShowLogs()
{
OpenFolder(Constants.LogsFolder);
}
#endregion
#region Updating
private Task ExecuteCheckForUpdate(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
#endregion
#region Tools
private void ExecuteShowSetupWizard()
{
}
private void ExecuteShowDebugger()
{
_debugService.ShowDebugger();
}
private void ExecuteShowDataFolder()
{
OpenFolder(Constants.DataFolder);
}
#endregion
private void OpenFolder(string path)
{
if (OperatingSystem.IsWindows())
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", path);
}
}
}
public enum ApplicationColorScheme
{
Light,
Dark,
Automatic
}
}
@@ -1,105 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginFeatureView"
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.UI.Screens.Settings.Tabs.Plugins"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginFeatureViewModel}">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="-3 -8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Icon column -->
<shared:ArtemisIcon Grid.Column="0"
Icon="{Binding FeatureInfo.ResolvedIcon}"
Width="20"
Height="20"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, FallbackValue=Collapsed}" />
<Button Grid.Column="0"
Margin="-8"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Visibility="{Binding LoadException, Converter={StaticResource NullToVisibilityConverter}, FallbackValue=Collapsed}"
Style="{StaticResource MaterialDesignIconButton}"
Foreground="#E74C4C"
ToolTip="An exception occurred while enabling this feature, click to view"
Command="{s:Action ViewLoadException}">
<materialDesign:PackIcon Kind="AlertCircle" />
</Button>
<!-- Display name column -->
<TextBlock Grid.Column="1"
Text="{Binding FeatureInfo.Name}"
Style="{StaticResource MaterialDesignTextBlock}"
VerticalAlignment="Center"
TextWrapping="Wrap"
ToolTip="{Binding FeatureInfo.Description}" />
<!-- Enable toggle column -->
<StackPanel Grid.Column="2"
HorizontalAlignment="Right"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay, FallbackValue=Collapsed}"
Orientation="Horizontal"
VerticalAlignment="Top"
ToolTip="This feature cannot be disabled without disabling the whole plugin"
ToolTipService.IsEnabled="{Binding FeatureInfo.AlwaysEnabled}">
<materialDesign:PackIcon Kind="ShieldHalfFull"
ToolTip="Plugin requires admin rights"
VerticalAlignment="Center"
Margin="0 0 5 0"
Visibility="{Binding ShowShield, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
<CheckBox Style="{StaticResource MaterialDesignCheckBox}"
IsChecked="{Binding IsEnabled}"
IsEnabled="{Binding CanToggleEnabled}">
Feature enabled
</CheckBox>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Margin="0 -4 -10 -4"
Foreground="{StaticResource MaterialDesignBody}"
IsEnabled="{Binding IsPopupEnabled}">
<StackPanel>
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay, FallbackValue=Collapsed}">
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0" IsIndeterminate="True" />
</StackPanel>
</Grid>
</UserControl>
@@ -1,200 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginFeatureViewModel : Screen
{
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private readonly IMessageService _messageService;
private readonly IPluginManagementService _pluginManagementService;
private bool _enabling;
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
bool showShield,
ICoreService coreService,
IDialogService dialogService,
IPluginManagementService pluginManagementService,
IMessageService messageService)
{
_coreService = coreService;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
_messageService = messageService;
FeatureInfo = pluginFeatureInfo;
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
}
public PluginFeatureInfo FeatureInfo { get; }
public Exception LoadException => FeatureInfo.LoadException;
public bool ShowShield { get; }
public bool Enabling
{
get => _enabling;
set => SetAndNotify(ref _enabling, value);
}
public bool IsEnabled
{
get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public bool CanToggleEnabled => FeatureInfo.Plugin.IsEnabled && !FeatureInfo.AlwaysEnabled;
public bool CanInstallPrerequisites => FeatureInfo.Prerequisites.Any();
public bool CanRemovePrerequisites => FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any());
public bool IsPopupEnabled => CanInstallPrerequisites || CanRemovePrerequisites;
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void ViewLoadException()
{
if (LoadException == null)
return;
_dialogService.ShowExceptionDialog("Feature failed to enable", LoadException);
}
public async Task InstallPrerequisites()
{
if (FeatureInfo.Prerequisites.Any())
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
}
public async Task RemovePrerequisites()
{
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
NotifyOfPropertyChange(nameof(IsEnabled));
}
}
protected override void OnInitialActivate()
{
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
FeatureInfo.Plugin.Enabled += PluginOnToggled;
FeatureInfo.Plugin.Disabled += PluginOnToggled;
base.OnInitialActivate();
}
protected override void OnClose()
{
_pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling;
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
FeatureInfo.Plugin.Enabled -= PluginOnToggled;
FeatureInfo.Plugin.Disabled -= PluginOnToggled;
base.OnClose();
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
if (FeatureInfo.Instance == null)
{
NotifyOfPropertyChange(nameof(IsEnabled));
_messageService.ShowMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.", "VIEW LOGS", ShowLogsFolder);
return;
}
if (enable)
{
Enabling = true;
try
{
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
}
// Check if all prerequisites are met async
if (!FeatureInfo.ArePrerequisitesMet())
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, new List<IPrerequisitesSubject> {FeatureInfo});
if (!FeatureInfo.ArePrerequisitesMet())
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
}
await Task.Run(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true));
}
catch (Exception e)
{
_messageService.ShowMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
}
finally
{
Enabling = false;
}
}
else
{
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
NotifyOfPropertyChange(nameof(IsEnabled));
}
}
private void OnFeatureEnabling(object sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = true;
}
private void OnFeatureEnableStopped(object sender, PluginFeatureEventArgs e)
{
if (e.PluginFeature != FeatureInfo.Instance) return;
Enabling = false;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(LoadException));
}
private void PluginOnToggled(object sender, EventArgs e)
{
NotifyOfPropertyChange(nameof(CanToggleEnabled));
}
}
}
@@ -1,91 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsTabView"
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.UI.Screens.Settings.Tabs.Plugins"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginSettingsTabViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Margin="0 15" Width="910">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" TextWrapping="Wrap">
The list below shows all loaded plugins. <LineBreak />
If you're missing something, view your logs folder.
</TextBlock>
<materialDesign:PackIcon Grid.Column="1"
Kind="Search"
VerticalAlignment="Top"
Margin="15 8 0 0" />
<TextBox Grid.Column="2"
Style="{StaticResource MaterialDesignFilledTextBox}"
materialDesign:TextFieldAssist.HasClearButton="True"
materialDesign:HintAssist.Hint="Search plugin"
materialDesign:HintAssist.IsFloating="False"
VerticalAlignment="Top"
Margin="5 0"
Text="{Binding SearchPluginInput, Delay=300, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="3" Style="{DynamicResource MaterialDesignFlatButton}"
HorizontalAlignment="Right"
Command="{s:Action OpenUrl}"
CommandParameter="https://wiki.artemis-rgb.com/en/guides/user/plugins">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="OpenInBrowser" />
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock>
</StackPanel>
</Button>
<Button Grid.Column="4"
HorizontalAlignment="Right"
Style="{StaticResource MaterialDesignRaisedButton}"
Command="{s:Action ImportPlugin}"
Margin="15 0">
IMPORT PLUGIN
</Button>
</Grid>
<ListBox Grid.Row="1"
ItemsSource="{Binding Items}"
VirtualizingPanel.ScrollUnit="Pixel"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
Margin="15 0 15 15">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}" Margin="5" HorizontalAlignment="Center" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
@@ -1,119 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Shared.Services;
using Ookii.Dialogs.Wpf;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginSettingsTabViewModel : Conductor<PluginSettingsViewModel>.Collection.AllActive
{
private readonly IPluginManagementService _pluginManagementService;
private readonly IMessageService _messageService;
private readonly ISettingsVmFactory _settingsVmFactory;
private string _searchPluginInput;
private List<PluginSettingsViewModel> _instances;
public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, IMessageService messageService, ISettingsVmFactory settingsVmFactory)
{
DisplayName = "PLUGINS";
_pluginManagementService = pluginManagementService;
_messageService = messageService;
_settingsVmFactory = settingsVmFactory;
}
public string SearchPluginInput
{
get => _searchPluginInput;
set
{
if (!SetAndNotify(ref _searchPluginInput, value)) return;
UpdatePluginSearch();
}
}
private void UpdatePluginSearch()
{
if (_instances == null)
return;
List<PluginSettingsViewModel> instances = _instances;
string search = SearchPluginInput?.ToLower();
if (!string.IsNullOrWhiteSpace(search))
instances = instances.Where(i => i.Plugin.Info.Name.ToLower().Contains(search) ||
i.Plugin.Info.Description != null && i.Plugin.Info.Description.ToLower().Contains(search)).ToList();
foreach (PluginSettingsViewModel pluginSettingsViewModel in instances)
{
if (!Items.Contains(pluginSettingsViewModel))
Items.Add(pluginSettingsViewModel);
}
foreach (PluginSettingsViewModel pluginSettingsViewModel in Items.ToList())
{
if (!instances.Contains(pluginSettingsViewModel))
Items.Remove(pluginSettingsViewModel);
}
((BindableCollection<PluginSettingsViewModel>) Items).Sort(i => i.Plugin.Info.Name);
}
protected override void OnInitialActivate()
{
// Take it off the UI thread to avoid freezing on tab change
Task.Run(async () =>
{
await Task.Delay(200);
GetPluginInstances();
});
base.OnInitialActivate();
}
public void OpenUrl(string url)
{
Core.Utilities.OpenUrl(url);
}
public async Task ImportPlugin()
{
VistaOpenFileDialog dialog = new() {Filter = "ZIP files (*.zip)|*.zip", Title = "Import Artemis plugin"};
bool? result = dialog.ShowDialog();
if (result != true)
return;
// Take the actual import off of the UI thread
await Task.Run(() =>
{
Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName);
GetPluginInstances();
SearchPluginInput = plugin.Info.Name;
// Enable it via the VM to enable the prerequisite dialog
PluginSettingsViewModel pluginViewModel = Items.FirstOrDefault(i => i.Plugin == plugin);
if (pluginViewModel is {IsEnabled: false})
pluginViewModel.IsEnabled = true;
_messageService.ShowMessage($"Imported plugin: {plugin.Info.Name}");
});
}
public void GetPluginInstances()
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
UpdatePluginSearch();
}
}
}
@@ -1,209 +0,0 @@
<UserControl x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsView"
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:devices="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
d:DataContext="{d:DesignInstance devices:PluginSettingsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
</ResourceDictionary.MergedDictionaries>
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
</ResourceDictionary>
</UserControl.Resources>
<materialDesign:Card Width="900">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="5*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<shared:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
Width="48"
Height="48"
Margin="0 5 0 0"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Center"
VerticalAlignment="Top" />
<TextBlock Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignBody2TextBlock}"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Name}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}" />
<TextBlock Grid.Column="1"
Grid.Row="1"
Style="{StaticResource MaterialDesignBody2TextBlock}" Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}"
Text="{Binding Plugin.Info.Author}"
Visibility="{Binding Plugin.Info.Author, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />
<TextBlock Grid.Column="1"
Grid.Row="2"
TextWrapping="Wrap"
behaviors:HighlightTermBehavior.TermToBeHighlighted="{Binding Parent.SearchPluginInput}"
behaviors:HighlightTermBehavior.Text="{Binding Plugin.Info.Description}"
behaviors:HighlightTermBehavior.HighlightForeground="{StaticResource Primary600Foreground}"
behaviors:HighlightTermBehavior.HighlightBackground="{StaticResource Primary600}"
Style="{StaticResource MaterialDesignTextBlock}"
Foreground="{DynamicResource MaterialDesignNavigationItemSubheader}" />
</Grid>
<Grid Grid.Row="1" Grid.Column="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button VerticalAlignment="Bottom"
Style="{StaticResource MaterialDesignRaisedButton}"
ToolTip="Open the plugins settings window"
Margin="4"
Command="{s:Action OpenSettings}">
SETTINGS
</Button>
<materialDesign:PopupBox Style="{StaticResource MaterialDesignToolPopupBox}"
Padding="2 0 2 0"
Foreground="{StaticResource MaterialDesignBody}"
IsPopupOpen="{Binding IsSettingsPopupOpen, Mode=TwoWay}">
<StackPanel>
<Button Command="{s:Action OpenPluginDirectory}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="FolderOpen" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Open plugin directory</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Reload}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Reload" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Reload plugin</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action InstallPrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="CheckAll" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Install prerequisites</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action RemovePrerequisites}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="Delete" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove prerequisites</TextBlock>
</StackPanel>
</Button>
<Separator />
<Button Command="{s:Action RemoveSettings}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DatabaseRemove" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Clear plugin settings</TextBlock>
</StackPanel>
</Button>
<Button Command="{s:Action Remove}">
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon Kind="DeleteForever" Margin="0 0 10 0 " VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center">Remove plugin</TextBlock>
</StackPanel>
</Button>
</StackPanel>
</materialDesign:PopupBox>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Website}"
Visibility="{Binding Plugin.Info.Website, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Website}">
<materialDesign:PackIcon Kind="Web" Width="20" Height="20" />
</Button>
<Button Height="40"
Width="40"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="{Binding Plugin.Info.Repository}"
Visibility="{Binding Plugin.Info.Repository, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}"
Command="{s:Action OpenUri}"
CommandParameter="{Binding Plugin.Info.Repository}">
<materialDesign:PackIcon Kind="Git" Width="20" Height="20" />
</Button>
</StackPanel>
<CheckBox Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignAccentCheckBox}" IsChecked="{Binding IsEnabled}">
<StackPanel Orientation="Horizontal">
<TextBlock>Plugin enabled</TextBlock>
<materialDesign:PackIcon Kind="ShieldHalfFull"
Margin="5 0 0 0"
ToolTip="Plugin requires admin rights"
Visibility="{Binding Plugin.Info.RequiresAdmin, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
</StackPanel>
</CheckBox>
<ProgressBar Grid.Row="0"
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="8"
Visibility="{Binding Enabling, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
Style="{StaticResource MaterialDesignCircularProgressBar}" Value="0"
IsIndeterminate="True" />
</Grid>
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{StaticResource MaterialDesignDivider}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Margin="10 10 0 5" Style="{StaticResource MaterialDesignBody2TextBlock}">Plugin features</TextBlock>
<ListBox Grid.Row="1"
MaxHeight="135"
ItemsSource="{Binding Items}"
HorizontalContentAlignment="Stretch"
VirtualizingPanel.ScrollUnit="Pixel">
<b:Interaction.Behaviors>
<shared:ScrollParentWhenAtMax />
</b:Interaction.Behaviors>
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl s:View.Model="{Binding IsAsync=True}" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsTabStop="False" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Grid>
</materialDesign:Card>
</UserControl>
@@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
/// <summary>
/// Interaction logic for PluginSettingsView.xaml
/// </summary>
public partial class PluginSettingsView : UserControl
{
public PluginSettingsView()
{
InitializeComponent();
}
}
}
@@ -1,325 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Ninject;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginSettingsViewModel : Conductor<PluginFeatureViewModel>.Collection.AllActive
{
private readonly ICoreService _coreService;
private readonly IDialogService _dialogService;
private readonly IMessageService _messageService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowManager _windowManager;
private bool _canInstallPrerequisites;
private bool _canRemovePrerequisites;
private bool _enabling;
private bool _isSettingsPopupOpen;
private Plugin _plugin;
public PluginSettingsViewModel(Plugin plugin,
ISettingsVmFactory settingsVmFactory,
ICoreService coreService,
IWindowManager windowManager,
IDialogService dialogService,
IPluginManagementService pluginManagementService,
IMessageService messageService)
{
Plugin = plugin;
_settingsVmFactory = settingsVmFactory;
_coreService = coreService;
_windowManager = windowManager;
_dialogService = dialogService;
_pluginManagementService = pluginManagementService;
_messageService = messageService;
}
public Plugin Plugin
{
get => _plugin;
set => SetAndNotify(ref _plugin, value);
}
public bool Enabling
{
get => _enabling;
set => SetAndNotify(ref _enabling, value);
}
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null;
public bool IsEnabled
{
get => Plugin.IsEnabled;
set => Task.Run(() => UpdateEnabled(value));
}
public bool IsSettingsPopupOpen
{
get => _isSettingsPopupOpen;
set
{
if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return;
CheckPrerequisites();
}
}
public bool CanInstallPrerequisites
{
get => _canInstallPrerequisites;
set => SetAndNotify(ref _canInstallPrerequisites, value);
}
public bool CanRemovePrerequisites
{
get => _canRemovePrerequisites;
set => SetAndNotify(ref _canRemovePrerequisites, value);
}
public void OpenSettings()
{
PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog;
if (configurationViewModel == null)
return;
try
{
PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type);
_windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
throw;
}
}
public void OpenPluginDirectory()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName);
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
}
}
public async Task Reload()
{
bool wasEnabled = IsEnabled;
_pluginManagementService.UnloadPlugin(Plugin);
Items.Clear();
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Reloaded plugin.");
}
public async Task InstallPrerequisites()
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled));
if (subjects.Any(s => s.Prerequisites.Any()))
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
}
public async Task RemovePrerequisites(bool forPluginRemoval = false)
{
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(!forPluginRemoval ? Plugin.Features.Where(f => f.AlwaysEnabled) : Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
{
await PluginPrerequisitesUninstallDialogViewModel.Show(_dialogService, subjects, forPluginRemoval ? "SKIP, REMOVE PLUGIN" : "CANCEL");
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
}
public async Task RemoveSettings()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
if (!confirmed)
return;
bool wasEnabled = IsEnabled;
if (IsEnabled)
await UpdateEnabled(false);
_pluginManagementService.RemovePluginSettings(Plugin);
if (wasEnabled)
await UpdateEnabled(true);
_messageService.ShowMessage("Cleared plugin settings.");
}
public async Task Remove()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?");
if (!confirmed)
return;
// If the plugin or any of its features has uninstall actions, offer to run these
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features);
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
await RemovePrerequisites(true);
try
{
_pluginManagementService.RemovePlugin(Plugin, false);
((PluginSettingsTabViewModel) Parent).GetPluginInstances();
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Failed to remove plugin", e);
throw;
}
_messageService.ShowMessage("Removed plugin.");
}
public void ShowLogsFolder()
{
try
{
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
}
catch (Exception e)
{
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
}
}
public void OpenUri(Uri uri)
{
Core.Utilities.OpenUrl(uri.ToString());
}
private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e)
{
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private async Task UpdateEnabled(bool enable)
{
if (IsEnabled == enable)
{
NotifyOfPropertyChange(nameof(IsEnabled));
return;
}
if (enable)
{
Enabling = true;
if (Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
{
bool confirmed = await _dialogService.ShowConfirmDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
if (!confirmed)
{
CancelEnable();
return;
}
}
// Check if all prerequisites are met async
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
subjects.AddRange(Plugin.Features.Where(f => f.AlwaysEnabled || f.EnabledInStorage));
if (subjects.Any(s => !s.ArePrerequisitesMet()))
{
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
if (!subjects.All(s => s.ArePrerequisitesMet()))
{
CancelEnable();
return;
}
}
await Task.Run(() =>
{
try
{
_pluginManagementService.EnablePlugin(Plugin, true, true);
}
catch (Exception e)
{
_messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
}
finally
{
Enabling = false;
}
});
}
else
_pluginManagementService.DisablePlugin(Plugin, true);
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private void CancelEnable()
{
Enabling = false;
NotifyOfPropertyChange(nameof(IsEnabled));
NotifyOfPropertyChange(nameof(CanOpenSettings));
}
private void CheckPrerequisites()
{
CanInstallPrerequisites = Plugin.Info.Prerequisites.Any() ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any());
CanRemovePrerequisites = Plugin.Info.Prerequisites.Any(p => p.UninstallActions.Any()) ||
Plugin.Features.Where(f => f.AlwaysEnabled).Any(f => f.Prerequisites.Any(p => p.UninstallActions.Any()));
}
#region Overrides of Screen
protected override void OnInitialActivate()
{
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
base.OnInitialActivate();
}
protected override void OnClose()
{
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
base.OnClose();
}
#endregion
}
}
@@ -1,43 +0,0 @@
<controls:MaterialWindow x:Class="Artemis.UI.Screens.Settings.Tabs.Plugins.PluginSettingsWindowView"
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:local="clr-namespace:Artemis.UI.Screens.Settings.Tabs.Plugins"
xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"
xmlns:s="https://github.com/canton7/Stylet"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
mc:Ignorable="d"
Title="Plugin Configuration | Artemis"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="pack://application:,,,/MaterialDesignThemes.Wpf;component/Resources/Roboto/#Roboto"
UseLayoutRounding="True"
Width="800"
Height="800"
d:DesignHeight="800"
d:DesignWidth="800"
d:DataContext="{d:DesignInstance local:PluginSettingsWindowViewModel}"
Icon="/Resources/Images/Logo/bow.ico">
<materialDesign:DialogHost IsTabStop="False"
Focusable="False"
Identifier="PluginSettingsDialog"
DialogTheme="Inherit">
<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 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>
<ContentControl s:View.Model="{Binding ActiveItem}" />
</DockPanel>
</materialDesign:DialogHost>
</controls:MaterialWindow>
@@ -1,34 +0,0 @@
using System;
using Artemis.Core;
using Artemis.UI.Shared;
using Stylet;
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
{
public class PluginSettingsWindowViewModel : Conductor<PluginConfigurationViewModel>
{
private readonly PluginConfigurationViewModel _configurationViewModel;
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
{
_configurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
Plugin = configurationViewModel.Plugin;
}
public Plugin Plugin { get; }
protected override void OnInitialActivate()
{
ActiveItem = _configurationViewModel;
ActiveItem.Closed += ActiveItemOnClosed;
base.OnInitialActivate();
}
private void ActiveItemOnClosed(object sender, CloseEventArgs e)
{
ActiveItem.Closed -= ActiveItemOnClosed;
RequestClose();
}
}
}
@@ -0,0 +1,13 @@
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.Settings.PluginsTabView">
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,*" Width="900">
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding SearchPluginInput}" Watermark="Search plugins" Margin="0 10" />
<ScrollViewer Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<ItemsControl Items="{Binding Plugins}" />
</ScrollViewer>
</Grid>
</UserControl>
@@ -0,0 +1,18 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public partial class PluginsTabView : ReactiveUserControl<PluginsTabViewModel>
{
public PluginsTabView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Plugins;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Builders;
using Artemis.UI.Shared.Services.Interfaces;
using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Settings
{
public class PluginsTabViewModel : ActivatableViewModelBase
{
private readonly INotificationService _notificationService;
private readonly IPluginManagementService _pluginManagementService;
private readonly ISettingsVmFactory _settingsVmFactory;
private readonly IWindowService _windowService;
private List<PluginSettingsViewModel>? _instances;
private string? _searchPluginInput;
public PluginsTabViewModel(IPluginManagementService pluginManagementService, INotificationService notificationService, IWindowService windowService, ISettingsVmFactory settingsVmFactory)
{
_pluginManagementService = pluginManagementService;
_notificationService = notificationService;
_windowService = windowService;
_settingsVmFactory = settingsVmFactory;
DisplayName = "Plugins";
Plugins = new ObservableCollection<PluginSettingsViewModel>();
this.WhenAnyValue(x => x.SearchPluginInput).Throttle(TimeSpan.FromMilliseconds(100)).Subscribe(SearchPlugins);
this.WhenActivated((CompositeDisposable _) => GetPluginInstances());
}
public ObservableCollection<PluginSettingsViewModel> Plugins { get; }
public string? SearchPluginInput
{
get => _searchPluginInput;
set => RaiseAndSetIfChanged(ref _searchPluginInput, value);
}
public void OpenUrl(string url)
{
Utilities.OpenUrl(url);
}
public async Task ImportPlugin()
{
string[]? files = await _windowService.CreateOpenFileDialog().WithTitle("Import Artemis plugin").HavingFilter(f => f.WithExtension("zip").WithName("ZIP files")).ShowAsync();
if (files == null)
return;
// Take the actual import off of the UI thread
await Task.Run(() =>
{
Plugin plugin = _pluginManagementService.ImportPlugin(files[0]);
GetPluginInstances();
SearchPluginInput = plugin.Info.Name;
// Enable it via the VM to enable the prerequisite dialog
PluginSettingsViewModel? pluginViewModel = Plugins.FirstOrDefault(i => i.Plugin == plugin);
if (pluginViewModel is {IsEnabled: false})
pluginViewModel.IsEnabled = true;
_notificationService.CreateNotification()
.WithTitle("Success")
.WithMessage($"Imported plugin: {plugin.Info.Name}")
.WithSeverity(NotificationSeverity.Success)
.Show();
});
}
public void GetPluginInstances()
{
Plugins.Clear();
Dispatcher.UIThread.InvokeAsync(() =>
{
_instances = _pluginManagementService.GetAllPlugins()
.Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p))
.OrderBy(i => i.Plugin.Info.Name)
.ToList();
SearchPlugins(SearchPluginInput);
}, DispatcherPriority.Background);
}
private void SearchPlugins(string? searchPluginInput)
{
if (_instances == null)
return;
List<PluginSettingsViewModel> instances = _instances;
string? search = searchPluginInput?.ToLower();
if (!string.IsNullOrWhiteSpace(search))
instances = instances.Where(i => i.Plugin.Info.Name.ToLower().Contains(search) ||
i.Plugin.Info.Description != null && i.Plugin.Info.Description.ToLower().Contains(search)).ToList();
foreach (PluginSettingsViewModel pluginSettingsViewModel in instances)
{
if (!Plugins.Contains(pluginSettingsViewModel))
Plugins.Add(pluginSettingsViewModel);
}
foreach (PluginSettingsViewModel pluginSettingsViewModel in Plugins.ToList())
{
if (!instances.Contains(pluginSettingsViewModel))
Plugins.Remove(pluginSettingsViewModel);
}
Plugins.Sort(i => i.Plugin.Info.Name);
}
}
}