mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Meta - Replace WPF projects with Avalonia projects
This commit is contained in:
1 parent
fcf0376a9a
commit
d7f3ee1190
1110 files changed
+10872
-52240
No files matched your search
@@ -0,0 +1,75 @@
|
||||
<controls:CoreWindow 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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:reactiveUi="http://reactiveui.net"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Debugger.DebugView"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis | Debugger"
|
||||
Width="1200"
|
||||
Height="800">
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="StackPanel.sidebar-stackpanel avalonia|MaterialIcon">
|
||||
<Setter Property="Margin" Value="-7 0 0 0" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel.sidebar-stackpanel TextBlock">
|
||||
<Setter Property="Margin" Value="15 0 0 0" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Panel>
|
||||
<controls:NavigationView x:Name="Navigation"
|
||||
SelectedItem="{Binding SelectedItem}"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
PaneDisplayMode="Left"
|
||||
Margin="0 10 0 0">
|
||||
<controls:NavigationView.MenuItems>
|
||||
<controls:NavigationViewItem Tag="Rendering">
|
||||
<controls:NavigationViewItem.Content>
|
||||
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
|
||||
<avalonia:MaterialIcon Kind="Paint" />
|
||||
<TextBlock>Rendering</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:NavigationViewItem.Content>
|
||||
</controls:NavigationViewItem>
|
||||
<controls:NavigationViewItem Tag="DataModel">
|
||||
<controls:NavigationViewItem.Content>
|
||||
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
|
||||
<avalonia:MaterialIcon Kind="FormatListBulletedType" />
|
||||
<TextBlock>Data Model</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:NavigationViewItem.Content>
|
||||
</controls:NavigationViewItem>
|
||||
<controls:NavigationViewItem Tag="Performance">
|
||||
<controls:NavigationViewItem.Content>
|
||||
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
|
||||
<avalonia:MaterialIcon Kind="Gauge" />
|
||||
<TextBlock>Performance</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:NavigationViewItem.Content>
|
||||
</controls:NavigationViewItem>
|
||||
<controls:NavigationViewItem Tag="Logging">
|
||||
<controls:NavigationViewItem.Content>
|
||||
<StackPanel Orientation="Horizontal" Classes="sidebar-stackpanel">
|
||||
<avalonia:MaterialIcon Kind="Text" />
|
||||
<TextBlock>Logging</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:NavigationViewItem.Content>
|
||||
</controls:NavigationViewItem>
|
||||
</controls:NavigationView.MenuItems>
|
||||
|
||||
<reactiveUi:RoutedViewHost Router="{Binding Router}" Padding="12">
|
||||
<reactiveUi:RoutedViewHost.PageTransition>
|
||||
<CrossFade Duration="0.1" />
|
||||
</reactiveUi:RoutedViewHost.PageTransition>
|
||||
</reactiveUi:RoutedViewHost>
|
||||
|
||||
</controls:NavigationView>
|
||||
<StackPanel Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
|
||||
</Panel>
|
||||
|
||||
</controls:CoreWindow>
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.UI.Shared.Events;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger
|
||||
{
|
||||
public class DebugView : ReactiveCoreWindow<DebugViewModel>
|
||||
{
|
||||
public DebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
||||
NavigationView navigation = this.Get<NavigationView>("Navigation");
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Observable.FromEventPattern(x => ViewModel!.ActivationRequested += x, x => ViewModel!.ActivationRequested -= x).Subscribe(_ =>
|
||||
{
|
||||
WindowState = WindowState.Normal;
|
||||
Activate();
|
||||
|
||||
}).DisposeWith(d);
|
||||
ViewModel!.SelectedItem = (NavigationViewItem) navigation.MenuItems.ElementAt(0);
|
||||
});
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void DeviceVisualizer_OnLedClicked(object? sender, LedClickedEventArgs e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.UI.Screens.Debugger.DataModel;
|
||||
using Artemis.UI.Screens.Debugger.Logs;
|
||||
using Artemis.UI.Screens.Debugger.Performance;
|
||||
using Artemis.UI.Screens.Debugger.Render;
|
||||
using Artemis.UI.Screens.Debugger.Settings;
|
||||
using Artemis.UI.Services.Interfaces;
|
||||
using Artemis.UI.Shared;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger
|
||||
{
|
||||
public class DebugViewModel : ActivatableViewModelBase, IScreen
|
||||
{
|
||||
private readonly IKernel _kernel;
|
||||
private readonly IDebugService _debugService;
|
||||
private NavigationViewItem? _selectedItem;
|
||||
|
||||
public DebugViewModel(IKernel kernel, IDebugService debugService)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_debugService = debugService;
|
||||
|
||||
this.WhenAnyValue(x => x.SelectedItem).WhereNotNull().Subscribe(NavigateToSelectedItem);
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Disposable
|
||||
.Create(HandleDeactivation)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public NavigationViewItem? SelectedItem
|
||||
{
|
||||
get => _selectedItem;
|
||||
set => RaiseAndSetIfChanged(ref _selectedItem, value);
|
||||
}
|
||||
|
||||
private void NavigateToSelectedItem(NavigationViewItem item)
|
||||
{
|
||||
// Kind of a lame way to do this but it's so static idc
|
||||
ConstructorArgument hostScreen = new("hostScreen", this);
|
||||
switch (item.Tag as string)
|
||||
{
|
||||
case "Rendering":
|
||||
Router.Navigate.Execute(_kernel.Get<RenderDebugViewModel>(hostScreen));
|
||||
break;
|
||||
case "DataModel":
|
||||
Router.Navigate.Execute(_kernel.Get<DataModelDebugViewModel>(hostScreen));
|
||||
break;
|
||||
case "Performance":
|
||||
Router.Navigate.Execute(_kernel.Get<PerformanceDebugViewModel>(hostScreen));
|
||||
break;
|
||||
case "Logging":
|
||||
Router.Navigate.Execute(_kernel.Get<LogsDebugViewModel>(hostScreen));
|
||||
break;
|
||||
case "Settings":
|
||||
Router.Navigate.Execute(_kernel.Get<DebugSettingsViewModel>(hostScreen));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleDeactivation()
|
||||
{
|
||||
_debugService.ClearDebugger();
|
||||
}
|
||||
|
||||
public RoutingState Router { get; } = new();
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
OnActivationRequested();
|
||||
}
|
||||
|
||||
public event EventHandler? ActivationRequested;
|
||||
|
||||
protected virtual void OnActivationRequested()
|
||||
{
|
||||
ActivationRequested?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<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:dataModel="clr-namespace:Artemis.UI.Shared.DataModelVisualization.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Debugger.DataModel.DataModelDebugView">
|
||||
<UserControl.Resources>
|
||||
<converters:TypeToStringConverter x:Key="TypeToStringConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock Classes="h3">Data Model</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
On this page you can view the contents of the Artemis data model.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
|
||||
Please note that having this window open can have a performance impact on your system.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<TreeView Grid.Row="1" Items="{Binding MainDataModel.Children}">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeViewItem">
|
||||
<Setter Property="IsExpanded" Value="{Binding IsVisualizationExpanded, Mode=TwoWay}" />
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.DataTemplates>
|
||||
<TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertiesViewModel}" ItemsSource="{Binding Children}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock FontWeight="Bold">[</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
|
||||
<TextBlock FontWeight="Bold">]</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding DisplayValue}"
|
||||
FontFamily="Consolas"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
|
||||
</TreeDataTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type dataModel:DataModelListViewModel}" ItemsSource="{Binding ListChildren}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock FontWeight="Bold">[</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
|
||||
<TextBlock FontWeight="Bold">]</TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding CountDisplay, Mode=OneWay}"
|
||||
FontFamily="Consolas"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type dataModel:DataModelPropertyViewModel}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
|
||||
<!-- Value description -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock FontWeight="Bold">[</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
|
||||
<TextBlock FontWeight="Bold">]</TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
|
||||
|
||||
<!-- Value display -->
|
||||
<ContentControl Grid.Column="2" Content="{Binding DisplayViewModel}" FontFamily="Consolas" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type dataModel:DataModelListItemViewModel}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<!-- Value description -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock FontWeight="Bold">[</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Text="{Binding ListType, Converter={StaticResource TypeToStringConverter}}" />
|
||||
<TextBlock FontWeight="Bold">]</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock>List item #</TextBlock>
|
||||
<TextBlock Text="{Binding Index, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Value display -->
|
||||
<ContentControl Grid.Column="2" Content="{Binding DisplayViewModel}" FontFamily="Consolas" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
|
||||
<TreeDataTemplate DataType="{x:Type dataModel:DataModelEventViewModel}" ItemsSource="{Binding Children}">
|
||||
<Grid ColumnDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0 0 5 0">
|
||||
<TextBlock FontWeight="Bold">[</TextBlock>
|
||||
<TextBlock FontWeight="Bold" Text="{Binding DisplayValueType, Converter={StaticResource TypeToStringConverter}, Mode=OneWay}" />
|
||||
<TextBlock FontWeight="Bold">]</TextBlock>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="{Binding PropertyDescription.Name}" ToolTip.Tip="{Binding PropertyDescription.Description}" />
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding CountDisplay, Mode=OneWay}"
|
||||
FontFamily="Consolas"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.DataTemplates>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.DataModel
|
||||
{
|
||||
public class DataModelDebugView : ReactiveUserControl<DataModelDebugViewModel>
|
||||
{
|
||||
public DataModelDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.DataModelVisualization.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.DataModel;
|
||||
|
||||
public class DataModelDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
private readonly IDataModelUIService _dataModelUIService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly DispatcherTimer _updateTimer;
|
||||
|
||||
private bool _isModuleFilterEnabled;
|
||||
private DataModelPropertiesViewModel? _mainDataModel;
|
||||
private string? _propertySearch;
|
||||
private Module? _selectedModule;
|
||||
private bool _slowUpdates;
|
||||
|
||||
public DataModelDebugViewModel(IScreen hostScreen, IDataModelUIService dataModelUIService, IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_dataModelUIService = dataModelUIService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(25), DispatcherPriority.Normal, (_, _) => Update());
|
||||
|
||||
HostScreen = hostScreen;
|
||||
Modules = new ObservableCollection<Module>();
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
|
||||
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
|
||||
.DisposeWith(disposables);
|
||||
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
|
||||
.Subscribe(d => PluginFeatureToggled(d.EventArgs.PluginFeature))
|
||||
.DisposeWith(disposables);
|
||||
|
||||
GetDataModel();
|
||||
_updateTimer.Start();
|
||||
Disposable.Create(() => _updateTimer.Stop()).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
public DataModelPropertiesViewModel? MainDataModel
|
||||
{
|
||||
get => _mainDataModel;
|
||||
set => RaiseAndSetIfChanged(ref _mainDataModel, value);
|
||||
}
|
||||
|
||||
public string? PropertySearch
|
||||
{
|
||||
get => _propertySearch;
|
||||
set => RaiseAndSetIfChanged(ref _propertySearch, value);
|
||||
}
|
||||
|
||||
public bool SlowUpdates
|
||||
{
|
||||
get => _slowUpdates;
|
||||
set
|
||||
{
|
||||
RaiseAndSetIfChanged(ref _slowUpdates, value);
|
||||
_updateTimer.Interval = TimeSpan.FromMilliseconds(_slowUpdates ? 500 : 25);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<Module> Modules { get; }
|
||||
|
||||
public Module? SelectedModule
|
||||
{
|
||||
get => _selectedModule;
|
||||
set
|
||||
{
|
||||
RaiseAndSetIfChanged(ref _selectedModule, value);
|
||||
GetDataModel();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsModuleFilterEnabled
|
||||
{
|
||||
get => _isModuleFilterEnabled;
|
||||
set
|
||||
{
|
||||
RaiseAndSetIfChanged(ref _isModuleFilterEnabled, value);
|
||||
|
||||
if (!IsModuleFilterEnabled)
|
||||
SelectedModule = null;
|
||||
else
|
||||
GetDataModel();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (MainDataModel == null)
|
||||
return;
|
||||
|
||||
lock (MainDataModel)
|
||||
{
|
||||
MainDataModel.Update(_dataModelUIService, new DataModelUpdateConfiguration(true));
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginFeatureToggled(PluginFeature pluginFeature)
|
||||
{
|
||||
if (pluginFeature is Module)
|
||||
PopulateModules();
|
||||
}
|
||||
|
||||
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 (MainDataModel == null)
|
||||
return;
|
||||
|
||||
if (SelectedModule == null)
|
||||
{
|
||||
if (MainDataModel != null)
|
||||
_dataModelUIService.UpdateModules(MainDataModel);
|
||||
}
|
||||
else if (!SelectedModule.IsEnabled)
|
||||
{
|
||||
SelectedModule = null;
|
||||
}
|
||||
}
|
||||
|
||||
public string UrlPathSegment => "data-model";
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
@@ -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.Debugger.Logs.LogsDebugView">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h3">Logs</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
On this page you can view Artemis's logs in real-time. Logging can come from Artemis itself, plugins and scripts.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Margin="0 20 0 0">TODO as there's no FlowDocumentScrollViewer in Avalonia and I'm too lazy to come up with an alternative.</TextBlock>
|
||||
<TextBlock Classes="subtitle">#feelsbadman</TextBlock>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Logs
|
||||
{
|
||||
public class LogsDebugView : ReactiveUserControl<LogsDebugViewModel>
|
||||
{
|
||||
public LogsDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Logs
|
||||
{
|
||||
public class LogsDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
public LogsDebugViewModel(IScreen hostScreen)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
}
|
||||
|
||||
public string UrlPathSegment => "logs";
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
}
|
||||
+19
-18
@@ -1,15 +1,16 @@
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public class PerformanceDebugMeasurementViewModel : PropertyChangedBase
|
||||
public class PerformanceDebugMeasurementViewModel : ViewModelBase
|
||||
{
|
||||
private string _average;
|
||||
private string _last;
|
||||
private string _max;
|
||||
private string _min;
|
||||
private string _percentile;
|
||||
private string? _average;
|
||||
private string? _last;
|
||||
private string? _max;
|
||||
private string? _min;
|
||||
private string? _percentile;
|
||||
|
||||
public PerformanceDebugMeasurementViewModel(ProfilingMeasurement measurement)
|
||||
{
|
||||
@@ -18,34 +19,34 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||
|
||||
public ProfilingMeasurement Measurement { get; }
|
||||
|
||||
public string Last
|
||||
public string? Last
|
||||
{
|
||||
get => _last;
|
||||
set => SetAndNotify(ref _last, value);
|
||||
set => RaiseAndSetIfChanged(ref _last, value);
|
||||
}
|
||||
|
||||
public string Average
|
||||
public string? Average
|
||||
{
|
||||
get => _average;
|
||||
set => SetAndNotify(ref _average, value);
|
||||
set => RaiseAndSetIfChanged(ref _average, value);
|
||||
}
|
||||
|
||||
public string Min
|
||||
public string? Min
|
||||
{
|
||||
get => _min;
|
||||
set => SetAndNotify(ref _min, value);
|
||||
set => RaiseAndSetIfChanged(ref _min, value);
|
||||
}
|
||||
|
||||
public string Max
|
||||
public string? Max
|
||||
{
|
||||
get => _max;
|
||||
set => SetAndNotify(ref _max, value);
|
||||
set => RaiseAndSetIfChanged(ref _max, value);
|
||||
}
|
||||
|
||||
public string Percentile
|
||||
public string? Percentile
|
||||
{
|
||||
get => _percentile;
|
||||
set => SetAndNotify(ref _percentile, value);
|
||||
set => RaiseAndSetIfChanged(ref _percentile, value);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
@@ -0,0 +1,18 @@
|
||||
<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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugPluginView">
|
||||
<Border Classes="card-condensed" Margin="0 5">
|
||||
<StackPanel>
|
||||
<Grid ColumnDefinitions="40,*">
|
||||
<controls:ArtemisIcon Grid.Column="0" Icon="{Binding Plugin.Info.ResolvedIcon}" Width="24" Height="24" />
|
||||
<TextBlock Grid.Column="1" VerticalAlignment="Center" Classes="h5" Text="{Binding Plugin.Info.Name}" />
|
||||
</Grid>
|
||||
|
||||
<ItemsControl Items="{Binding Profilers}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public partial class PerformanceDebugPluginView : UserControl
|
||||
{
|
||||
public PerformanceDebugPluginView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -1,10 +1,11 @@
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public class PerformanceDebugPluginViewModel : Screen
|
||||
public class PerformanceDebugPluginViewModel : ViewModelBase
|
||||
{
|
||||
public PerformanceDebugPluginViewModel(Plugin plugin)
|
||||
{
|
||||
@@ -13,13 +14,15 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||
|
||||
public Plugin Plugin { get; }
|
||||
|
||||
public BindableCollection<PerformanceDebugProfilerViewModel> Profilers { get; } = new();
|
||||
public ObservableCollection<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();
|
||||
@@ -0,0 +1,29 @@
|
||||
<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:local="clr-namespace:Artemis.UI.Screens.Debugger.Performance"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugProfilerView">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="subtitle" Text="{Binding Profiler.Name}" Margin="10 10 0 0" />
|
||||
|
||||
<DataGrid Items="{Binding Measurements}"
|
||||
d:DataContext="{d:DesignInstance Type={x:Type local:PerformanceDebugMeasurementViewModel}}"
|
||||
CanUserSortColumns="True"
|
||||
IsReadOnly="True"
|
||||
AutoGenerateColumns="False"
|
||||
CanUserReorderColumns="False"
|
||||
CanUserResizeColumns="False"
|
||||
Margin="10 5 10 10">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Binding="{Binding Measurement.Identifier}" Header="Identifier" />
|
||||
<DataGridTextColumn Binding="{Binding Last}" Header="Last" />
|
||||
<DataGridTextColumn Binding="{Binding Min}" Header="Min" />
|
||||
<DataGridTextColumn Binding="{Binding Max}" Header="Max" />
|
||||
<DataGridTextColumn Binding="{Binding Average}" Header="Average" />
|
||||
<DataGridTextColumn Binding="{Binding Percentile}" Header="95th percentile" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public partial class PerformanceDebugProfilerView : UserControl
|
||||
{
|
||||
public PerformanceDebugProfilerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-8
@@ -1,19 +1,20 @@
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Debug.Tabs.Performance
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public class PerformanceDebugProfilerViewModel : Screen
|
||||
public class PerformanceDebugProfilerViewModel : ViewModelBase
|
||||
{
|
||||
public Profiler Profiler { get; }
|
||||
|
||||
public PerformanceDebugProfilerViewModel(Profiler profiler)
|
||||
{
|
||||
Profiler = profiler;
|
||||
}
|
||||
|
||||
public BindableCollection<PerformanceDebugMeasurementViewModel> Measurements { get; } = new();
|
||||
|
||||
public Profiler Profiler { get; }
|
||||
|
||||
public ObservableCollection<PerformanceDebugMeasurementViewModel> Measurements { get; } = new();
|
||||
|
||||
public void Update()
|
||||
{
|
||||
@@ -0,0 +1,41 @@
|
||||
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Debugger.Performance.PerformanceDebugView">
|
||||
<Grid RowDefinitions="Auto,Auto,*">
|
||||
<StackPanel Grid.Row="0">
|
||||
<TextBlock Classes="h3">Performance</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
On this page 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>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
|
||||
These performance stats are rather basic, for advanced performance profiling check out the wiki.
|
||||
</TextBlock>
|
||||
<controls:HyperlinkButton Grid.Column="1" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins/profiling">
|
||||
JetBrains Profiling Guide
|
||||
</controls:HyperlinkButton>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="10 0">
|
||||
<TextBlock Text="FPS: " />
|
||||
<TextBlock FontWeight="Bold" Text="{Binding CurrentFps}" />
|
||||
<TextBlock Text=" at " />
|
||||
<TextBlock Text="{Binding RenderWidth}" />
|
||||
<TextBlock Text="x" />
|
||||
<TextBlock Text="{Binding RenderHeight}" />
|
||||
<TextBlock Text=" - Renderer: "></TextBlock>
|
||||
<TextBlock Text="{Binding Renderer}"></TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsControl Items="{Binding Items}" Margin="0 0 10 0" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Performance
|
||||
{
|
||||
public class PerformanceDebugView : ReactiveUserControl<PerformanceDebugViewModel>
|
||||
{
|
||||
public PerformanceDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Performance;
|
||||
|
||||
public class PerformanceDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly DispatcherTimer _updateTimer;
|
||||
private double _currentFps;
|
||||
private string? _renderer;
|
||||
private int _renderHeight;
|
||||
private int _renderWidth;
|
||||
|
||||
public PerformanceDebugViewModel(IScreen hostScreen, ICoreService coreService, IPluginManagementService pluginManagementService)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
_coreService = coreService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Normal, (_, _) => Update());
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureEnabled += x, x => pluginManagementService.PluginFeatureEnabled -= x)
|
||||
.Subscribe(_ => Repopulate())
|
||||
.DisposeWith(disposables);
|
||||
Observable.FromEventPattern<PluginFeatureEventArgs>(x => pluginManagementService.PluginFeatureDisabled += x, x => pluginManagementService.PluginFeatureDisabled -= x)
|
||||
.Subscribe(_ => Repopulate())
|
||||
.DisposeWith(disposables);
|
||||
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginEnabled += x, x => pluginManagementService.PluginEnabled -= x)
|
||||
.Subscribe(_ => Repopulate())
|
||||
.DisposeWith(disposables);
|
||||
Observable.FromEventPattern<PluginEventArgs>(x => pluginManagementService.PluginDisabled += x, x => pluginManagementService.PluginDisabled -= x)
|
||||
.Subscribe(_ => Repopulate())
|
||||
.DisposeWith(disposables);
|
||||
|
||||
HandleActivation();
|
||||
PopulateItems();
|
||||
_updateTimer.Start();
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_updateTimer.Stop();
|
||||
Items.Clear();
|
||||
HandleDeactivation();
|
||||
}).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
public ObservableCollection<PerformanceDebugPluginViewModel> Items { get; } = new();
|
||||
|
||||
public double CurrentFps
|
||||
{
|
||||
get => _currentFps;
|
||||
set => RaiseAndSetIfChanged(ref _currentFps, value);
|
||||
}
|
||||
|
||||
public int RenderWidth
|
||||
{
|
||||
get => _renderWidth;
|
||||
set => RaiseAndSetIfChanged(ref _renderWidth, value);
|
||||
}
|
||||
|
||||
public int RenderHeight
|
||||
{
|
||||
get => _renderHeight;
|
||||
set => RaiseAndSetIfChanged(ref _renderHeight, value);
|
||||
}
|
||||
|
||||
public string? Renderer
|
||||
{
|
||||
get => _renderer;
|
||||
set => RaiseAndSetIfChanged(ref _renderer, value);
|
||||
}
|
||||
|
||||
private void HandleActivation()
|
||||
{
|
||||
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
|
||||
_coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||
}
|
||||
|
||||
private void HandleDeactivation()
|
||||
{
|
||||
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
|
||||
}
|
||||
|
||||
private void PopulateItems()
|
||||
{
|
||||
foreach (PerformanceDebugPluginViewModel performanceDebugPluginViewModel in _pluginManagementService.GetAllPlugins()
|
||||
.Where(p => p.IsEnabled && p.Profilers.Any(pr => pr.Measurements.Any()))
|
||||
.OrderBy(p => p.Info.Name)
|
||||
.Select(p => new PerformanceDebugPluginViewModel(p)))
|
||||
Items.Add(performanceDebugPluginViewModel);
|
||||
}
|
||||
|
||||
private void Repopulate()
|
||||
{
|
||||
Items.Clear();
|
||||
PopulateItems();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
foreach (PerformanceDebugPluginViewModel viewModel in Items)
|
||||
viewModel.Update();
|
||||
}
|
||||
|
||||
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||
{
|
||||
CurrentFps = _coreService.FrameRate;
|
||||
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
|
||||
|
||||
RenderHeight = bitmapInfo.Height;
|
||||
RenderWidth = bitmapInfo.Width;
|
||||
}
|
||||
|
||||
|
||||
public string UrlPathSegment => "performance";
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<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.Debugger.Render.RenderDebugView">
|
||||
<StackPanel>
|
||||
<TextBlock Classes="h3">Render</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
On this page you can view what Artemis renders to devices in real time. Artemis will overlay this image on your devices, taking the average of each pixel covering a LED, resulting the image appearing on your devices.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Margin="0 10">
|
||||
Please note that having this window open can have a performance impact on your system.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="FPS: " />
|
||||
<TextBlock FontWeight="Bold" Text="{Binding CurrentFps}" />
|
||||
<TextBlock Text=" at " />
|
||||
<TextBlock Text="{Binding RenderWidth}" />
|
||||
<TextBlock Text="x" />
|
||||
<TextBlock Text="{Binding RenderHeight}" />
|
||||
<TextBlock Text=" - Renderer: "></TextBlock>
|
||||
<TextBlock Text="{Binding Renderer}"></TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<Border Classes="card" Padding="10">
|
||||
<ZoomBorder Name="ZoomBorder"
|
||||
Stretch="None"
|
||||
ClipToBounds="True"
|
||||
Focusable="True"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Image Name="Visualization" Source="{Binding CurrentFrame}">
|
||||
<Image.Transitions>
|
||||
<Transitions>
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.2" Easing="CubicEaseOut"/>
|
||||
</Transitions>
|
||||
</Image.Transitions>
|
||||
</Image>
|
||||
</ZoomBorder>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Render
|
||||
{
|
||||
public class RenderDebugView : ReactiveUserControl<RenderDebugViewModel>
|
||||
{
|
||||
public RenderDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Avalonia.Media.Imaging;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Render
|
||||
{
|
||||
public class RenderDebugViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private double _currentFps;
|
||||
|
||||
private Bitmap? _currentFrame;
|
||||
private string? _frameTargetPath;
|
||||
private string? _renderer;
|
||||
private int _renderHeight;
|
||||
private int _renderWidth;
|
||||
|
||||
public RenderDebugViewModel(DebugViewModel hostScreen, ICoreService coreService)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
_coreService = coreService;
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
HandleActivation();
|
||||
Disposable.Create(HandleDeactivation).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
public Bitmap? CurrentFrame
|
||||
{
|
||||
get => _currentFrame;
|
||||
set => RaiseAndSetIfChanged(ref _currentFrame, value);
|
||||
}
|
||||
|
||||
public double CurrentFps
|
||||
{
|
||||
get => _currentFps;
|
||||
set => RaiseAndSetIfChanged(ref _currentFps, value);
|
||||
}
|
||||
|
||||
public int RenderWidth
|
||||
{
|
||||
get => _renderWidth;
|
||||
set => RaiseAndSetIfChanged(ref _renderWidth, value);
|
||||
}
|
||||
|
||||
public int RenderHeight
|
||||
{
|
||||
get => _renderHeight;
|
||||
set => RaiseAndSetIfChanged(ref _renderHeight, value);
|
||||
}
|
||||
|
||||
public string? Renderer
|
||||
{
|
||||
get => _renderer;
|
||||
set => RaiseAndSetIfChanged(ref _renderer, value);
|
||||
}
|
||||
|
||||
private void HandleActivation()
|
||||
{
|
||||
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
|
||||
_coreService.FrameRendered += CoreServiceOnFrameRendered;
|
||||
}
|
||||
|
||||
private void HandleDeactivation()
|
||||
{
|
||||
_coreService.FrameRendered -= CoreServiceOnFrameRendered;
|
||||
}
|
||||
|
||||
private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
|
||||
{
|
||||
CurrentFps = _coreService.FrameRate;
|
||||
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;
|
||||
|
||||
// TODO: This performs well enough but look into something else
|
||||
using (SKData data = skImage.Encode(SKEncodedImageFormat.Png, 100))
|
||||
{
|
||||
CurrentFrame = new Bitmap(data.AsStream());
|
||||
}
|
||||
}
|
||||
|
||||
public string UrlPathSegment => "render";
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<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.Debugger.Settings.DebugSettingsView">
|
||||
<TextBlock>Settings</TextBlock>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Settings
|
||||
{
|
||||
public class DebugSettingsView : ReactiveUserControl<DebugSettingsViewModel>
|
||||
{
|
||||
public DebugSettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Debugger.Settings
|
||||
{
|
||||
public class DebugSettingsViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
public DebugSettingsViewModel(IScreen hostScreen)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
}
|
||||
|
||||
public string UrlPathSegment => "logs";
|
||||
public IScreen HostScreen { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="1050"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceDetectInputView">
|
||||
<StackPanel Width="500">
|
||||
<!-- TODO: Replace with Text.Run stuff once available -->
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Press a button/key on your device that is currently showing a yellow color.
|
||||
</TextBlock>
|
||||
|
||||
<avalonia:MaterialIcon Kind="Keyboard"
|
||||
Width="300"
|
||||
Height="300"
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding !IsMouse}" />
|
||||
<avalonia:MaterialIcon Kind="Mouse"
|
||||
Width="300"
|
||||
Height="300"
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding IsMouse}" />
|
||||
|
||||
<TextBlock TextWrapping="Wrap" Margin="0 10">
|
||||
This will teach Artemis to associate button/key presses with this specific device.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DeviceDetectInputView : ReactiveUserControl<DeviceDetectInputViewModel>
|
||||
{
|
||||
public DeviceDetectInputView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DeviceDetectInputViewModel : ContentDialogViewModelBase
|
||||
{
|
||||
private readonly IInputService _inputService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly ListLedGroup _ledGroup;
|
||||
|
||||
public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRgbService rgbService)
|
||||
{
|
||||
_inputService = inputService;
|
||||
_notificationService = notificationService;
|
||||
_rgbService = rgbService;
|
||||
|
||||
Device = device;
|
||||
|
||||
// Create a LED group way at the top
|
||||
_ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
|
||||
{
|
||||
Brush = new SolidColorBrush(new Color(255, 255, 0)),
|
||||
ZIndex = 999
|
||||
};
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Observable.FromEventPattern(x => _inputService.DeviceIdentified += x, x => _inputService.DeviceIdentified -= x)
|
||||
.Subscribe(_ => InputServiceOnDeviceIdentified())
|
||||
.DisposeWith(disposables);
|
||||
|
||||
Disposable.Create(() => _ledGroup.Detach()).DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public bool IsMouse => Device.RgbDevice.DeviceInfo.DeviceType == RGBDeviceType.Mouse;
|
||||
|
||||
public bool MadeChanges { get; set; }
|
||||
|
||||
private void InputServiceOnDeviceIdentified()
|
||||
{
|
||||
_notificationService.CreateNotification()
|
||||
.WithMessage($"{Device.RgbDevice.DeviceInfo.DeviceName} identified 😁")
|
||||
.WithSeverity(NotificationSeverity.Success)
|
||||
.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<controls1:CoreWindow 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:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="800"
|
||||
x:Class="Artemis.UI.Screens.Device.DevicePropertiesView"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis | Device Properties"
|
||||
Width="1250"
|
||||
Height="900">
|
||||
|
||||
<Grid ColumnDefinitions="*,0,1.5*">
|
||||
<Grid.Background>
|
||||
<VisualBrush TileMode="Tile" Stretch="Uniform" DestinationRect="0,0,25,25">
|
||||
<VisualBrush.Visual>
|
||||
<Grid Width="25" Height="25" RowDefinitions="*,*" ColumnDefinitions="*,*">
|
||||
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Black" Opacity="0.15" />
|
||||
<Rectangle Grid.Row="0" Grid.Column="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>
|
||||
<Grid Grid.Column="0" Name="DeviceDisplayGrid">
|
||||
<!-- No need to provide LEDs to highlight as LEDs are already physically highlighted -->
|
||||
<controls:DeviceVisualizer Device="{Binding Device}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ShowColors="True"
|
||||
Margin="20" />
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="15"
|
||||
IsVisible="{Binding Device.Layout.RgbLayout.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<TextBlock Classes="h5" Text="Device layout by " />
|
||||
<TextBlock Classes="h5" FontWeight="Bold" Text="{Binding Device.Layout.RgbLayout.Author}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="15" Margin="-15 0 0 0" Background="Transparent" HorizontalAlignment="Stretch" />
|
||||
|
||||
<Border Grid.Column="2" Classes="card" CornerRadius="10 0 0 0" Margin="0 10 0 0" Background="#ff323232">
|
||||
<TabControl Items="{Binding Tabs}">
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<ContentControl Content="{Binding}" Margin="0 10 0 0" />
|
||||
</ScrollViewer>
|
||||
</DataTemplate>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
</Border>
|
||||
|
||||
<StackPanel Grid.Column="0" Grid.ColumnSpan="3" Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
|
||||
</controls1:CoreWindow>
|
||||
@@ -0,0 +1,22 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class DevicePropertiesView : ReactiveCoreWindow<DevicePropertiesViewModel>
|
||||
{
|
||||
public DevicePropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using RGB.NET.Core;
|
||||
using ArtemisLed = Artemis.Core.ArtemisLed;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DevicePropertiesViewModel : DialogViewModelBase<object>
|
||||
{
|
||||
public DevicePropertiesViewModel(ArtemisDevice device, IDeviceVmFactory deviceVmFactory)
|
||||
{
|
||||
Device = device;
|
||||
SelectedLeds = new ObservableCollection<ArtemisLed>();
|
||||
Tabs = new ObservableCollection<ActivatableViewModelBase>();
|
||||
|
||||
Tabs.Add(deviceVmFactory.DevicePropertiesTabViewModel(device));
|
||||
Tabs.Add(deviceVmFactory.DeviceInfoTabViewModel(device));
|
||||
if (Device .DeviceType == RGBDeviceType.Keyboard)
|
||||
Tabs.Add(deviceVmFactory.InputMappingsTabViewModel(device, SelectedLeds));
|
||||
Tabs.Add(deviceVmFactory.DeviceLedsTabViewModel(device, SelectedLeds));
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public ObservableCollection<ArtemisLed> SelectedLeds { get; }
|
||||
public ObservableCollection<ActivatableViewModelBase> Tabs { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceSettingsView">
|
||||
<Border Classes="card" Padding="0" Width="200" ClipToBounds="True" Margin="5">
|
||||
<Grid RowDefinitions="140,*,Auto">
|
||||
<Rectangle Grid.Row="0" Fill="{DynamicResource CheckerboardBrush}"/>
|
||||
<controls1:DeviceVisualizer VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="5"
|
||||
ShowColors="False"
|
||||
Device="{Binding Device}"
|
||||
Grid.Row="0" />
|
||||
<Button Grid.Row="0"
|
||||
Classes="icon-button icon-button-large"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="5"
|
||||
ToolTip.Tip="Identify"
|
||||
Command="{Binding IdentifyDevice}">
|
||||
<avalonia:MaterialIcon Kind="AlarmLight" />
|
||||
</Button>
|
||||
<StackPanel Grid.Row="1" Margin="8 24 8 0">
|
||||
<TextBlock Text="{Binding Device.RgbDevice.DeviceInfo.Model}" />
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer, Mode=OneWay}" />
|
||||
<TextBlock TextWrapping="Wrap" Classes="subtitle" Text="{Binding Device.DeviceType, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="2" Margin="10" ColumnDefinitions="25,*">
|
||||
<CheckBox IsChecked="{Binding IsDeviceEnabled}" />
|
||||
|
||||
<controls:SplitButton Grid.Column="1" Content="Properties" Command="{Binding ViewProperties}" HorizontalAlignment="Right">
|
||||
<controls:SplitButton.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuItem Header="Open plugin directory" Command="{Binding OpenPluginDirectory}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="FolderOpen" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Identity input" Command="{Binding DetectInput}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="GestureDoubleTap" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</controls:SplitButton.Flyout>
|
||||
</controls:SplitButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class DeviceSettingsView : UserControl
|
||||
{
|
||||
public DeviceSettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Reactive;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Settings;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia.Threading;
|
||||
using Humanizer;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DeviceSettingsViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly DevicesTabViewModel _devicesTabViewModel;
|
||||
private readonly IDeviceVmFactory _deviceVmFactory;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IWindowService _windowService;
|
||||
private bool _togglingDevice;
|
||||
|
||||
public DeviceSettingsViewModel(ArtemisDevice device, DevicesTabViewModel devicesTabViewModel, IDeviceService deviceService, IWindowService windowService, IDeviceVmFactory deviceVmFactory,
|
||||
IRgbService rgbService)
|
||||
{
|
||||
_devicesTabViewModel = devicesTabViewModel;
|
||||
_deviceService = deviceService;
|
||||
_windowService = windowService;
|
||||
_deviceVmFactory = deviceVmFactory;
|
||||
_rgbService = rgbService;
|
||||
Device = device;
|
||||
|
||||
Type = Device.DeviceType.ToString().Humanize();
|
||||
Name = Device.RgbDevice.DeviceInfo.Model;
|
||||
Manufacturer = Device.RgbDevice.DeviceInfo.Manufacturer;
|
||||
|
||||
DetectInput = ReactiveCommand.CreateFromTask(ExecuteDetectInput, this.WhenAnyValue(vm => vm.CanDetectInput));
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public string Type { get; }
|
||||
public string Name { get; }
|
||||
public string Manufacturer { get; }
|
||||
|
||||
public bool CanDetectInput => Device.DeviceType is RGBDeviceType.Keyboard or RGBDeviceType.Mouse;
|
||||
public ReactiveCommand<Unit, Unit> DetectInput { get; }
|
||||
|
||||
public bool IsDeviceEnabled
|
||||
{
|
||||
get => Device.IsEnabled;
|
||||
set => Dispatcher.UIThread.InvokeAsync(async () => await UpdateIsDeviceEnabled(value));
|
||||
}
|
||||
|
||||
public bool TogglingDevice
|
||||
{
|
||||
get => _togglingDevice;
|
||||
set => RaiseAndSetIfChanged(ref _togglingDevice, value);
|
||||
}
|
||||
|
||||
public void IdentifyDevice()
|
||||
{
|
||||
_deviceService.IdentifyDevice(Device);
|
||||
}
|
||||
|
||||
public void OpenPluginDirectory()
|
||||
{
|
||||
Utilities.OpenFolder(Device.DeviceProvider.Plugin.Directory.FullName);
|
||||
}
|
||||
|
||||
private async Task ExecuteDetectInput()
|
||||
{
|
||||
if (!CanDetectInput)
|
||||
return;
|
||||
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle($"{Device.RgbDevice.DeviceInfo.DeviceName} - Detect input")
|
||||
.WithViewModel<DeviceDetectInputViewModel>(out var viewModel, ("device", Device))
|
||||
.WithCloseButtonText("Cancel")
|
||||
.ShowAsync();
|
||||
|
||||
if (viewModel.MadeChanges)
|
||||
_rgbService.SaveDevice(Device);
|
||||
}
|
||||
|
||||
public async Task ViewProperties()
|
||||
{
|
||||
await _windowService.ShowDialogAsync(_deviceVmFactory.DevicePropertiesViewModel(Device));
|
||||
}
|
||||
|
||||
private async Task UpdateIsDeviceEnabled(bool value)
|
||||
{
|
||||
if (TogglingDevice)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
TogglingDevice = true;
|
||||
|
||||
if (!value)
|
||||
value = !await _devicesTabViewModel.ShowDeviceDisableDialog();
|
||||
|
||||
if (value)
|
||||
_rgbService.EnableDevice(Device);
|
||||
else
|
||||
_rgbService.DisableDevice(Device);
|
||||
|
||||
this.RaisePropertyChanged(nameof(IsDeviceEnabled));
|
||||
SaveDevice();
|
||||
}
|
||||
finally
|
||||
{
|
||||
TogglingDevice = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveDevice()
|
||||
{
|
||||
_rgbService.SaveDevice(Device);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Device.DeviceInfoTabView">
|
||||
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,*" Margin="-5">
|
||||
<!-- First row -->
|
||||
<Border Classes="card" Grid.Column="0" Grid.Row="0" Margin="5">
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
|
||||
<TextBlock FontWeight="Bold">Device name</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.RgbDevice.DeviceInfo.DeviceName}" />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
<TextBlock FontWeight="Bold">Manufacturer</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.RgbDevice.DeviceInfo.Manufacturer}" />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
|
||||
<TextBlock FontWeight="Bold">Device type</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.DeviceType}" />
|
||||
<Separator Classes="card-separator" IsVisible="{Binding IsKeyboard}" />
|
||||
|
||||
<StackPanel IsVisible="{Binding IsKeyboard}">
|
||||
<TextBlock FontWeight="Bold">Physical layout</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.PhysicalLayout}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Classes="card" Grid.Row="0" Grid.Column="1" Margin="5">
|
||||
<StackPanel VerticalAlignment="Top">
|
||||
<TextBlock FontWeight="Bold">Size (1px = 1mm)</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.RgbDevice.Size}" />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
<TextBlock FontWeight="Bold">Location (1px = 1mm)</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.RgbDevice.Location}" />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
<TextBlock FontWeight="Bold">Rotation (degrees)</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.RgbDevice.Rotation.Degrees}" />
|
||||
<Separator Classes="card-separator" IsVisible="{Binding IsKeyboard}" />
|
||||
|
||||
<StackPanel IsVisible="{Binding IsKeyboard}">
|
||||
<TextBlock FontWeight="Bold">Logical layout</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.LogicalLayout}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Second row -->
|
||||
<Border Classes="card" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="5">
|
||||
<StackPanel>
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock FontWeight="Bold">Default layout file path</TextBlock>
|
||||
<Button Grid.Column="1"
|
||||
Classes="icon-button"
|
||||
ToolTip.Tip="Copy path to clipboard"
|
||||
Command="{Binding CopyToClipboard}"
|
||||
CommandParameter="{Binding DefaultLayoutPath}">
|
||||
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding DefaultLayoutPath}" />
|
||||
<Separator Classes="card-separator" />
|
||||
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBlock FontWeight="Bold">Image file path</TextBlock>
|
||||
<Button Grid.Column="1"
|
||||
Classes="icon-button"
|
||||
ToolTip.Tip="Copy path to clipboard"
|
||||
Command="{Binding CopyToClipboard}"
|
||||
CommandParameter="{Binding Device.Layout.Image.LocalPath}">
|
||||
<avalonia:MaterialIcon Kind="ContentCopy" Width="18" Height="18" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Device.Layout.Image.LocalPath}" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class DeviceInfoTabView : UserControl
|
||||
{
|
||||
public DeviceInfoTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DeviceInfoTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public DeviceInfoTabViewModel(ArtemisDevice device, INotificationService notificationService)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
|
||||
Device = device;
|
||||
DisplayName = "Info";
|
||||
|
||||
DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath;
|
||||
}
|
||||
|
||||
public bool IsKeyboard => Device.DeviceType == RGBDeviceType.Keyboard;
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public string DefaultLayoutPath { get; }
|
||||
|
||||
public async Task CopyToClipboard(string content)
|
||||
{
|
||||
if (Application.Current?.Clipboard == null)
|
||||
return;
|
||||
|
||||
await Application.Current.Clipboard.SetTextAsync(content);
|
||||
_notificationService.CreateNotification().WithMessage("Copied path to clipboard.").Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<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.Device.DeviceLedsTabView">
|
||||
Welcome to Avalonia!
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class DeviceLedsTabView : UserControl
|
||||
{
|
||||
public DeviceLedsTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
using DynamicData.Binding;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DeviceLedsTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
|
||||
|
||||
public DeviceLedsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds)
|
||||
{
|
||||
_selectedLeds = selectedLeds;
|
||||
|
||||
Device = device;
|
||||
DisplayName = "LEDs";
|
||||
LedViewModels = new ObservableCollection<DeviceLedsTabLedViewModel>(Device.Leds.Select(l => new DeviceLedsTabLedViewModel(l, _selectedLeds)));
|
||||
|
||||
this.WhenActivated(disposables => _selectedLeds.ToObservableChangeSet().Subscribe(_ => UpdateSelectedLeds()).DisposeWith(disposables));
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
public ObservableCollection<DeviceLedsTabLedViewModel> LedViewModels { get; }
|
||||
|
||||
private void SelectedLedsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateSelectedLeds();
|
||||
}
|
||||
|
||||
private void UpdateSelectedLeds()
|
||||
{
|
||||
foreach (DeviceLedsTabLedViewModel deviceLedsTabLedViewModel in LedViewModels)
|
||||
deviceLedsTabLedViewModel.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceLedsTabLedViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
|
||||
private bool _isSelected;
|
||||
|
||||
public DeviceLedsTabLedViewModel(ArtemisLed artemisLed, ObservableCollection<ArtemisLed> selectedLeds)
|
||||
{
|
||||
_selectedLeds = selectedLeds;
|
||||
ArtemisLed = artemisLed;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public ArtemisLed ArtemisLed { get; }
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (!RaiseAndSetIfChanged(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
|
||||
xmlns:device="clr-namespace:Artemis.UI.Screens.Device"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1200"
|
||||
x:Class="Artemis.UI.Screens.Device.DevicePropertiesTabView">
|
||||
<UserControl.Resources>
|
||||
<converters:SKColorToColorConverter x:Key="SKColorToColorConverter" />
|
||||
</UserControl.Resources>
|
||||
<Design.DataContext>
|
||||
<device:DevicePropertiesTabViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<!-- Body -->
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
<StackPanel Grid.Row="0">
|
||||
<!-- Layout -->
|
||||
<TextBlock Classes="h4">
|
||||
Categories
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Artemis uses categories to determine where the layers of imported profiles are applied to.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
You can hover over a category for a more detailed description.
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox IsChecked="{Binding HasDeskCategory}"
|
||||
ToolTip.Tip="A device acting as desk ornamentation such as a LED strip"
|
||||
Content="Desk" />
|
||||
<CheckBox IsChecked="{Binding HasMonitorCategory}"
|
||||
ToolTip.Tip="A device attached to the monitor such as ambilight LEDs"
|
||||
Content="Monitor" />
|
||||
<CheckBox ToolTip.Tip="A device inside your computer case"
|
||||
IsChecked="{Binding HasCaseCategory}"
|
||||
Content="Case" />
|
||||
<CheckBox IsChecked="{Binding HasRoomCategory}"
|
||||
ToolTip.Tip="A device elsewhere in the room"
|
||||
Content="Room" />
|
||||
<CheckBox IsChecked="{Binding HasPeripheralsCategory}"
|
||||
ToolTip.Tip="A peripheral such as a mouse or keyboard"
|
||||
Content="Peripheral" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Margin="0 25" ColumnDefinitions="*,40,*">
|
||||
<Grid Grid.Column="0" ColumnDefinitions="*,Auto,Auto" RowDefinitions="*,*,*,*,*">
|
||||
<TextBlock Grid.Row="0" Grid.ColumnSpan="3" Classes="h4">
|
||||
Surface properties
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center">X-coordinate</TextBlock>
|
||||
<NumericUpDown Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="10 0"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding X}" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center">Y-coordinate</TextBlock>
|
||||
<NumericUpDown Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="10 0"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding Y}" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="2" VerticalAlignment="Center">mm</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center">Scale</TextBlock>
|
||||
<NumericUpDown Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Margin="10 0"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding Scale}" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="2" VerticalAlignment="Center">times</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center">Rotation</TextBlock>
|
||||
<NumericUpDown Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="10 0"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding Rotation}" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="2" VerticalAlignment="Center">deg</TextBlock>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Rectangle Grid.Column="1" VerticalAlignment="Stretch" Width="1" Fill="{DynamicResource ButtonBorderBrush}" Margin="0 0 0 5" />
|
||||
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Classes="h4">
|
||||
Color calibration
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
Use the sliders below to adjust the colors of your device so that it matches your other devices.
|
||||
</TextBlock>
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="*,*,*,*">
|
||||
<Label Grid.Row="0" Grid.Column="0" Content="R" VerticalAlignment="Center" />
|
||||
<Slider Grid.Row="0" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding RedScale}" Margin="10 0" VerticalAlignment="Center" />
|
||||
<NumericUpDown Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Width="65"
|
||||
Value="{Binding RedScale}"
|
||||
ShowButtonSpinner="False"
|
||||
FormatString="{}{0:0.0}"
|
||||
Minimum="0"
|
||||
Maximum="200"
|
||||
ClipValueToMinMax="True" />
|
||||
|
||||
<Label Grid.Row="1" Grid.Column="0" Content="G" VerticalAlignment="Center" />
|
||||
<Slider Grid.Row="1" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding GreenScale}" Margin="10 0" VerticalAlignment="Center" />
|
||||
<NumericUpDown Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Width="65"
|
||||
Value="{Binding GreenScale}"
|
||||
ShowButtonSpinner="False"
|
||||
FormatString="{}{0:0.0}"
|
||||
Minimum="0"
|
||||
Maximum="200"
|
||||
ClipValueToMinMax="True" />
|
||||
|
||||
<Label Grid.Row="2" Grid.Column="0" Content="B" VerticalAlignment="Center" />
|
||||
<Slider Grid.Row="2" Grid.Column="1" Minimum="0" Maximum="200" Value="{Binding BlueScale}" Margin="10 0" Ticks="100" VerticalAlignment="Center" />
|
||||
<NumericUpDown Grid.Row="2"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
Width="65"
|
||||
Value="{Binding BlueScale}"
|
||||
ShowButtonSpinner="False"
|
||||
FormatString="{}{0:0.0}"
|
||||
Minimum="0"
|
||||
Maximum="200"
|
||||
ClipValueToMinMax="True" />
|
||||
|
||||
<CheckBox Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" IsChecked="{Binding DisplayOnDevices}" Content="Show preview" VerticalAlignment="Center" />
|
||||
<controls:ColorPickerButton Grid.Row="3" Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Right" Color="{Binding CurrentColor, Converter={StaticResource SKColorToColorConverter}}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Layout -->
|
||||
<TextBlock Classes="h4">
|
||||
Layout
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
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" IsChecked="{Binding Device.DisableDefaultLayout}">
|
||||
<CheckBox.Content>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock>Don't load default layout</TextBlock>
|
||||
<avalonia:MaterialIcon Kind="HelpCircle"
|
||||
Margin="5 0"
|
||||
ToolTip.Tip="With this enabled Artemis will not load a layout for this device unless you specifically provide one." />
|
||||
</StackPanel>
|
||||
</CheckBox.Content>
|
||||
</CheckBox>
|
||||
|
||||
<Grid>
|
||||
<TextBox Text="{Binding Device.CustomLayoutPath}"
|
||||
Watermark="Custom layout path"
|
||||
UseFloatingWatermark="True"
|
||||
IsReadOnly="True"
|
||||
PointerReleased="InputElement_OnPointerReleased"
|
||||
Padding="10 5 36 6"/>
|
||||
<Button Classes="AppBarButton" HorizontalAlignment="Right" Command="{Binding ClearCustomLayout}" Padding="6" Margin="5 0">
|
||||
<avalonia:MaterialIcon Kind="CloseCircle" />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<controls:HyperlinkButton NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts" Margin="0 20" HorizontalAlignment="Right">
|
||||
Learn more about layouts on the wiki
|
||||
</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Buttons -->
|
||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
|
||||
<Button Grid.Column="0"
|
||||
Classes="AppBarButton"
|
||||
Command="{Binding SelectPhysicalLayout}"
|
||||
ToolTip.Tip="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 Classes="AppBarButton" IsCancel="True" Command="{Binding Reset}" Margin="0 0 5 0">Reset</Button>
|
||||
<Button IsDefault="True" Command="{Binding Apply}">Apply</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class DevicePropertiesTabView : ReactiveUserControl<DevicePropertiesTabViewModel>
|
||||
{
|
||||
public DevicePropertiesTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void InputElement_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
|
||||
{
|
||||
ViewModel?.BrowseCustomLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
-95
@@ -1,49 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Disposables;
|
||||
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 Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class DevicePropertiesTabViewModel : Screen
|
||||
public class DevicePropertiesTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly List<DeviceCategory> _categories;
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly float _initialBlueScale;
|
||||
private readonly float _initialGreenScale;
|
||||
private readonly float _initialRedScale;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IWindowService _windowService;
|
||||
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)
|
||||
#pragma warning disable CS8618 // Design-time constructor
|
||||
public DevicePropertiesTabViewModel()
|
||||
{
|
||||
}
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public DevicePropertiesTabViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IWindowService windowService, INotificationService notificationService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_rgbService = rgbService;
|
||||
_dialogService = dialogService;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_categories = new List<DeviceCategory>(device.Categories);
|
||||
|
||||
Device = device;
|
||||
DisplayName = "PROPERTIES";
|
||||
DisplayName = "Properties";
|
||||
|
||||
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;
|
||||
CurrentColor = SKColors.White;
|
||||
|
||||
// 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;
|
||||
|
||||
this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling());
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Device.PropertyChanged += DeviceOnPropertyChanged;
|
||||
_coreService.FrameRendering += OnFrameRendering;
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_coreService.FrameRendering -= OnFrameRendering;
|
||||
Device.PropertyChanged -= DeviceOnPropertyChanged;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
@@ -51,55 +84,55 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
public int X
|
||||
{
|
||||
get => _x;
|
||||
set => SetAndNotify(ref _x, value);
|
||||
set => RaiseAndSetIfChanged(ref _x, value);
|
||||
}
|
||||
|
||||
public int Y
|
||||
{
|
||||
get => _y;
|
||||
set => SetAndNotify(ref _y, value);
|
||||
set => RaiseAndSetIfChanged(ref _y, value);
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get => _scale;
|
||||
set => SetAndNotify(ref _scale, value);
|
||||
set => RaiseAndSetIfChanged(ref _scale, value);
|
||||
}
|
||||
|
||||
public int Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set => SetAndNotify(ref _rotation, value);
|
||||
set => RaiseAndSetIfChanged(ref _rotation, value);
|
||||
}
|
||||
|
||||
public float RedScale
|
||||
{
|
||||
get => _redScale;
|
||||
set => SetAndNotify(ref _redScale, value);
|
||||
set => RaiseAndSetIfChanged(ref _redScale, value);
|
||||
}
|
||||
|
||||
public float GreenScale
|
||||
{
|
||||
get => _greenScale;
|
||||
set => SetAndNotify(ref _greenScale, value);
|
||||
set => RaiseAndSetIfChanged(ref _greenScale, value);
|
||||
}
|
||||
|
||||
public float BlueScale
|
||||
{
|
||||
get => _blueScale;
|
||||
set => SetAndNotify(ref _blueScale, value);
|
||||
set => RaiseAndSetIfChanged(ref _blueScale, value);
|
||||
}
|
||||
|
||||
public SKColor CurrentColor
|
||||
{
|
||||
get => _currentColor;
|
||||
set => SetAndNotify(ref _currentColor, value);
|
||||
set => RaiseAndSetIfChanged(ref _currentColor, value);
|
||||
}
|
||||
|
||||
public bool DisplayOnDevices
|
||||
{
|
||||
get => _displayOnDevices;
|
||||
set => SetAndNotify(ref _displayOnDevices, value);
|
||||
set => RaiseAndSetIfChanged(ref _displayOnDevices, value);
|
||||
}
|
||||
|
||||
// This solution won't scale well but I don't expect there to be many more categories.
|
||||
@@ -143,36 +176,42 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
_rgbService.FlushLeds = true;
|
||||
}
|
||||
|
||||
public void BrowseCustomLayout(object sender, MouseEventArgs e)
|
||||
public void ClearCustomLayout()
|
||||
{
|
||||
if (e.OriginalSource is Button)
|
||||
{
|
||||
Device.CustomLayoutPath = null;
|
||||
((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue("Cleared imported layout.");
|
||||
return;
|
||||
}
|
||||
Device.CustomLayoutPath = null;
|
||||
_notificationService.CreateNotification()
|
||||
.WithMessage("Cleared imported layout.")
|
||||
.WithSeverity(NotificationSeverity.Informational);
|
||||
}
|
||||
|
||||
VistaOpenFileDialog dialog = new();
|
||||
dialog.Filter = "Layout files (*.xml)|*.xml";
|
||||
dialog.Title = "Select device layout file";
|
||||
bool? result = dialog.ShowDialog();
|
||||
if (result == true)
|
||||
public async Task BrowseCustomLayout()
|
||||
{
|
||||
string[]? files = await _windowService.CreateOpenFileDialog()
|
||||
.WithTitle("Select device layout file")
|
||||
.HavingFilter(f => f.WithName("Layout files").WithExtension("xml"))
|
||||
.ShowAsync();
|
||||
|
||||
if (files?.Length > 0)
|
||||
{
|
||||
Device.CustomLayoutPath = dialog.FileName;
|
||||
((DeviceDialogViewModel) Parent).DeviceMessageQueue.Enqueue($"Imported layout from {dialog.FileName}.");
|
||||
Device.CustomLayoutPath = files[0];
|
||||
_notificationService.CreateNotification()
|
||||
.WithTitle("Imported layout")
|
||||
.WithMessage($"File loaded from {files[0]}")
|
||||
.WithSeverity(NotificationSeverity.Informational);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SelectPhysicalLayout()
|
||||
{
|
||||
await _dialogService.ShowDialogAt<DeviceLayoutDialogViewModel>("DeviceDialog", new Dictionary<string, object> {{"device", Device}});
|
||||
// await _windowService.CreateContentDialog()
|
||||
// .WithTitle("Select layout")
|
||||
// .WithViewModel<DeviceLayoutDialogViewModel>(("device", Device))
|
||||
// .ShowAsync();
|
||||
}
|
||||
|
||||
public async Task Apply()
|
||||
{
|
||||
await ValidateAsync();
|
||||
if (HasErrors)
|
||||
return;
|
||||
// TODO: Validation
|
||||
|
||||
_coreService.ProfileRenderingDisabled = true;
|
||||
await Task.Delay(100);
|
||||
@@ -206,36 +245,6 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
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);
|
||||
@@ -248,31 +257,22 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
else
|
||||
_categories.Remove(category);
|
||||
|
||||
NotifyOfPropertyChange($"Has{category}Category");
|
||||
this.RaisePropertyChanged($"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)
|
||||
private void OnFrameRendering(object? sender, FrameRenderingEventArgs e)
|
||||
{
|
||||
if (!_displayOnDevices)
|
||||
return;
|
||||
|
||||
using SKPaint overlayPaint = new()
|
||||
{
|
||||
Color = CurrentColor
|
||||
};
|
||||
using SKPaint overlayPaint = new() {Color = CurrentColor};
|
||||
e.Canvas.DrawRect(0, 0, e.Canvas.LocalClipBounds.Width, e.Canvas.LocalClipBounds.Height, overlayPaint);
|
||||
}
|
||||
|
||||
#endregion
|
||||
private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
|
||||
Task.Run(() => _rgbService.ApplyBestDeviceLayout(Device));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<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.Device.InputMappingsTabView">
|
||||
Welcome to Avalonia!
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public partial class InputMappingsTabView : UserControl
|
||||
{
|
||||
public InputMappingsTabView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
using RGB.NET.Core;
|
||||
|
||||
namespace Artemis.UI.Screens.Device
|
||||
{
|
||||
public class InputMappingsTabViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly IRgbService _rgbService;
|
||||
private readonly IInputService _inputService;
|
||||
private readonly ObservableCollection<ArtemisLed> _selectedLeds;
|
||||
private ArtemisLed? _selectedLed;
|
||||
|
||||
public InputMappingsTabViewModel(ArtemisDevice device, ObservableCollection<ArtemisLed> selectedLeds, IRgbService rgbService, IInputService inputService)
|
||||
{
|
||||
if (device.DeviceType != RGBDeviceType.Keyboard)
|
||||
throw new ArtemisUIException("The input mappings tab only supports keyboards");
|
||||
|
||||
_rgbService = rgbService;
|
||||
_inputService = inputService;
|
||||
_selectedLeds = selectedLeds;
|
||||
|
||||
Device = device;
|
||||
DisplayName = "Input Mappings";
|
||||
InputMappings = new ObservableCollection<(ArtemisLed, ArtemisLed)>();
|
||||
}
|
||||
|
||||
public ArtemisDevice Device { get; }
|
||||
|
||||
public ArtemisLed? SelectedLed
|
||||
{
|
||||
get => _selectedLed;
|
||||
set => RaiseAndSetIfChanged(ref _selectedLed, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<(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,35 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Header.SimpleHeaderView"
|
||||
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.Header"
|
||||
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:SimpleHeaderViewModel}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="20"
|
||||
Margin="15 0" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding FrameTime}" VerticalAlignment="Center" FontSize="14" Margin="10 0" ToolTip="The time the last frame took to render" />
|
||||
|
||||
<!-- 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="Center"
|
||||
ToolTip="Open debugger"
|
||||
Command="{s:Action ShowDebugger}"
|
||||
materialDesign:RippleAssist.RippleOnTop="True">
|
||||
<materialDesign:PackIcon Kind="Matrix" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,69 +0,0 @@
|
||||
using System.Timers;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Header
|
||||
{
|
||||
public class SimpleHeaderViewModel : Screen
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDebugService _debugService;
|
||||
private string _frameTime;
|
||||
private Timer _frameTimeUpdateTimer;
|
||||
|
||||
public SimpleHeaderViewModel(string displayName, ICoreService coreService, IDebugService debugService)
|
||||
{
|
||||
DisplayName = displayName;
|
||||
|
||||
_coreService = coreService;
|
||||
_debugService = debugService;
|
||||
}
|
||||
|
||||
public string FrameTime
|
||||
{
|
||||
get => _frameTime;
|
||||
set => SetAndNotify(ref _frameTime, value);
|
||||
}
|
||||
|
||||
public void ShowDebugger()
|
||||
{
|
||||
_debugService.ShowDebugger();
|
||||
}
|
||||
|
||||
private void UpdateFrameTime()
|
||||
{
|
||||
FrameTime = $"Frame time: {_coreService.FrameTime.TotalMilliseconds:F2} ms";
|
||||
}
|
||||
|
||||
private void OnFrameTimeUpdateTimerOnElapsed(object sender, ElapsedEventArgs args)
|
||||
{
|
||||
UpdateFrameTime();
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_frameTimeUpdateTimer = new Timer(500);
|
||||
_frameTimeUpdateTimer.Elapsed += OnFrameTimeUpdateTimerOnElapsed;
|
||||
_frameTimeUpdateTimer.Start();
|
||||
|
||||
UpdateFrameTime();
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
_frameTimeUpdateTimer.Elapsed -= OnFrameTimeUpdateTimerOnElapsed;
|
||||
_frameTimeUpdateTimer?.Dispose();
|
||||
_frameTimeUpdateTimer = null;
|
||||
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="1200" d:DesignHeight="900"
|
||||
x:Class="Artemis.UI.Screens.Home.HomeView">
|
||||
<Border Classes="router-container">
|
||||
<Grid RowDefinitions="200,*">
|
||||
<Image Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
VerticalAlignment="Top"
|
||||
Source="/Assets/Images/home-banner.png"
|
||||
Height="200"
|
||||
Stretch="UniformToFill" />
|
||||
|
||||
<!-- TODO: Replace with a shadow when available -->
|
||||
<TextBlock Grid.Row="0"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="Black"
|
||||
FontSize="32"
|
||||
Margin="32"
|
||||
Text=" Welcome to Artemis, the unified RGB platform." />
|
||||
<TextBlock Grid.Row="0"
|
||||
TextWrapping="Wrap"
|
||||
Foreground="White"
|
||||
FontSize="32"
|
||||
Margin="30"
|
||||
Text=" Welcome to Artemis, the unified RGB platform." />
|
||||
|
||||
<Grid Grid.Row="1" MaxWidth="840" Margin="30" VerticalAlignment="Bottom" ColumnDefinitions="*,*" RowDefinitions="*,*">
|
||||
<Border Classes="card" Margin="8" Grid.ColumnSpan="2" ClipToBounds="True">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="Auto,Auto" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Plug" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Classes="h3">Plugins</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
Artemis is built up using plugins. This means devices, brushes, effects and modules (for supporting games) can all be added via plugins.
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Margin="0 15">
|
||||
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers. We're also keeping track of a list of third-party plugins on our wiki.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<controls:HyperlinkButton Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" NavigateUri="https://wiki.artemis-rgb.com/en/guides/user/plugins" HorizontalAlignment="Right">
|
||||
<controls:HyperlinkButton.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Test"></MenuItem>
|
||||
</ContextMenu>
|
||||
</controls:HyperlinkButton.ContextMenu>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="OpenInBrowser" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Get more plugins</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Classes="card" Margin="8" Grid.Row="1" Grid.Column="0" ClipToBounds="True">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="150,95" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Discord" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Classes="h3">Have a chat</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
If you need help, have some feedback or have any other questions feel free to contact us through any of the following channels.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0">
|
||||
<Grid Margin="8" RowDefinitions="*,*">
|
||||
<controls:HyperlinkButton Grid.Row="0" NavigateUri="https://github.com/Artemis-RGB/Artemis">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="0" HorizontalAlignment="Right" NavigateUri="https://artemis-rgb.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="1" NavigateUri="https://discordapp.com/invite/S3MVaC9">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Discord" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Grid.Row="1" HorizontalAlignment="Right" NavigateUri="mailto:spoinky.nl@gmail.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Email" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border Classes="card" Margin="8" Grid.Row="1" Grid.Column="1" ClipToBounds="True">
|
||||
<Grid VerticalAlignment="Stretch" RowDefinitions="150,95" ColumnDefinitions="150,*">
|
||||
<avalonia:MaterialIcon Kind="Github" Width="100" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Classes="h3">Open Source</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top">
|
||||
This project is open source. If you like it and want to say thanks you could hit the GitHub Star button, I like numbers.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<controls:HyperlinkButton Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
NavigateUri="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<avalonia:MaterialIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
|
||||
</StackPanel>
|
||||
</controls:HyperlinkButton>
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center">
|
||||
Feel like making a donation? It would be gratefully received. Click the button to donate via PayPal.
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Home
|
||||
{
|
||||
public class HomeView : ReactiveUserControl<HomeViewModel>
|
||||
{
|
||||
public HomeView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Home.HomeView"
|
||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:home="clr-namespace:Artemis.UI.Screens.Home"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="574.026"
|
||||
d:DesignWidth="1029.87"
|
||||
d:DataContext="{d:DesignInstance home:HomeViewModel}">
|
||||
<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" />
|
||||
<!-- throw in some extra colour for our floating action button -->
|
||||
<ResourceDictionary
|
||||
Source="pack://application:,,,/MaterialDesignColors;component/Themes/MaterialDesignColor.Green.Named.Primary.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="0,0,0,-188">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image Source="{svgc:SvgImage Source=/Resources/Images/Logo/bow.svg}" Height="80" Width="80" />
|
||||
<TextBlock Grid.Column="1" Margin="24 0 0 0" VerticalAlignment="Center" Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap">
|
||||
<Run Text="Welcome to Artemis, the unified" />
|
||||
<Run Text="RGB">
|
||||
<Run.Foreground>
|
||||
<LinearGradientBrush EndPoint="0,0" StartPoint="1,1">
|
||||
<GradientStop Color="#f19d25" />
|
||||
<GradientStop Color="#f63d3d" Offset="0.2" />
|
||||
<GradientStop Color="#c93cec" Offset="0.4" />
|
||||
<GradientStop Color="#2667f4" Offset="0.6" />
|
||||
<GradientStop Color="#1cb6e7" Offset="0.8" />
|
||||
<GradientStop Color="#2df4b5" Offset="1" />
|
||||
</LinearGradientBrush>
|
||||
</Run.Foreground>
|
||||
</Run>
|
||||
<Run Text="platform." />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Bottom"
|
||||
Margin="0 0 0 32">
|
||||
<StackPanel>
|
||||
<materialDesign:Card Width="852" Margin="8" Height="Auto">
|
||||
<Grid VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Kind="Plug" Width="140" Height="140" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="16 16 16 8">Plugins</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
||||
TextWrapping="Wrap" Margin="16 0 16 8"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
VerticalAlignment="Top">
|
||||
Artemis is built up using plugins. This means devices, brushes, effects and modules (for supporting games!) can all be added via plugins. <LineBreak /> <LineBreak />
|
||||
Under Settings > Plugins you can find your currently installed plugins, these default plugins are created by Artemis developers.
|
||||
<LineBreak /> <LineBreak />
|
||||
We're also keeping track of a list of third-party plugins on our wiki.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0" BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<Button 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>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:Card Width="420" Margin="8 2 4 16" Height="Auto">
|
||||
<Grid VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="175" />
|
||||
<RowDefinition Height="95" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Kind="Discord" Width="140" Height="140"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="16 16 16 8">Have a chat</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
||||
TextWrapping="Wrap" Margin="16 0 16 8"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
VerticalAlignment="Top">
|
||||
If you need help, have some feedback or have any other questions feel free to contact us through any of the
|
||||
following channels.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
<Grid Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Button Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Left"
|
||||
x:Name="GitHubButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://github.com/Artemis-RGB/Artemis">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Github" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">GitHub</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="0" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Right"
|
||||
x:Name="WebsiteButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://artemis-rgb.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Web" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Website</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Left"
|
||||
x:Name="DiscordButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://discordapp.com/invite/S3MVaC9">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Discord" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Discord</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Grid.Row="1" Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
HorizontalAlignment="Right"
|
||||
x:Name="MailButton" Command="{s:Action OpenUrl}"
|
||||
CommandParameter="mailto:spoinky.nl@gmail.com">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Email" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">E-mail</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
<materialDesign:Card Width="420" Margin="8 2 4 16" Height="Auto">
|
||||
<Grid VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="175" />
|
||||
<RowDefinition Height="95" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<materialDesign:PackIcon Kind="Github" Width="160" Height="160"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" Margin="16 16 16 8">Open Source</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignTextBlock}"
|
||||
TextWrapping="Wrap" Margin="16 0 16 8"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
VerticalAlignment="Top">
|
||||
This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button,
|
||||
I like numbers. You could even make plugins, there's a full documentation on the website
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1" Grid.ColumnSpan="2" Grid.Column="0" BorderThickness="0 1 0 0"
|
||||
BorderBrush="{DynamicResource MaterialDesignDivider}" Padding="8">
|
||||
<DockPanel>
|
||||
<Button Style="{DynamicResource MaterialDesignFlatButton}"
|
||||
DockPanel.Dock="Right"
|
||||
x:Name="DonateButton"
|
||||
Command="{s:Action OpenUrl}"
|
||||
CommandParameter="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VQBAEJYUFLU4J">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<materialDesign:PackIcon Kind="Gift" />
|
||||
<TextBlock Margin="8 0 0 0" VerticalAlignment="Center">Donate</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<TextBlock Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="16"
|
||||
VerticalAlignment="Center">
|
||||
Feel like you want to make a donation? It would be gratefully received. Click the button to donate via PayPal.
|
||||
</TextBlock>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,18 +1,12 @@
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Home
|
||||
{
|
||||
public class HomeViewModel : MainScreenViewModel
|
||||
{
|
||||
public HomeViewModel(IHeaderVmFactory headerVmFactory)
|
||||
public HomeViewModel(IScreen hostScreen) : base(hostScreen, "home")
|
||||
{
|
||||
DisplayName = "Home";
|
||||
HeaderViewModel = headerVmFactory.SimpleHeaderViewModel(DisplayName);
|
||||
}
|
||||
|
||||
public void OpenUrl(string url)
|
||||
{
|
||||
Core.Utilities.OpenUrl(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens
|
||||
{
|
||||
public abstract class MainScreenViewModel : Screen
|
||||
{
|
||||
private Screen _headerViewModel;
|
||||
|
||||
public Screen HeaderViewModel
|
||||
{
|
||||
get => _headerViewModel;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _headerViewModel, value)) return;
|
||||
_headerViewModel.ConductWith(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens
|
||||
{
|
||||
public abstract class MainScreenViewModel : ActivatableViewModelBase, IRoutableViewModel
|
||||
{
|
||||
protected MainScreenViewModel(IScreen hostScreen, string urlPathSegment)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
UrlPathSegment = urlPathSegment;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlPathSegment { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IScreen HostScreen { get; }
|
||||
|
||||
public ViewModelBase? TitleBarViewModel { get; protected set; }
|
||||
}
|
||||
}
|
||||
+38
-51
@@ -1,86 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase
|
||||
public class PluginPrerequisitesInstallDialogViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
private PluginPrerequisiteViewModel _activePrerequisite;
|
||||
private CancellationTokenSource _tokenSource;
|
||||
private bool _showProgress;
|
||||
private bool _showIntro = true;
|
||||
private PluginPrerequisiteViewModel? _activePrerequisite;
|
||||
private bool _canInstall;
|
||||
private bool _showFailed;
|
||||
private bool _showInstall = true;
|
||||
private bool _canInstall;
|
||||
private bool _showIntro = true;
|
||||
private bool _showProgress;
|
||||
private CancellationTokenSource? _tokenSource;
|
||||
|
||||
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory, IDialogService dialogService)
|
||||
public PluginPrerequisitesInstallDialogViewModel(List<IPrerequisitesSubject> subjects, IPrerequisitesVmFactory prerequisitesVmFactory)
|
||||
{
|
||||
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>();
|
||||
foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
|
||||
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, false)));
|
||||
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
|
||||
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
|
||||
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, false));
|
||||
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
pluginPrerequisiteViewModel.ConductWith(this);
|
||||
CanInstall = false;
|
||||
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
_tokenSource?.Dispose();
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
|
||||
public PluginPrerequisiteViewModel ActivePrerequisite
|
||||
public PluginPrerequisiteViewModel? ActivePrerequisite
|
||||
{
|
||||
get => _activePrerequisite;
|
||||
set => SetAndNotify(ref _activePrerequisite, value);
|
||||
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
|
||||
}
|
||||
|
||||
public bool ShowProgress
|
||||
{
|
||||
get => _showProgress;
|
||||
set => SetAndNotify(ref _showProgress, value);
|
||||
set => RaiseAndSetIfChanged(ref _showProgress, value);
|
||||
}
|
||||
|
||||
public bool ShowIntro
|
||||
{
|
||||
get => _showIntro;
|
||||
set => SetAndNotify(ref _showIntro, value);
|
||||
set => RaiseAndSetIfChanged(ref _showIntro, value);
|
||||
}
|
||||
|
||||
public bool ShowFailed
|
||||
{
|
||||
get => _showFailed;
|
||||
set => SetAndNotify(ref _showFailed, value);
|
||||
set => RaiseAndSetIfChanged(ref _showFailed, value);
|
||||
}
|
||||
|
||||
public bool ShowInstall
|
||||
{
|
||||
get => _showInstall;
|
||||
set => SetAndNotify(ref _showInstall, value);
|
||||
set => RaiseAndSetIfChanged(ref _showInstall, value);
|
||||
}
|
||||
|
||||
public bool CanInstall
|
||||
{
|
||||
get => _canInstall;
|
||||
set => SetAndNotify(ref _canInstall, value);
|
||||
set => RaiseAndSetIfChanged(ref _canInstall, value);
|
||||
}
|
||||
|
||||
#region Overrides of DialogViewModelBase
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
base.OnDialogClosed(sender, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async void Install()
|
||||
public async Task Install()
|
||||
{
|
||||
CanInstall = false;
|
||||
ShowFailed = false;
|
||||
@@ -128,25 +128,12 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
public void Accept()
|
||||
{
|
||||
Session?.Close(true);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects)
|
||||
public static async Task<bool> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects)
|
||||
{
|
||||
return dialogService.ShowDialog<PluginPrerequisitesInstallDialogViewModel>(new Dictionary<string, object> {{"subjects", subjects}});
|
||||
return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects));
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
CanInstall = false;
|
||||
Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet()));
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+46
-71
@@ -1,97 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase
|
||||
public class PluginPrerequisitesUninstallDialogViewModel : DialogViewModelBase<bool>
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly List<IPrerequisitesSubject> _subjects;
|
||||
private PluginPrerequisiteViewModel _activePrerequisite;
|
||||
private readonly IWindowService _windowService;
|
||||
private PluginPrerequisiteViewModel? _activePrerequisite;
|
||||
private bool _canUninstall;
|
||||
private bool _isFinished;
|
||||
private CancellationTokenSource _tokenSource;
|
||||
private CancellationTokenSource? _tokenSource;
|
||||
|
||||
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory,
|
||||
IDialogService dialogService, IPluginManagementService pluginManagementService)
|
||||
public PluginPrerequisitesUninstallDialogViewModel(List<IPrerequisitesSubject> subjects, string cancelLabel, IPrerequisitesVmFactory prerequisitesVmFactory, IWindowService windowService,
|
||||
IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_subjects = subjects;
|
||||
_dialogService = dialogService;
|
||||
_windowService = windowService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
|
||||
CancelLabel = cancelLabel;
|
||||
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>();
|
||||
foreach (IPrerequisitesSubject prerequisitesSubject in subjects)
|
||||
Prerequisites.AddRange(prerequisitesSubject.Prerequisites.Select(p => prerequisitesVmFactory.PluginPrerequisiteViewModel(p, true)));
|
||||
Prerequisites = new ObservableCollection<PluginPrerequisiteViewModel>();
|
||||
foreach (PluginPrerequisite prerequisite in subjects.SelectMany(prerequisitesSubject => prerequisitesSubject.Prerequisites))
|
||||
Prerequisites.Add(prerequisitesVmFactory.PluginPrerequisiteViewModel(prerequisite, true));
|
||||
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
pluginPrerequisiteViewModel.ConductWith(this);
|
||||
// Could be slow so take it off of the UI thread
|
||||
Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
_tokenSource?.Dispose();
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public string CancelLabel { get; }
|
||||
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
public ObservableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
|
||||
public PluginPrerequisiteViewModel ActivePrerequisite
|
||||
public PluginPrerequisiteViewModel? ActivePrerequisite
|
||||
{
|
||||
get => _activePrerequisite;
|
||||
set => SetAndNotify(ref _activePrerequisite, value);
|
||||
set => RaiseAndSetIfChanged(ref _activePrerequisite, value);
|
||||
}
|
||||
|
||||
public bool CanUninstall
|
||||
{
|
||||
get => _canUninstall;
|
||||
set => SetAndNotify(ref _canUninstall, value);
|
||||
set => RaiseAndSetIfChanged(ref _canUninstall, value);
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get => _isFinished;
|
||||
set => SetAndNotify(ref _isFinished, value);
|
||||
set => RaiseAndSetIfChanged(ref _isFinished, value);
|
||||
}
|
||||
|
||||
#region Overrides of DialogViewModelBase
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
base.OnDialogClosed(sender, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async void Uninstall()
|
||||
public async Task Uninstall()
|
||||
{
|
||||
CanUninstall = false;
|
||||
|
||||
// Disable all subjects that are plugins, this will disable their features too
|
||||
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
|
||||
{
|
||||
if (prerequisitesSubject is PluginInfo pluginInfo)
|
||||
_pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
|
||||
if (prerequisitesSubject is PluginInfo pluginInfo) _pluginManagementService.DisablePlugin(pluginInfo.Plugin, true);
|
||||
}
|
||||
|
||||
// Disable all subjects that are features if still required
|
||||
foreach (IPrerequisitesSubject prerequisitesSubject in _subjects)
|
||||
{
|
||||
if (prerequisitesSubject is not PluginFeatureInfo featureInfo)
|
||||
continue;
|
||||
if (prerequisitesSubject is not PluginFeatureInfo featureInfo) continue;
|
||||
|
||||
// Disable the parent plugin if the feature is AlwaysEnabled
|
||||
if (featureInfo.AlwaysEnabled)
|
||||
_pluginManagementService.DisablePlugin(featureInfo.Plugin, true);
|
||||
else if (featureInfo.Instance != null)
|
||||
_pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
|
||||
else if (featureInfo.Instance != null) _pluginManagementService.DisablePluginFeature(featureInfo.Instance, true);
|
||||
}
|
||||
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
@@ -101,15 +98,13 @@ namespace Artemis.UI.Screens.Plugins
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
{
|
||||
pluginPrerequisiteViewModel.IsMet = pluginPrerequisiteViewModel.PluginPrerequisite.IsMet();
|
||||
if (!pluginPrerequisiteViewModel.IsMet)
|
||||
continue;
|
||||
if (!pluginPrerequisiteViewModel.IsMet) continue;
|
||||
|
||||
ActivePrerequisite = pluginPrerequisiteViewModel;
|
||||
await ActivePrerequisite.Uninstall(_tokenSource.Token);
|
||||
|
||||
// Wait after the task finished for the user to process what happened
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last())
|
||||
await Task.Delay(1000);
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last()) await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (Prerequisites.All(p => !p.IsMet))
|
||||
@@ -120,14 +115,12 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
// This shouldn't be happening and the experience isn't very nice for the user (too lazy to make a nice UI for such an edge case)
|
||||
// but at least give some feedback
|
||||
Session?.Close(false);
|
||||
await _dialogService.ShowConfirmDialog(
|
||||
"Plugin prerequisites",
|
||||
"The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.",
|
||||
"Confirm",
|
||||
""
|
||||
);
|
||||
await Show(_dialogService, _subjects);
|
||||
Close(false);
|
||||
await _windowService.CreateContentDialog()
|
||||
.WithTitle("Plugin prerequisites")
|
||||
.WithContent("The plugin was not able to fully remove all prerequisites. \r\nPlease try again or contact the plugin creator.")
|
||||
.ShowAsync();
|
||||
await Show(_windowService, _subjects);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -143,30 +136,12 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
public void Accept()
|
||||
{
|
||||
Session?.Close(true);
|
||||
Close(true);
|
||||
}
|
||||
|
||||
public static Task<object> Show(IDialogService dialogService, List<IPrerequisitesSubject> subjects, string cancelLabel = "CANCEL")
|
||||
public static async Task<object> Show(IWindowService windowService, List<IPrerequisitesSubject> subjects, string cancelLabel = "Cancel")
|
||||
{
|
||||
return dialogService.ShowDialog<PluginPrerequisitesUninstallDialogViewModel>(new Dictionary<string, object>
|
||||
{
|
||||
{"subjects", subjects},
|
||||
{"cancelLabel", cancelLabel},
|
||||
});
|
||||
return await windowService.ShowDialogAsync<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
CanUninstall = false;
|
||||
// Could be slow so take it off of the UI thread
|
||||
Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet()));
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginFeatureView">
|
||||
<Grid ColumnDefinitions="30,*,Auto">
|
||||
<Grid.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuItem Header="Install prerequisites" Command="{Binding InstallPrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="CheckAll" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove prerequisites" Command="{Binding RemovePrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Delete" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</Grid.ContextFlyout>
|
||||
|
||||
<!-- Icon column -->
|
||||
<controls:ArtemisIcon Grid.Column="0"
|
||||
Icon="{Binding FeatureInfo.ResolvedIcon}"
|
||||
Width="20"
|
||||
Height="20"
|
||||
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNull}}" />
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Classes="AppBarButton icon-button"
|
||||
IsVisible="{Binding LoadException, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Foreground="#E74C4C"
|
||||
ToolTip.Tip="An exception occurred while enabling this feature, click to view"
|
||||
Command="{Binding ViewLoadException}">
|
||||
<avalonia:MaterialIcon Kind="AlertCircle" />
|
||||
</Button>
|
||||
|
||||
<!-- Display name column -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding FeatureInfo.Name}"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="{Binding FeatureInfo.Description}" />
|
||||
|
||||
<!-- Enable toggle column -->
|
||||
<StackPanel Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding !Enabling}"
|
||||
Orientation="Horizontal"
|
||||
ToolTip.Tip="This feature cannot be disabled without disabling the whole plugin">
|
||||
<avalonia:MaterialIcon Kind="ShieldHalfFull"
|
||||
ToolTip.Tip="Plugin requires admin rights"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 5 0"
|
||||
IsVisible="{Binding ShowShield}" />
|
||||
|
||||
<CheckBox IsChecked="{Binding IsEnabled}" IsEnabled="{Binding CanToggleEnabled}">
|
||||
Feature enabled
|
||||
</CheckBox>
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="7" IsVisible="{Binding Enabling}">
|
||||
<ProgressBar Value="0" IsIndeterminate="True" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public partial class PluginFeatureView : ReactiveUserControl<PluginFeatureViewModel>
|
||||
{
|
||||
public PluginFeatureView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.Builders;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia;
|
||||
using Avalonia.Threading;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginFeatureViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IWindowService _windowService;
|
||||
private bool _enabling;
|
||||
|
||||
public PluginFeatureViewModel(PluginFeatureInfo pluginFeatureInfo,
|
||||
bool showShield,
|
||||
ICoreService coreService,
|
||||
IWindowService windowService,
|
||||
INotificationService notificationService,
|
||||
IPluginManagementService pluginManagementService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
|
||||
FeatureInfo = pluginFeatureInfo;
|
||||
ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling;
|
||||
_pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped;
|
||||
_pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped;
|
||||
|
||||
FeatureInfo.Plugin.Enabled += PluginOnToggled;
|
||||
FeatureInfo.Plugin.Disabled += PluginOnToggled;
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling;
|
||||
_pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped;
|
||||
_pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped;
|
||||
|
||||
FeatureInfo.Plugin.Enabled -= PluginOnToggled;
|
||||
FeatureInfo.Plugin.Disabled -= PluginOnToggled;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public PluginFeatureInfo FeatureInfo { get; }
|
||||
public Exception? LoadException => FeatureInfo.LoadException;
|
||||
|
||||
public bool ShowShield { get; }
|
||||
|
||||
public bool Enabling
|
||||
{
|
||||
get => _enabling;
|
||||
set => RaiseAndSetIfChanged(ref _enabling, value);
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => FeatureInfo.Instance != null && FeatureInfo.Instance.IsEnabled;
|
||||
set => Dispatcher.UIThread.InvokeAsync(() => 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
|
||||
{
|
||||
Utilities.OpenFolder(Constants.LogsFolder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ViewLoadException()
|
||||
{
|
||||
if (LoadException != null)
|
||||
_windowService.ShowExceptionDialog("Feature failed to enable", LoadException);
|
||||
}
|
||||
|
||||
public async Task InstallPrerequisites()
|
||||
{
|
||||
if (FeatureInfo.Prerequisites.Any())
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
|
||||
}
|
||||
|
||||
public async Task RemovePrerequisites()
|
||||
{
|
||||
if (FeatureInfo.Prerequisites.Any(p => p.UninstallActions.Any()))
|
||||
{
|
||||
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateEnabled(bool enable)
|
||||
{
|
||||
if (IsEnabled == enable)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
return;
|
||||
}
|
||||
|
||||
if (FeatureInfo.Instance == null)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
_notificationService.CreateNotification()
|
||||
.WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.")
|
||||
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
|
||||
.WithSeverity(NotificationSeverity.Error)
|
||||
.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable)
|
||||
{
|
||||
Enabling = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (FeatureInfo.Plugin.Info.RequiresAdmin && !_coreService.IsElevated)
|
||||
{
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable feature", "The plugin of this feature requires admin rights, are you sure you want to enable it?");
|
||||
if (!confirmed)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all prerequisites are met async
|
||||
if (!FeatureInfo.ArePrerequisitesMet())
|
||||
{
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, new List<IPrerequisitesSubject> {FeatureInfo});
|
||||
if (!FeatureInfo.ArePrerequisitesMet())
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() => _pluginManagementService.EnablePluginFeature(FeatureInfo.Instance!, true));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_notificationService.CreateNotification()
|
||||
.WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}")
|
||||
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
|
||||
.WithSeverity(NotificationSeverity.Error)
|
||||
.Show();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Enabling = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginManagementService.DisablePluginFeature(FeatureInfo.Instance, true);
|
||||
this.RaisePropertyChanged(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;
|
||||
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
this.RaisePropertyChanged(nameof(LoadException));
|
||||
}
|
||||
|
||||
private void PluginOnToggled(object? sender, EventArgs e)
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(CanToggleEnabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteActionView"
|
||||
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.Plugins"
|
||||
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:PluginPrerequisiteActionViewModel}">
|
||||
<StackPanel>
|
||||
<ProgressBar Value="{Binding Action.Progress.Percentage, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Action.ProgressIndeterminate, Mode=OneWay}"
|
||||
Visibility="{Binding Action.ShowProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
materialDesign:TransitionAssist.DisableTransitions="True"
|
||||
Margin="0 10"/>
|
||||
<ProgressBar Value="{Binding Action.SubProgress.Percentage, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Action.SubProgressIndeterminate, Mode=OneWay}"
|
||||
Visibility="{Binding Action.ShowSubProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
materialDesign:TransitionAssist.DisableTransitions="True"
|
||||
Margin="0 10"/>
|
||||
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Action.Status, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,14 +1,10 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisiteActionViewModel : Screen
|
||||
public class PluginPrerequisiteActionViewModel : ViewModelBase
|
||||
{
|
||||
|
||||
|
||||
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
|
||||
{
|
||||
Action = action;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteView"
|
||||
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.Plugins"
|
||||
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:PluginPrerequisiteViewModel}">
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Name, Mode=OneWay}" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Description, Mode=OneWay}" Margin="0 0 0 15" />
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Run Text="Step" />
|
||||
<Run Text="{Binding ActiveStemNumber, Mode=OneWay}" /><Run Text="/" /><Run Text="{Binding Items.Count, Mode=OneWay}" />
|
||||
<Run Text="-" />
|
||||
<Run Text="{Binding ActiveItem.Action.Name, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding ActiveItem.Action.Name, Mode=OneWay}"
|
||||
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
Margin="0 10 0 0" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,26 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
using Artemis.UI.Shared;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisiteViewModel : Conductor<PluginPrerequisiteActionViewModel>.Collection.OneActive
|
||||
public class PluginPrerequisiteViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ObservableAsPropertyHelper<int> _activeStepNumber;
|
||||
private readonly ObservableAsPropertyHelper<bool> _busy;
|
||||
private readonly bool _uninstall;
|
||||
|
||||
private PluginPrerequisiteActionViewModel? _activeAction;
|
||||
|
||||
private bool _installing;
|
||||
private bool _uninstalling;
|
||||
private bool _isMet;
|
||||
private bool _uninstalling;
|
||||
|
||||
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
|
||||
{
|
||||
_uninstall = uninstall;
|
||||
|
||||
PluginPrerequisite = pluginPrerequisite;
|
||||
Actions = new ObservableCollection<PluginPrerequisiteActionViewModel>(!_uninstall
|
||||
? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))
|
||||
: PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
|
||||
|
||||
this.WhenAnyValue(x => x.Installing, x => x.Uninstalling, (i, u) => i || u).ToProperty(this, x => x.Busy, out _busy);
|
||||
this.WhenAnyValue(x => x.ActiveAction, a => Actions.IndexOf(a!)).ToProperty(this, x => x.ActiveStepNumber, out _activeStepNumber);
|
||||
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
|
||||
Disposable.Create(() => PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged).DisposeWith(d);
|
||||
});
|
||||
|
||||
// Could be slow so take it off of the UI thread
|
||||
Task.Run(() => IsMet = PluginPrerequisite.IsMet());
|
||||
}
|
||||
|
||||
public ObservableCollection<PluginPrerequisiteActionViewModel> Actions { get; }
|
||||
|
||||
public PluginPrerequisiteActionViewModel? ActiveAction
|
||||
{
|
||||
get => _activeAction;
|
||||
set => RaiseAndSetIfChanged(ref _activeAction, value);
|
||||
}
|
||||
|
||||
public PluginPrerequisite PluginPrerequisite { get; }
|
||||
@@ -28,32 +58,24 @@ namespace Artemis.UI.Screens.Plugins
|
||||
public bool Installing
|
||||
{
|
||||
get => _installing;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _installing, value);
|
||||
NotifyOfPropertyChange(nameof(Busy));
|
||||
}
|
||||
set => RaiseAndSetIfChanged(ref _installing, value);
|
||||
}
|
||||
|
||||
public bool Uninstalling
|
||||
{
|
||||
get => _uninstalling;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _uninstalling, value);
|
||||
NotifyOfPropertyChange(nameof(Busy));
|
||||
}
|
||||
set => RaiseAndSetIfChanged(ref _uninstalling, value);
|
||||
}
|
||||
|
||||
public bool IsMet
|
||||
{
|
||||
get => _isMet;
|
||||
set => SetAndNotify(ref _isMet, value);
|
||||
set => RaiseAndSetIfChanged(ref _isMet, value);
|
||||
}
|
||||
|
||||
public bool Busy => Installing || Uninstalling;
|
||||
public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1;
|
||||
public bool HasMultipleActions => Items.Count > 1;
|
||||
public bool Busy => _busy.Value;
|
||||
public int ActiveStepNumber => _activeStepNumber.Value;
|
||||
public bool HasMultipleActions => Actions.Count > 1;
|
||||
|
||||
public async Task Install(CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -89,7 +111,7 @@ namespace Artemis.UI.Screens.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginPrerequisiteOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
|
||||
ActivateCurrentAction();
|
||||
@@ -97,37 +119,11 @@ namespace Artemis.UI.Screens.Plugins
|
||||
|
||||
private void ActivateCurrentAction()
|
||||
{
|
||||
PluginPrerequisiteActionViewModel newActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
|
||||
if (newActiveItem == null)
|
||||
PluginPrerequisiteActionViewModel? activeAction = Actions.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
|
||||
if (activeAction == null)
|
||||
return;
|
||||
|
||||
ActiveItem = newActiveItem;
|
||||
NotifyOfPropertyChange(nameof(ActiveStemNumber));
|
||||
ActiveAction = activeAction;
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
|
||||
// Could be slow so take it off of the UI thread
|
||||
Task.Run(() => IsMet = PluginPrerequisite.IsMet());
|
||||
|
||||
Items.AddRange(!_uninstall
|
||||
? PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a))
|
||||
: PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesInstallDialogView"
|
||||
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.Plugins"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PluginPrerequisitesInstallDialogViewModel}">
|
||||
<UserControl.Resources>
|
||||
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
|
||||
<converters:NullToBooleanConverter x:Key="NullToBooleanConverter" />
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition MinHeight="150" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="500" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
|
||||
Plugin/feature prerequisites
|
||||
</TextBlock>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Prerequisites}"
|
||||
SelectedItem="{Binding ActivePrerequisite, Mode=OneWay}"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:PluginPrerequisiteViewModel}">
|
||||
<Border Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ToggleButton VerticalAlignment="Center"
|
||||
Style="{StaticResource MaterialDesignActionToggleButton}"
|
||||
Focusable="False"
|
||||
IsHitTestVisible="False"
|
||||
IsChecked="{Binding IsMet}">
|
||||
<ToggleButton.Content>
|
||||
<Border Background="#E74C4C" Width="32" Height="32">
|
||||
<materialDesign:PackIcon Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
</Border>
|
||||
</ToggleButton.Content>
|
||||
<materialDesign:ToggleButtonAssist.OnContent>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</materialDesign:ToggleButtonAssist.OnContent>
|
||||
</ToggleButton>
|
||||
|
||||
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding PluginPrerequisite.Name}" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding PluginPrerequisite.Description}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
s:View.Model="{Binding ActivePrerequisite}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Margin="10 0"
|
||||
IsTabStop="False"
|
||||
Visibility="{Binding ShowProgress, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignBody1TextBlock}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="10 0"
|
||||
Visibility="{Binding ShowIntro, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
In order for this plugin/feature to function certain prerequisites must be met. <LineBreak /><LineBreak />
|
||||
On the left side you can see all prerequisites and whether they are currently met or not.
|
||||
Clicking install prerequisites will automatically set everything up for you.
|
||||
</TextBlock>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignBody1TextBlock}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="10 0"
|
||||
Visibility="{Binding ShowFailed, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
Installing <Run Text="{Binding ActivePrerequisite.PluginPrerequisite.Name, Mode=OneWay}" FontWeight="SemiBold" /> failed. <LineBreak /><LineBreak />
|
||||
You may try again to see if that helps, otherwise install the prerequisite manually or contact the plugin developer.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding ShowInstall, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<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"
|
||||
IsEnabled="{Binding ShowProgress, Converter={StaticResource InverseBooleanConverter}, Mode=OneWay}"
|
||||
Command="{s:Action Install}"
|
||||
Content="INSTALL PREREQUISITES" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding ShowInstall, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Focusable="False"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Accept}"
|
||||
Content="FINISH" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,125 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesUninstallDialogView"
|
||||
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.Plugins"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PluginPrerequisitesUninstallDialogViewModel}">
|
||||
<UserControl.Resources>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition MinHeight="150"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="500" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
|
||||
Plugin/feature prerequisites
|
||||
</TextBlock>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Prerequisites}"
|
||||
SelectedItem="{Binding ActivePrerequisite, Mode=OneWay}"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:PluginPrerequisiteViewModel}">
|
||||
<Border Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ToggleButton VerticalAlignment="Center"
|
||||
Style="{StaticResource MaterialDesignActionToggleButton}"
|
||||
Focusable="False"
|
||||
IsHitTestVisible="False"
|
||||
IsChecked="{Binding IsMet}">
|
||||
<ToggleButton.Content>
|
||||
<Border Background="#E74C4C" Width="32" Height="32">
|
||||
<materialDesign:PackIcon Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
</Border>
|
||||
</ToggleButton.Content>
|
||||
<materialDesign:ToggleButtonAssist.OnContent>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</materialDesign:ToggleButtonAssist.OnContent>
|
||||
</ToggleButton>
|
||||
|
||||
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding PluginPrerequisite.Name}" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding PluginPrerequisite.Description}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
s:View.Model="{Binding ActivePrerequisite}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Margin="10 0"
|
||||
IsTabStop="False"
|
||||
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}}"/>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignBody1TextBlock}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="10 0"
|
||||
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
|
||||
This plugin/feature installed certain prerequisites in order to function. <LineBreak /><LineBreak />
|
||||
In this screen you can chose to remove these, this will mean the plugin/feature will no longer work until you reinstall the prerequisites.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Focusable="False"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Cancel}"
|
||||
Content="{Binding CancelLabel}" />
|
||||
<Button x:Name="ConfirmButton"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsDefault="True"
|
||||
Focusable="True"
|
||||
Command="{s:Action Uninstall}"
|
||||
Content="REMOVE PREREQUISITES" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Focusable="False"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Accept}"
|
||||
Content="FINISH" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,118 @@
|
||||
<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:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsView">
|
||||
<Border Classes="card" Padding="15" Margin="0 5">
|
||||
<Grid RowDefinitions="*,Auto" ColumnDefinitions="4*,5*">
|
||||
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*" ColumnDefinitions="80,*">
|
||||
<controls1:ArtemisIcon Icon="{Binding Plugin.Info.ResolvedIcon}"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="0 5 0 0"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
VerticalAlignment="Top" />
|
||||
|
||||
<TextBlock Grid.Column="1" Grid.Row="0" Classes="h5 no-margin" Text="{Binding Plugin.Info.Name}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Classes="subtitle"
|
||||
Text="{Binding Plugin.Info.Author}"
|
||||
IsVisible="{Binding Plugin.Info.Author, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0 5"
|
||||
Text="{Binding Plugin.Info.Description}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="0" ColumnDefinitions="*,Auto">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<controls:SplitButton Content="Settings" Command="{Binding OpenSettings}">
|
||||
<controls:SplitButton.Flyout>
|
||||
<MenuFlyout Placement="Bottom">
|
||||
<MenuItem Header="Open plugin directory" Command="{Binding OpenPluginDirectory}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="FolderOpen" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Reload plugin" Command="{Binding Reload}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Reload" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Install prerequisites" Command="{Binding InstallPrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="CheckAll" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove prerequisites" Command="{Binding RemovePrerequisites}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="Delete" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Clear plugin settings" Command="{Binding RemoveSettings}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="DatabaseRemove" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Remove plugin" Command="{Binding Remove}">
|
||||
<MenuItem.Icon>
|
||||
<avalonia:MaterialIcon Kind="DeleteForever" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</controls:SplitButton.Flyout>
|
||||
</controls:SplitButton>
|
||||
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
Margin="5 0"
|
||||
IsVisible="{Binding Plugin.Info.Website, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{Binding Plugin.Info.Website}">
|
||||
<avalonia:MaterialIcon Kind="Web" />
|
||||
</controls:HyperlinkButton>
|
||||
<controls:HyperlinkButton Classes="icon-button icon-button-large"
|
||||
Margin="5 0"
|
||||
IsVisible="{Binding Plugin.Info.Repository, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||
NavigateUri="{Binding Plugin.Info.Repository}">
|
||||
<avalonia:MaterialIcon Kind="Git" />
|
||||
</controls:HyperlinkButton>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding !Enabling}"
|
||||
IsChecked="{Binding IsEnabled}">
|
||||
<StackPanel x:Name="EnableText" Orientation="Horizontal">
|
||||
<TextBlock>Plugin enabled</TextBlock>
|
||||
<avalonia:MaterialIcon Kind="ShieldHalfFull"
|
||||
Margin="5 0 0 0"
|
||||
ToolTip.Tip="Plugin requires admin rights"
|
||||
IsVisible="{Binding Plugin.Info.RequiresAdmin}" />
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
|
||||
<ProgressBar Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding Enabling}"
|
||||
IsIndeterminate="True" />
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="{DynamicResource ButtonBorderBrush}" BorderThickness="1 0 0 0" Margin="10 0 0 0" Padding="10 0 0 0">
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<TextBlock Classes="h5">Plugin features</TextBlock>
|
||||
<ListBox Grid.Row="1" MaxHeight="135" Items="{Binding PluginFeatures}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public partial class PluginSettingsView : ReactiveUserControl<PluginSettingsViewModel>
|
||||
{
|
||||
public PluginSettingsView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
-86
@@ -1,28 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Avalonia.Threading;
|
||||
using Ninject;
|
||||
using Stylet;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginSettingsViewModel : Conductor<PluginFeatureViewModel>.Collection.AllActive
|
||||
public class PluginSettingsViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IMessageService _messageService;
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly ISettingsVmFactory _settingsVmFactory;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly IWindowService _windowService;
|
||||
private bool _canInstallPrerequisites;
|
||||
private bool _canRemovePrerequisites;
|
||||
private bool _enabling;
|
||||
@@ -32,35 +34,60 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
public PluginSettingsViewModel(Plugin plugin,
|
||||
ISettingsVmFactory settingsVmFactory,
|
||||
ICoreService coreService,
|
||||
IWindowManager windowManager,
|
||||
IDialogService dialogService,
|
||||
IPluginManagementService pluginManagementService,
|
||||
IMessageService messageService)
|
||||
IWindowService windowService,
|
||||
INotificationService notificationService,
|
||||
IPluginManagementService pluginManagementService)
|
||||
{
|
||||
Plugin = plugin;
|
||||
_plugin = plugin;
|
||||
|
||||
_settingsVmFactory = settingsVmFactory;
|
||||
_coreService = coreService;
|
||||
_windowManager = windowManager;
|
||||
_dialogService = dialogService;
|
||||
_windowService = windowService;
|
||||
_notificationService = notificationService;
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_messageService = messageService;
|
||||
|
||||
PluginFeatures = new ObservableCollection<PluginFeatureViewModel>();
|
||||
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
|
||||
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
|
||||
|
||||
|
||||
|
||||
OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null));
|
||||
InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites));
|
||||
RemovePrerequisites = ReactiveCommand.CreateFromTask<bool>(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites));
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled;
|
||||
_pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled;
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled;
|
||||
_pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public ReactiveCommand<Unit, Unit> OpenSettings { get; }
|
||||
public ReactiveCommand<Unit, Unit> InstallPrerequisites { get; }
|
||||
public ReactiveCommand<bool, Unit> RemovePrerequisites { get; }
|
||||
|
||||
public ObservableCollection<PluginFeatureViewModel> PluginFeatures { get; }
|
||||
|
||||
public Plugin Plugin
|
||||
{
|
||||
get => _plugin;
|
||||
set => SetAndNotify(ref _plugin, value);
|
||||
set => RaiseAndSetIfChanged(ref _plugin, value);
|
||||
}
|
||||
|
||||
public bool Enabling
|
||||
{
|
||||
get => _enabling;
|
||||
set => SetAndNotify(ref _enabling, value);
|
||||
set => RaiseAndSetIfChanged(ref _enabling, value);
|
||||
}
|
||||
|
||||
|
||||
public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name;
|
||||
public bool CanOpenSettings => IsEnabled && Plugin.ConfigurationDialog != null;
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
@@ -73,7 +100,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
get => _isSettingsPopupOpen;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _isSettingsPopupOpen, value)) return;
|
||||
if (!RaiseAndSetIfChanged(ref _isSettingsPopupOpen, value)) return;
|
||||
CheckPrerequisites();
|
||||
}
|
||||
}
|
||||
@@ -81,29 +108,31 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
public bool CanInstallPrerequisites
|
||||
{
|
||||
get => _canInstallPrerequisites;
|
||||
set => SetAndNotify(ref _canInstallPrerequisites, value);
|
||||
set => RaiseAndSetIfChanged(ref _canInstallPrerequisites, value);
|
||||
}
|
||||
|
||||
public bool CanRemovePrerequisites
|
||||
{
|
||||
get => _canRemovePrerequisites;
|
||||
set => SetAndNotify(ref _canRemovePrerequisites, value);
|
||||
set => RaiseAndSetIfChanged(ref _canRemovePrerequisites, value);
|
||||
}
|
||||
|
||||
public void OpenSettings()
|
||||
private void ExecuteOpenSettings()
|
||||
{
|
||||
PluginConfigurationDialog configurationViewModel = (PluginConfigurationDialog) Plugin.ConfigurationDialog;
|
||||
if (configurationViewModel == null)
|
||||
if (Plugin.ConfigurationDialog == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
PluginConfigurationViewModel viewModel = (PluginConfigurationViewModel) Plugin.Kernel.Get(configurationViewModel.Type);
|
||||
_windowManager.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
|
||||
PluginConfigurationViewModel? viewModel = Plugin.Kernel!.Get(Plugin.ConfigurationDialog.Type) as PluginConfigurationViewModel;
|
||||
if (viewModel == null)
|
||||
throw new ArtemisUIException($"The type of a plugin configuration dialog must inherit {nameof(PluginConfigurationViewModel)}");
|
||||
|
||||
_windowService.ShowWindow(new PluginSettingsWindowViewModel(viewModel));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_dialogService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
|
||||
_windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's settings window", e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -112,11 +141,11 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Plugin.Directory.FullName);
|
||||
Utilities.OpenFolder(Plugin.Directory.FullName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_dialogService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
|
||||
_windowService.ShowExceptionDialog("Welp, we couldn't open the device's plugin folder for you", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,44 +153,44 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
{
|
||||
bool wasEnabled = IsEnabled;
|
||||
|
||||
_pluginManagementService.UnloadPlugin(Plugin);
|
||||
Items.Clear();
|
||||
await Task.Run(() => _pluginManagementService.UnloadPlugin(Plugin));
|
||||
|
||||
PluginFeatures.Clear();
|
||||
|
||||
Plugin = _pluginManagementService.LoadPlugin(Plugin.Directory);
|
||||
foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features)
|
||||
Items.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
|
||||
PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false));
|
||||
|
||||
if (wasEnabled)
|
||||
await UpdateEnabled(true);
|
||||
|
||||
_messageService.ShowMessage("Reloaded plugin.");
|
||||
_notificationService.CreateNotification().WithTitle("Reloaded plugin.").Show();
|
||||
}
|
||||
|
||||
public async Task InstallPrerequisites()
|
||||
public async Task ExecuteInstallPrerequisites()
|
||||
{
|
||||
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);
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
|
||||
}
|
||||
|
||||
public async Task RemovePrerequisites(bool forPluginRemoval = false)
|
||||
public async Task ExecuteRemovePrerequisites(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));
|
||||
await PluginPrerequisitesUninstallDialogViewModel.Show(_windowService, subjects, forPluginRemoval ? "Skip, remove plugin" : "Cancel");
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveSettings()
|
||||
{
|
||||
bool confirmed = await _dialogService.ShowConfirmDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Clear plugin settings", "Are you sure you want to clear the settings of this plugin?");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
@@ -175,12 +204,12 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
if (wasEnabled)
|
||||
await UpdateEnabled(true);
|
||||
|
||||
_messageService.ShowMessage("Cleared plugin settings.");
|
||||
_notificationService.CreateNotification().WithTitle("Cleared plugin settings.").Show();
|
||||
}
|
||||
|
||||
public async Task Remove()
|
||||
{
|
||||
bool confirmed = await _dialogService.ShowConfirmDialog("Remove plugin", "Are you sure you want to remove this plugin?");
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Remove plugin", "Are you sure you want to remove this plugin?");
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
@@ -188,50 +217,48 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
List<IPrerequisitesSubject> subjects = new() {Plugin.Info};
|
||||
subjects.AddRange(Plugin.Features);
|
||||
if (subjects.Any(s => s.Prerequisites.Any(p => p.UninstallActions.Any())))
|
||||
await RemovePrerequisites(true);
|
||||
await ExecuteRemovePrerequisites(true);
|
||||
|
||||
try
|
||||
{
|
||||
_pluginManagementService.RemovePlugin(Plugin, false);
|
||||
((PluginSettingsTabViewModel) Parent).GetPluginInstances();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_dialogService.ShowExceptionDialog("Failed to remove plugin", e);
|
||||
_windowService.ShowExceptionDialog("Failed to remove plugin", e);
|
||||
throw;
|
||||
}
|
||||
|
||||
_messageService.ShowMessage("Removed plugin.");
|
||||
_notificationService.CreateNotification().WithTitle("Removed plugin.").Show();
|
||||
}
|
||||
|
||||
public void ShowLogsFolder()
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(Environment.GetEnvironmentVariable("WINDIR") + @"\explorer.exe", Path.Combine(Constants.DataFolder, "Logs"));
|
||||
Utilities.OpenFolder(Constants.LogsFolder);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_dialogService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
|
||||
_windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenUri(Uri uri)
|
||||
{
|
||||
Core.Utilities.OpenUrl(uri.ToString());
|
||||
Utilities.OpenUrl(uri.ToString());
|
||||
}
|
||||
|
||||
private void PluginManagementServiceOnPluginToggled(object sender, PluginEventArgs e)
|
||||
private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
private async Task UpdateEnabled(bool enable)
|
||||
{
|
||||
if (IsEnabled == enable)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -241,7 +268,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
|
||||
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?");
|
||||
bool confirmed = await _windowService.ShowConfirmContentDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
|
||||
if (!confirmed)
|
||||
{
|
||||
CancelEnable();
|
||||
@@ -255,7 +282,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
|
||||
if (subjects.Any(s => !s.ArePrerequisitesMet()))
|
||||
{
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_dialogService, subjects);
|
||||
await PluginPrerequisitesInstallDialogViewModel.Show(_windowService, subjects);
|
||||
if (!subjects.All(s => s.ArePrerequisitesMet()))
|
||||
{
|
||||
CancelEnable();
|
||||
@@ -263,7 +290,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Run(() =>
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -271,7 +298,10 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_messageService.ShowMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}", "VIEW LOGS", ShowLogsFolder);
|
||||
await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification()
|
||||
.WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}")
|
||||
.HavingButton(b => b.WithText("View logs").WithAction(ShowLogsFolder))
|
||||
.Show());
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -280,17 +310,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_pluginManagementService.DisablePlugin(Plugin, true);
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
private void CancelEnable()
|
||||
{
|
||||
Enabling = false;
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
this.RaisePropertyChanged(nameof(IsEnabled));
|
||||
}
|
||||
|
||||
private void CheckPrerequisites()
|
||||
@@ -300,26 +330,5 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<controls:CoreWindow 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:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Plugins.PluginSettingsWindowView"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="{Binding DisplayName}"
|
||||
Width="800"
|
||||
Height="800"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
<Panel>
|
||||
<ContentControl Content="{Binding ConfigurationViewModel}"></ContentControl>
|
||||
<StackPanel Classes="notification-container" Name="NotificationContainer" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
|
||||
</Panel>
|
||||
</controls:CoreWindow>
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginSettingsWindowView : ReactiveCoreWindow<PluginSettingsWindowViewModel>
|
||||
{
|
||||
public PluginSettingsWindowView()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Observable.FromEventPattern(
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested += x,
|
||||
x => ViewModel!.ConfigurationViewModel.CloseRequested -= x
|
||||
)
|
||||
.Subscribe(_ => Close())
|
||||
.DisposeWith(disposables);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginSettingsWindowViewModel : ActivatableViewModelBase
|
||||
{
|
||||
public PluginSettingsWindowViewModel(PluginConfigurationViewModel configurationViewModel)
|
||||
{
|
||||
ConfigurationViewModel = configurationViewModel ?? throw new ArgumentNullException(nameof(configurationViewModel));
|
||||
Plugin = configurationViewModel.Plugin;
|
||||
|
||||
DisplayName = $"{Plugin.Info.Name} | Settings";
|
||||
}
|
||||
|
||||
public PluginConfigurationViewModel ConfigurationViewModel { get; }
|
||||
public Plugin Plugin { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.LayerBrushPresetView"
|
||||
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"
|
||||
xmlns:layerBrushes="clr-namespace:Artemis.Core.LayerBrushes;assembly=Artemis.Core"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
|
||||
Select a brush preset
|
||||
</TextBlock>
|
||||
|
||||
<ListBox ItemsSource="{Binding Presets}" SelectedItem="{Binding SelectedPreset}" HorizontalContentAlignment="Stretch" Margin="0 8 0 16">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type layerBrushes:ILayerBrushPreset}">
|
||||
<Border x:Name="Border" Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<shared:ArtemisIcon Icon="{Binding Icon}" VerticalAlignment="Center" Width="30" Height="30"/>
|
||||
<StackPanel Margin="8 0 0 0" Grid.Column="1">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding Name}" />
|
||||
<TextBlock Text="{Binding Description}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
|
||||
USE DEFAULT SETTINGS
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,43 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
||||
{
|
||||
public class LayerBrushPresetViewModel : DialogViewModelBase
|
||||
{
|
||||
private readonly BaseLayerBrush _layerBrush;
|
||||
private ILayerBrushPreset _selectedPreset;
|
||||
|
||||
public LayerBrushPresetViewModel(BaseLayerBrush layerBrush)
|
||||
{
|
||||
_layerBrush = layerBrush;
|
||||
Presets = new BindableCollection<ILayerBrushPreset>();
|
||||
Presets.AddRange(layerBrush.Presets);
|
||||
}
|
||||
|
||||
public BindableCollection<ILayerBrushPreset> Presets { get; }
|
||||
|
||||
public ILayerBrushPreset SelectedPreset
|
||||
{
|
||||
get => _selectedPreset;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _selectedPreset, value);
|
||||
SelectPreset(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectPreset(ILayerBrushPreset preset)
|
||||
{
|
||||
_layerBrush.BaseProperties?.ResetAllLayerProperties();
|
||||
preset.Apply();
|
||||
Execute.OnUIThreadAsync(async () =>
|
||||
{
|
||||
await Task.Delay(250);
|
||||
Session?.Close(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.Dialogs.RenameView"
|
||||
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"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="213.053" d:DesignWidth="254.425">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
|
||||
<Run Text="Rename" />
|
||||
<Run Text="{Binding Subject, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
|
||||
<TextBox materialDesign:HintAssist.Hint="Element name"
|
||||
Margin="0 8 0 16"
|
||||
Width="300"
|
||||
Style="{StaticResource MaterialDesignFilledTextBox}"
|
||||
Text="{Binding ElementName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Loaded="FrameworkElement_OnLoaded" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0 8 0 0">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 0 8 0" Command="{s:Action Cancel}">
|
||||
CANCEL
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 0 0 0" Command="{s:Action Accept}">
|
||||
ACCEPT
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for RenameView.xaml
|
||||
/// </summary>
|
||||
public partial class RenameView : UserControl
|
||||
{
|
||||
public RenameView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void FrameworkElement_OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
TextBox textBox = (TextBox) sender;
|
||||
Keyboard.Focus(textBox);
|
||||
textBox.SelectAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Dialogs
|
||||
{
|
||||
public class RenameViewModel : DialogViewModelBase
|
||||
{
|
||||
private string _elementName;
|
||||
|
||||
public RenameViewModel(IModelValidator<RenameViewModel> validator, string subject, string currentName) : base(validator)
|
||||
{
|
||||
Subject = subject;
|
||||
ElementName = currentName;
|
||||
}
|
||||
|
||||
public string Subject { get; }
|
||||
|
||||
public string ElementName
|
||||
{
|
||||
get => _elementName;
|
||||
set => SetAndNotify(ref _elementName, value);
|
||||
}
|
||||
|
||||
public async Task Accept()
|
||||
{
|
||||
await ValidateAsync();
|
||||
|
||||
if (HasErrors)
|
||||
return;
|
||||
|
||||
Session.Close(ElementName);
|
||||
}
|
||||
}
|
||||
|
||||
public class ProfileElementRenameViewModelValidator : AbstractValidator<RenameViewModel>
|
||||
{
|
||||
public ProfileElementRenameViewModelValidator()
|
||||
{
|
||||
RuleFor(m => m.ElementName).NotEmpty().WithMessage("Element name may not be empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +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:s="https://github.com/canton7/Stylet"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:displayConditions="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayConditions"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.DisplayConditionsView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type displayConditions:DisplayConditionsViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<converters:ComparisonConverter x:Key="ComparisonConverter" />
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0" Margin="0 -2">
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" VerticalAlignment="Center">
|
||||
Display conditions
|
||||
</TextBlock>
|
||||
|
||||
<materialDesign:ColorZone Mode="Standard" CornerRadius="3" Margin="0 -2" HorizontalAlignment="Right">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<RadioButton Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding Path=DisplayConditionType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static displayConditions:DisplayConditionType.None}}"
|
||||
MinWidth="0"
|
||||
Padding="10 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
No condition, the element is always displayed
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="No" VerticalAlignment="Center" Margin="0 0 2 -3" />
|
||||
NONE
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding Path=DisplayConditionType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static displayConditions:DisplayConditionType.Static}}"
|
||||
MinWidth="0"
|
||||
Padding="10 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
A condition that constantly checks and is either 'on' or 'off'
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="Check" VerticalAlignment="Center" Margin="0 0 2 -3" />
|
||||
CONSTANT
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="2"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding Path=DisplayConditionType, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static displayConditions:DisplayConditionType.Events}}"
|
||||
MinWidth="0"
|
||||
Padding="10 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
A condition triggers on a selectable event
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="LightningBolt" VerticalAlignment="Center" Margin="0 0 2 -3" />
|
||||
EVENT
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
</materialDesign:ColorZone>
|
||||
</DockPanel>
|
||||
|
||||
<Separator Grid.Row="1" Grid.Column="0" Style="{StaticResource MaterialDesignDarkSeparator}" Margin="-2 0" />
|
||||
|
||||
<ContentControl Grid.Row="2"
|
||||
s:View.Model="{Binding ActiveItem}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
Visibility="{Binding ActiveItem, Converter={StaticResource NullToVisibilityConverter}}"/>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding ActiveItem, ConverterParameter=Inverted, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" TextWrapping="Wrap" TextAlignment="Center" Margin="0 10">
|
||||
Display conditions disabled
|
||||
<materialDesign:PackIcon Kind="No" Margin="0 0 0 -6" Width="30" Height="30" VerticalAlignment="Center" />
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" TextWrapping="Wrap" TextAlignment="Center">
|
||||
This element will always display.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,114 +0,0 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions
|
||||
{
|
||||
public class DisplayConditionsViewModel : Conductor<Screen>, IProfileEditorPanelViewModel
|
||||
{
|
||||
private readonly IConditionVmFactory _conditionVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private DisplayConditionType _displayConditionType;
|
||||
private StaticCondition _staticCondition;
|
||||
private EventCondition _eventCondition;
|
||||
|
||||
public DisplayConditionsViewModel(IProfileEditorService profileEditorService, IConditionVmFactory conditionVmFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_conditionVmFactory = conditionVmFactory;
|
||||
}
|
||||
|
||||
public DisplayConditionType DisplayConditionType
|
||||
{
|
||||
get => _displayConditionType;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _displayConditionType, value)) return;
|
||||
ChangeConditionType();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_profileEditorService.SelectedProfileElementChanged += ProfileEditorServiceOnSelectedProfileElementChanged;
|
||||
Update(_profileEditorService.SelectedProfileElement);
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
_profileEditorService.SelectedProfileElementChanged -= ProfileEditorServiceOnSelectedProfileElementChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
private void ChangeConditionType()
|
||||
{
|
||||
if (_profileEditorService.SelectedProfileElement == null)
|
||||
return;
|
||||
|
||||
// Keep the old condition around in case the user changes their mind
|
||||
if (_profileEditorService.SelectedProfileElement.DisplayCondition is StaticCondition staticCondition)
|
||||
_staticCondition = staticCondition;
|
||||
else if (_profileEditorService.SelectedProfileElement.DisplayCondition is EventCondition eventCondition)
|
||||
_eventCondition = eventCondition;
|
||||
|
||||
// If we have the old condition around put it back
|
||||
if (DisplayConditionType == DisplayConditionType.Static)
|
||||
_profileEditorService.SelectedProfileElement.DisplayCondition = _staticCondition ?? new StaticCondition(_profileEditorService.SelectedProfileElement);
|
||||
else if (DisplayConditionType == DisplayConditionType.Events)
|
||||
_profileEditorService.SelectedProfileElement.DisplayCondition = _eventCondition ?? new EventCondition(_profileEditorService.SelectedProfileElement);
|
||||
else
|
||||
_profileEditorService.SelectedProfileElement.DisplayCondition = null;
|
||||
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
Update(_profileEditorService.SelectedProfileElement);
|
||||
}
|
||||
|
||||
private void Update(RenderProfileElement renderProfileElement)
|
||||
{
|
||||
if (renderProfileElement == null)
|
||||
{
|
||||
ActiveItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderProfileElement.DisplayCondition is StaticCondition staticCondition)
|
||||
{
|
||||
ActiveItem = _conditionVmFactory.StaticConditionViewModel(staticCondition);
|
||||
_displayConditionType = DisplayConditionType.Static;
|
||||
}
|
||||
else if (renderProfileElement.DisplayCondition is EventCondition eventsCondition)
|
||||
{
|
||||
ActiveItem = _conditionVmFactory.EventConditionViewModel(eventsCondition);
|
||||
_displayConditionType = DisplayConditionType.Events;
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveItem = null;
|
||||
_displayConditionType = DisplayConditionType.None;
|
||||
}
|
||||
|
||||
NotifyOfPropertyChange(nameof(DisplayConditionType));
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
|
||||
{
|
||||
if (_staticCondition != null && e.PreviousRenderProfileElement?.DisplayCondition != _staticCondition)
|
||||
_staticCondition.Dispose();
|
||||
if (_eventCondition != null && e.PreviousRenderProfileElement?.DisplayCondition != _eventCondition)
|
||||
_eventCondition.Dispose();
|
||||
|
||||
Update(e.RenderProfileElement);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DisplayConditionType
|
||||
{
|
||||
None,
|
||||
Static,
|
||||
Events
|
||||
}
|
||||
}
|
||||
@@ -1,204 +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.ProfileEditor.DisplayConditions.Event"
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
|
||||
xmlns:Shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared" x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event.EventConditionView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type local:EventConditionViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<Shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<converters:ComparisonConverter x:Key="ComparisonConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="22" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<shared:DataModelPicker Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="0 0 0 5"
|
||||
ButtonBrush="DarkGoldenrod"
|
||||
DataModelPath="{Binding EventCondition.EventPath}"
|
||||
DataModelPathSelected="{s:Action DataModelPathSelected}"
|
||||
FilterTypes="{Binding FilterTypes}"
|
||||
ShowFullPath="True"
|
||||
Modules="{Binding Modules}" />
|
||||
|
||||
<CheckBox Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
IsChecked="{Binding TriggerConditionally}"
|
||||
Content="Conditional trigger"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="When enabled, the element will only trigger if the script evaluates to true"
|
||||
Style="{StaticResource MaterialDesignDarkCheckBox}" />
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
MouseUp="{s:Action ScriptGridMouseUp}"
|
||||
Cursor="Hand"
|
||||
Visibility="{Binding EventCondition.Script, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Normal, Mode=OneWay}">
|
||||
<controls:VisualScriptPresenter Script="{Binding EventCondition.Script}" AutoFitScript="True" />
|
||||
<Border VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Opacity="0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding Color, Source={StaticResource MaterialDesignCardBackground}}" Opacity="0.75" />
|
||||
</Border.Background>
|
||||
<Border.Style>
|
||||
<Style>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Grid}, Mode=FindAncestor}}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" VerticalAlignment="Center" TextAlignment="Center">
|
||||
Click to edit script
|
||||
<materialDesign:PackIcon Kind="OpenInNew" Margin="0 0 0 -6 " Width="30" Height="30" VerticalAlignment="Center" />
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Visibility="{Binding EventCondition.Script, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted, Mode=OneWay}">
|
||||
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" TextWrapping="Wrap" TextAlignment="Center" Margin="0 10">
|
||||
Conditional trigger disabled
|
||||
<materialDesign:PackIcon Kind="No" Margin="0 0 0 -6" Width="30" Height="30" VerticalAlignment="Center" />
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" TextWrapping="Wrap" TextAlignment="Center">
|
||||
This element will trigger any time the event fires.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<!-- Trigger mode -->
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Trigger mode" VerticalAlignment="Center">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||
<TextBlock>
|
||||
<Run Text="Configure how the layer should act when the event(s) trigger" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
|
||||
<materialDesign:ColorZone Grid.Row="2" Grid.Column="0" Mode="Standard" CornerRadius="3">
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<RadioButton Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Restart}, Converter={StaticResource ComparisonConverter}}">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
<Run Text="Stop the current run and restart the timeline" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<InlineUIContainer>
|
||||
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" RESTART" />
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Toggle}, Converter={StaticResource ComparisonConverter}}">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
<Run Text="Repeat the timeline until the event fires again" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<InlineUIContainer>
|
||||
<materialDesign:PackIcon Kind="TrafficLight" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" TOGGLE" />
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="2"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Ignore}, Converter={StaticResource ComparisonConverter}}">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
<Run Text="Ignore subsequent event fires until the timeline finishes" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<InlineUIContainer>
|
||||
<materialDesign:PackIcon Kind="EarHearingOff" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" IGNORE" />
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="3"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding EventOverlapMode, ConverterParameter={x:Static core:TimeLineEventOverlapMode.Copy}, Converter={StaticResource ComparisonConverter}}">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
<Run Text="Play another copy of the timeline on top of the current run" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<InlineUIContainer>
|
||||
<materialDesign:PackIcon Kind="ContentCopy" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
</InlineUIContainer>
|
||||
<Run Text=" COPY" />
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
</materialDesign:ColorZone>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Event
|
||||
{
|
||||
public class EventConditionViewModel : Screen
|
||||
{
|
||||
private readonly INodeVmFactory _nodeVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly IWindowManager _windowManager;
|
||||
private NodeScript<bool> _oldScript;
|
||||
|
||||
public EventConditionViewModel(EventCondition eventCondition, IProfileEditorService profileEditorService, IWindowManager windowManager, INodeVmFactory nodeVmFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_windowManager = windowManager;
|
||||
_nodeVmFactory = nodeVmFactory;
|
||||
EventCondition = eventCondition;
|
||||
|
||||
FilterTypes = new BindableCollection<Type> {typeof(IDataModelEvent)};
|
||||
if (_profileEditorService.SelectedProfileConfiguration?.Module != null)
|
||||
Modules = new BindableCollection<Module> {_profileEditorService.SelectedProfileConfiguration.Module};
|
||||
}
|
||||
|
||||
public EventCondition EventCondition { get; }
|
||||
public BindableCollection<Type> FilterTypes { get; }
|
||||
public BindableCollection<Module> Modules { get; }
|
||||
|
||||
public TimeLineEventOverlapMode EventOverlapMode
|
||||
{
|
||||
get => EventCondition.EventOverlapMode;
|
||||
set
|
||||
{
|
||||
if (EventCondition.EventOverlapMode == value) return;
|
||||
EventCondition.EventOverlapMode = value;
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
public bool TriggerConditionally
|
||||
{
|
||||
get => EventCondition.Script != null;
|
||||
set
|
||||
{
|
||||
if (EventCondition.Script != null)
|
||||
{
|
||||
_oldScript = EventCondition.Script;
|
||||
EventCondition.Script = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_oldScript != null)
|
||||
{
|
||||
EventCondition.Script = _oldScript;
|
||||
_oldScript = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
EventCondition.CreateEmptyNodeScript();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
_oldScript?.Dispose();
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void DataModelPathSelected(object sender, DataModelSelectedEventArgs e)
|
||||
{
|
||||
EventCondition.UpdateEventNode();
|
||||
DisplayName = EventCondition.EventPath?.Segments.LastOrDefault()?.GetPropertyDescription()?.Name ?? "Invalid event";
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
|
||||
public void ScriptGridMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
_windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(EventCondition.Script));
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.DisplayConditions.Static.StaticConditionView"
|
||||
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.ProfileEditor.DisplayConditions.Static"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
|
||||
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 {x:Type local:StaticConditionViewModel}}">
|
||||
<UserControl.Resources>
|
||||
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" MouseUp="{s:Action ScriptGridMouseUp}" Cursor="Hand">
|
||||
<controls:VisualScriptPresenter Script="{Binding StaticCondition.Script}" AutoFitScript="True"/>
|
||||
<Border Opacity="0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="{Binding Color, Source={StaticResource MaterialDesignCardBackground}}" Opacity="0.75" />
|
||||
</Border.Background>
|
||||
<Border.Style>
|
||||
<Style>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Grid}, Mode=FindAncestor}}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
<DataTrigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</DataTrigger.ExitActions>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline5TextBlock}" VerticalAlignment="Center" TextAlignment="Center">
|
||||
Click to edit script
|
||||
<materialDesign:PackIcon Kind="OpenInNew" Margin="0 0 0 -6 " Width="30" Height="30" VerticalAlignment="Center" />
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="22" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" MinWidth="140" />
|
||||
<ColumnDefinition Width="*" MinWidth="170" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Play mode -->
|
||||
<TextBlock Grid.Column="0" Text="Play mode" VerticalAlignment="Center">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||
<TextBlock>
|
||||
Configure how the layer should act while the conditions above are met
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
<materialDesign:ColorZone Grid.Row="1" Grid.Column="0" Mode="Standard" CornerRadius="3" Margin="0 0 2 0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<RadioButton Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding DisplayContinuously}"
|
||||
MinWidth="0"
|
||||
Padding="5 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
Continue repeating the main segment of the timeline while the condition is met
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="Repeat" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
REPEAT
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding DisplayContinuously, Converter={StaticResource InverseBooleanConverter}}"
|
||||
MinWidth="0"
|
||||
Padding="5 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
Only play the timeline once when the condition is met
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="StopwatchOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
ONCE
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
</materialDesign:ColorZone>
|
||||
|
||||
<!-- Stop mode -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Stop mode">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-30">
|
||||
<TextBlock>
|
||||
Configure how the layer should act when the conditions above are no longer met
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
<materialDesign:ColorZone Grid.Row="1" Grid.Column="1" Mode="Standard" CornerRadius="3" Margin="2 0 0 0" HorizontalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition MinWidth="100" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<RadioButton Grid.Column="0"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding AlwaysFinishTimeline}"
|
||||
MinWidth="0"
|
||||
Padding="5 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
When conditions are no longer met, finish the the current run of the main timeline
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="PlayOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
FINISH
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
<RadioButton Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignTabRadioButton}"
|
||||
IsChecked="{Binding AlwaysFinishTimeline, Converter={StaticResource InverseBooleanConverter}}"
|
||||
MinWidth="0"
|
||||
Padding="5 0">
|
||||
<RadioButton.ToolTip>
|
||||
<ToolTip Placement="Center" VerticalOffset="-40">
|
||||
<TextBlock>
|
||||
When conditions are no longer met, skip to the end segment of the timeline
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</RadioButton.ToolTip>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<materialDesign:PackIcon Kind="SkipNextOutline" VerticalAlignment="Center" Margin="-3 0 0 -3" />
|
||||
SKIP TO END
|
||||
</TextBlock>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
</materialDesign:ColorZone>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
using System.Windows.Input;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.DisplayConditions.Static
|
||||
{
|
||||
public class StaticConditionViewModel : Screen
|
||||
{
|
||||
private readonly IWindowManager _windowManager;
|
||||
private readonly INodeVmFactory _nodeVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly RenderProfileElement _renderProfileElement;
|
||||
|
||||
public StaticConditionViewModel(StaticCondition staticCondition, IWindowManager windowManager, INodeVmFactory nodeVmFactory, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_windowManager = windowManager;
|
||||
_nodeVmFactory = nodeVmFactory;
|
||||
_profileEditorService = profileEditorService;
|
||||
_renderProfileElement = (RenderProfileElement) staticCondition.ProfileElement;
|
||||
StaticCondition = staticCondition;
|
||||
}
|
||||
|
||||
public StaticCondition StaticCondition { get; }
|
||||
|
||||
public bool DisplayContinuously
|
||||
{
|
||||
get => _renderProfileElement.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
||||
set
|
||||
{
|
||||
TimelinePlayMode playMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
|
||||
if (_renderProfileElement.Timeline.PlayMode == playMode) return;
|
||||
_renderProfileElement.Timeline.PlayMode = playMode;
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
public bool AlwaysFinishTimeline
|
||||
{
|
||||
get => _renderProfileElement?.Timeline.StopMode == TimelineStopMode.Finish;
|
||||
set
|
||||
{
|
||||
TimelineStopMode stopMode = value ? TimelineStopMode.Finish : TimelineStopMode.SkipToEnd;
|
||||
if (_renderProfileElement.Timeline.StopMode == stopMode) return;
|
||||
_renderProfileElement.Timeline.StopMode = stopMode;
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
|
||||
public void ScriptGridMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
return;
|
||||
|
||||
_windowManager.ShowDialog(_nodeVmFactory.NodeScriptWindowViewModel(StaticCondition.Script));
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor
|
||||
{
|
||||
public interface IProfileEditorPanelViewModel : IScreen
|
||||
{
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.DataBindingsView"
|
||||
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"
|
||||
xmlns:controls="clr-namespace:Artemis.VisualScripting.Editor.Controls;assembly=Artemis.VisualScripting"
|
||||
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type layerProperties:DataBindingsViewModel}}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
VerticalAlignment="Center"
|
||||
Margin="15 0"
|
||||
Text="{Binding DataBinding.BaseLayerProperty.PropertyDescription.Name}" />
|
||||
<CheckBox Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding DataBindingEnabled}">
|
||||
Enable data binding
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
|
||||
<controls:VisualScriptPresenter Grid.Row="1"
|
||||
AvailableNodes="{Binding AvailableNodes}"
|
||||
Script="{Binding DataBinding.Script}"
|
||||
AlwaysShowValues="{Binding AlwaysShowValues.Value}"/>
|
||||
<Border Grid.Row="1" Background="{StaticResource MaterialDesignToolBarBackground}"
|
||||
Visibility="{Binding DataBindingEnabled, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="16">
|
||||
<materialDesign:PackIcon Kind="TransitConnectionVariant" Width="100" Height="100" HorizontalAlignment="Center" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 25">
|
||||
Enable this data binding to use nodes!
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
|
||||
When you enable data bindings you can no longer use keyframes or normal values for this property. <LineBreak />
|
||||
Instead you'll be using our node scripting system to set a value based on the data model or values of other layers/folders.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings
|
||||
{
|
||||
public class DataBindingsViewModel : Screen
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private IDataBinding _dataBinding;
|
||||
|
||||
public DataBindingsViewModel(IProfileEditorService profileEditorService, INodeService nodeService, ISettingsService settingsService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
AvailableNodes = nodeService.AvailableNodes.ToList();
|
||||
AlwaysShowValues = settingsService.GetSetting("ProfileEditor.AlwaysShowValues", true);
|
||||
}
|
||||
|
||||
public List<NodeData> AvailableNodes { get; }
|
||||
public PluginSetting<bool> AlwaysShowValues { get; }
|
||||
|
||||
public IDataBinding DataBinding
|
||||
{
|
||||
get => _dataBinding;
|
||||
set => SetAndNotify(ref _dataBinding, value);
|
||||
}
|
||||
|
||||
public bool DataBindingEnabled
|
||||
{
|
||||
get => _dataBinding?.IsEnabled ?? false;
|
||||
set
|
||||
{
|
||||
if (_dataBinding != null)
|
||||
_dataBinding.IsEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
|
||||
{
|
||||
SubscribeToSelectedDataBinding();
|
||||
}
|
||||
|
||||
private void SubscribeToSelectedDataBinding()
|
||||
{
|
||||
if (DataBinding != null)
|
||||
{
|
||||
DataBinding.DataBindingEnabled -= DataBindingOnDataBindingToggled;
|
||||
DataBinding.DataBindingDisabled -= DataBindingOnDataBindingToggled;
|
||||
}
|
||||
|
||||
DataBinding = _profileEditorService.SelectedDataBinding;
|
||||
if (DataBinding != null)
|
||||
{
|
||||
DataBinding.DataBindingEnabled += DataBindingOnDataBindingToggled;
|
||||
DataBinding.DataBindingDisabled += DataBindingOnDataBindingToggled;
|
||||
|
||||
OnPropertyChanged(nameof(DataBindingEnabled));
|
||||
}
|
||||
}
|
||||
|
||||
private void DataBindingOnDataBindingToggled(object sender, DataBindingEventArgs e)
|
||||
{
|
||||
OnPropertyChanged(nameof(DataBindingEnabled));
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_profileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
|
||||
SubscribeToSelectedDataBinding();
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
_profileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects.EffectsView"
|
||||
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.ProfileEditor.LayerProperties.LayerEffects"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
|
||||
xmlns:layerEffect="clr-namespace:Artemis.Core.LayerEffects;assembly=Artemis.Core"
|
||||
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:EffectsViewModel}">
|
||||
<Grid Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Margin="16"
|
||||
Visibility="{Binding HasLayerEffectDescriptors, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
|
||||
<materialDesign:PackIcon Kind="AutoAwesome" Width="80" Height="80" HorizontalAlignment="Center" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 15">
|
||||
Looks like you have no effects installed or enabled!
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center">
|
||||
Effects will apply some sort of post- or pre-processing to layers or even entire folders. <LineBreak />
|
||||
Think of things like blur, black & white but also audio visualization etc.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedLayerEffectDescriptor}" HorizontalContentAlignment="Stretch"
|
||||
Visibility="{Binding HasLayerEffectDescriptors, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="behaviors:MouseBehavior.MouseUpCommand" Value="{x:Static materialDesign:Transitioner.MoveFirstCommand}" />
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type layerEffect:LayerEffectDescriptor}">
|
||||
<Border Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<shared:ArtemisIcon Icon="{Binding Icon}" Width="20" Height="20" VerticalAlignment="Center" />
|
||||
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding DisplayName}" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects
|
||||
{
|
||||
public class EffectsViewModel : Conductor<LayerEffectDescriptor>.Collection.AllActive
|
||||
{
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private LayerEffectDescriptor _selectedLayerEffectDescriptor;
|
||||
|
||||
public EffectsViewModel(LayerPropertiesViewModel layerPropertiesViewModel, IPluginManagementService pluginManagementService, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_pluginManagementService = pluginManagementService;
|
||||
_profileEditorService = profileEditorService;
|
||||
LayerPropertiesViewModel = layerPropertiesViewModel;
|
||||
PropertyChanged += HandleSelectedLayerEffectChanged;
|
||||
}
|
||||
|
||||
public LayerPropertiesViewModel LayerPropertiesViewModel { get; }
|
||||
public bool HasLayerEffectDescriptors => Items.Any();
|
||||
|
||||
public LayerEffectDescriptor SelectedLayerEffectDescriptor
|
||||
{
|
||||
get => _selectedLayerEffectDescriptor;
|
||||
set => SetAndNotify(ref _selectedLayerEffectDescriptor, value);
|
||||
}
|
||||
|
||||
public void PopulateDescriptors()
|
||||
{
|
||||
List<LayerEffectProvider> layerBrushProviders = _pluginManagementService.GetFeaturesOfType<LayerEffectProvider>();
|
||||
List<LayerEffectDescriptor> descriptors = layerBrushProviders.SelectMany(l => l.LayerEffectDescriptors).ToList();
|
||||
Items.AddRange(descriptors.Except(Items));
|
||||
Items.RemoveRange(Items.Except(descriptors));
|
||||
|
||||
// Sort by display name
|
||||
int index = 0;
|
||||
foreach (LayerEffectDescriptor layerEffectDescriptor in Items.OrderBy(d => d.DisplayName).ToList())
|
||||
{
|
||||
if (Items.IndexOf(layerEffectDescriptor) != index)
|
||||
((BindableCollection<LayerEffectDescriptor>) Items).Move(Items.IndexOf(layerEffectDescriptor), index);
|
||||
index++;
|
||||
}
|
||||
|
||||
SelectedLayerEffectDescriptor = null;
|
||||
NotifyOfPropertyChange(nameof(HasLayerEffectDescriptors));
|
||||
}
|
||||
|
||||
private void HandleSelectedLayerEffectChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
RenderProfileElement renderElement;
|
||||
if (LayerPropertiesViewModel.SelectedLayer != null)
|
||||
renderElement = LayerPropertiesViewModel.SelectedLayer;
|
||||
else if (LayerPropertiesViewModel.SelectedFolder != null)
|
||||
renderElement = LayerPropertiesViewModel.SelectedFolder;
|
||||
else
|
||||
return;
|
||||
|
||||
if (e.PropertyName == nameof(SelectedLayerEffectDescriptor) && SelectedLayerEffectDescriptor != null)
|
||||
{
|
||||
// Let the fancy animation run
|
||||
Execute.PostToUIThread(async () =>
|
||||
{
|
||||
await Task.Delay(500);
|
||||
renderElement.AddLayerEffect(SelectedLayerEffectDescriptor);
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,470 +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:s="https://github.com/canton7/Stylet"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:behaviors="clr-namespace:Artemis.UI.Behaviors"
|
||||
xmlns:Converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:utilities="clr-namespace:Artemis.UI.Utilities"
|
||||
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties"
|
||||
xmlns:controls="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Controls"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerPropertiesView"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance {x:Type layerProperties:LayerPropertiesViewModel}}"
|
||||
behaviors:InputBindingBehavior.PropagateInputBindingsToWindow="True">
|
||||
<UserControl.Resources>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||
<Converters:NullToBooleanConverter x:Key="NullToBooleanConverter" />
|
||||
<utilities:BindingProxy x:Key="DataContextProxy" Data="{Binding}" />
|
||||
<Style x:Key="SvStyle" TargetType="{x:Type ScrollViewer}">
|
||||
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ScrollViewer}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ScrollContentPresenter Grid.ColumnSpan="2" Grid.RowSpan="2" />
|
||||
<ScrollBar x:Name="PART_VerticalScrollBar"
|
||||
HorizontalAlignment="Right"
|
||||
Opacity="0.5"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Value="{TemplateBinding VerticalOffset}"
|
||||
Maximum="{TemplateBinding ScrollableHeight}"
|
||||
ViewportSize="{TemplateBinding ViewportHeight}"
|
||||
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
|
||||
<ScrollBar x:Name="PART_HorizontalScrollBar"
|
||||
VerticalAlignment="Bottom"
|
||||
Orientation="Horizontal"
|
||||
Opacity="0.5"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Value="{TemplateBinding HorizontalOffset}"
|
||||
Maximum="{TemplateBinding ScrollableWidth}"
|
||||
ViewportSize="{TemplateBinding ViewportWidth}"
|
||||
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<UserControl.InputBindings>
|
||||
<KeyBinding Command="{s:Action Play}" Key="Space" />
|
||||
<KeyBinding Command="{s:Action PlayFromStart}" Modifiers="Shift" Key="Space" />
|
||||
</UserControl.InputBindings>
|
||||
<materialDesign:DialogHost Identifier="LayerProperties" DialogTheme="Inherit">
|
||||
<Grid x:Name="ContainerGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="28" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Left side -->
|
||||
<Grid Grid.Column="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Misc controls & time display -->
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<DockPanel VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Play from start (Shift+Space)" Command="{s:Action PlayFromStart}"
|
||||
Focusable="False">
|
||||
<materialDesign:PackIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}"
|
||||
ToolTip="Toggle play/pause (Space)" Command="{s:Action Play}" Focusable="False">
|
||||
<StackPanel>
|
||||
<materialDesign:PackIcon Kind="Play"
|
||||
Visibility="{Binding Playing, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="Pause"
|
||||
Visibility="{Binding Playing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to start" Command="{s:Action GoToStart}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Go to end" Command="{s:Action GoToEnd}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Previous frame" Command="{s:Action GoToPreviousFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignIconForegroundButton}" ToolTip="Next frame" Command="{s:Action GoToNextFrame}" Focusable="False">
|
||||
<materialDesign:PackIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<shared:LockableToggleButton Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||
IsChecked="{Binding Repeating}"
|
||||
IsLocked="True"
|
||||
Focusable="False"
|
||||
Click="{s:Action CycleRepeating}">
|
||||
<shared:LockableToggleButton.ToolTip>
|
||||
<StackPanel>
|
||||
<StackPanel Visibility="{Binding Repeating, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<TextBlock Text="Repeat entire timeline"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<TextBlock Text="Repeat segment"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<TextBlock Visibility="{Binding Repeating, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
Don't repeat the timeline
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
This setting only applies to the editor and <Run Text="does not" FontWeight="Bold" /> affect the repeat mode during normal profile playback
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</shared:LockableToggleButton.ToolTip>
|
||||
<Grid>
|
||||
<materialDesign:PackIcon Kind="Repeat"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatTimeline, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
<materialDesign:PackIcon Kind="RepeatOne"
|
||||
Height="24"
|
||||
Width="24"
|
||||
Visibility="{Binding RepeatSegment, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
</shared:LockableToggleButton>
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}"
|
||||
Text="{Binding FormattedCurrentTime}" HorizontalAlignment="Right" Margin="0 0 20 0" />
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Properties tree -->
|
||||
<materialDesign:DialogHost Identifier="PropertyTreeDialogHost" DialogTheme="Inherit" CloseOnClickAway="True" Grid.Row="1">
|
||||
<materialDesign:Transitioner x:Name="PropertyTreeTransitioner" SelectedIndex="{Binding PropertyTreeIndex}" DefaultTransitionOrigin="0.9, 0.9" AutoApplyTransitionOrigins="True">
|
||||
<ScrollViewer x:Name="PropertyTreeScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource MaterialDesignDivider}">
|
||||
<ContentControl s:View.Model="{Binding TreeViewModel}"
|
||||
shared:SizeObserver.Observe="True"
|
||||
shared:SizeObserver.ObservedHeight="{Binding TreeViewModelHeight, Mode=OneWayToSource}" />
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<ContentControl s:View.Model="{Binding EffectsViewModel}" />
|
||||
<!-- Transitions only work when the command comes from inside the transitioner but we want the button outside,
|
||||
by setting the command target to this hidden button we circumvent that -->
|
||||
<Button x:Name="TransitionCommandAnchor"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Width="110"
|
||||
Height="1"
|
||||
Visibility="Hidden" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
|
||||
<!-- Resize -->
|
||||
<GridSplitter Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="15" HorizontalAlignment="Stretch" Cursor="SizeWE" Margin="-15 0" Background="Transparent" />
|
||||
|
||||
<!-- Right side -->
|
||||
<materialDesign:Transitioner Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
x:Name="RightSideTransitioner"
|
||||
SelectedIndex="{Binding RightSideIndex}"
|
||||
DefaultTransitionOrigin="0, 0.5">
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid>
|
||||
<Grid.InputBindings>
|
||||
<KeyBinding Key="Delete" Command="{s:Action DeleteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="D" Modifiers="Control" Command="{s:Action DuplicateKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="C" Modifiers="Control" Command="{s:Action CopyKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
<KeyBinding Key="V" Modifiers="Control" Command="{s:Action PasteKeyframes}" s:View.ActionTarget="{Binding TimelineViewModel}" />
|
||||
</Grid.InputBindings>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="48" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Timeline headers -->
|
||||
<ScrollViewer Grid.Row="0"
|
||||
x:Name="TimelineHeaderScrollViewer"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Canvas Background="{DynamicResource MaterialDesignCardBackground}" Width="{Binding ActualWidth, ElementName=PropertyTimeLine}">
|
||||
<!-- Timeline segments -->
|
||||
<ContentControl Canvas.Left="{Binding EndTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding EndTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding MainTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding MainTimelineSegmentViewModel}" />
|
||||
<ContentControl Canvas.Left="{Binding StartTimelineSegmentViewModel.SegmentStartPosition}" s:View.Model="{Binding StartTimelineSegmentViewModel}" />
|
||||
|
||||
<!-- Timeline header body -->
|
||||
<controls:PropertyTimelineHeader Margin="0 18 0 0"
|
||||
Foreground="{DynamicResource MaterialDesignBody}"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}"
|
||||
PixelsPerSecond="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
HorizontalOffset="{Binding ContentHorizontalOffset, ElementName=TimelineHeaderScrollViewer}"
|
||||
VisibleWidth="{Binding ActualWidth, ElementName=TimelineHeaderScrollViewer}"
|
||||
OffsetFirstValue="True"
|
||||
MouseLeftButtonUp="{s:Action TimelineJump}"
|
||||
Width="{Binding ActualWidth, ElementName=PropertyTimeLine}"
|
||||
Cursor="Hand" />
|
||||
|
||||
<!-- Timeline caret -->
|
||||
<Polygon Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
Points="-8,0 -8,8 0,20, 8,8 8,0"
|
||||
Fill="{StaticResource SecondaryHueMidBrush}" />
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=TimelineHeaderScrollViewer}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryHueMidBrush}" />
|
||||
</Canvas>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- Timeline rails -->
|
||||
<ScrollViewer Grid.Row="1"
|
||||
x:Name="TimelineRailsScrollViewer"
|
||||
Style="{StaticResource SvStyle}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="TimelineScrollChanged">
|
||||
<Grid Background="{DynamicResource MaterialDesignToolBarBackground}" x:Name="RailsGrid">
|
||||
<Canvas Grid.Column="0" Panel.ZIndex="1">
|
||||
<Line Canvas.Left="{Binding TimeCaretPosition}"
|
||||
Cursor="SizeWE"
|
||||
MouseDown="{s:Action TimelineMouseDown}"
|
||||
MouseUp="{s:Action TimelineMouseUp}"
|
||||
MouseMove="{s:Action TimelineMouseMove}"
|
||||
X1="0"
|
||||
X2="0"
|
||||
Y1="0"
|
||||
Y2="{Binding ActualHeight, ElementName=RailsGrid}"
|
||||
StrokeThickness="2"
|
||||
Stroke="{StaticResource SecondaryHueMidBrush}" />
|
||||
</Canvas>
|
||||
|
||||
<ContentControl Grid.Column="0" s:View.Model="{Binding TimelineViewModel}" x:Name="PropertyTimeLine" />
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
|
||||
<materialDesign:TransitionerSlide>
|
||||
<materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<materialDesign:CircleWipe />
|
||||
</materialDesign:TransitionerSlide.BackwardWipe>
|
||||
<Grid Background="{StaticResource MaterialDesignCardBackground}">
|
||||
<ContentControl s:View.Model="{Binding DataBindingsViewModel}" />
|
||||
</Grid>
|
||||
</materialDesign:TransitionerSlide>
|
||||
</materialDesign:Transitioner>
|
||||
|
||||
<!-- Bottom row, a bit hacky but has a ZIndex of 2 to cut off the time caret that overlaps the entire timeline -->
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Panel.ZIndex="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
<!-- Selected layer controls -->
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedLayer, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Layers" Width="16" />
|
||||
<shared:ArtemisIcon Icon="{Binding SelectedLayer.LayerBrush.Descriptor.Icon}"
|
||||
Width="16"
|
||||
Margin="5 0 0 0"
|
||||
ToolTip="{Binding SelectedLayer.LayerBrush.Descriptor.DisplayName, Mode=OneWay}"
|
||||
Background="Transparent"
|
||||
Visibility="{Binding SelectedLayer.LayerBrush, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock Text="{Binding SelectedLayer.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="0"
|
||||
Orientation="Horizontal"
|
||||
Margin="6"
|
||||
Visibility="{Binding SelectedFolder, Converter={StaticResource NullToVisibilityConverter}}">
|
||||
<materialDesign:PackIcon Kind="Folder" Width="16" />
|
||||
<TextBlock Text="{Binding SelectedFolder.Name}" Margin="5 0 0 0" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Select an effect to add"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveLastCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="11">
|
||||
ADD EFFECT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignFlatMidBgButton}"
|
||||
Margin="0 -2 5 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="115"
|
||||
ToolTip="Show the layer/folder properties"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding PropertyTreeVisible, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}"
|
||||
Command="{x:Static materialDesign:Transitioner.MoveFirstCommand}"
|
||||
CommandTarget="{Binding ElementName=TransitionCommandAnchor}"
|
||||
Click="{s:Action ToggleEffectsViewModel}"
|
||||
IsEnabled="{Binding CanToggleEffectsViewModel}">
|
||||
<TextBlock FontSize="10">
|
||||
SHOW PROPERTIES
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Panel.ZIndex="2"
|
||||
ClipToBounds="False"
|
||||
Background="{DynamicResource MaterialDesignCardBackground}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Margin="5 -2 0 -2"
|
||||
Padding="10 0"
|
||||
Height="20"
|
||||
Width="110"
|
||||
ToolTip="Add a new segment to the timeline"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding SelectedProfileElement, Converter={StaticResource NullToBooleanConverter}}"
|
||||
Visibility="{Binding TimelineVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
||||
<Button.Style>
|
||||
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource MaterialDesignFlatAccentBgButton}">
|
||||
<Style.Triggers>
|
||||
<EventTrigger RoutedEvent="Click">
|
||||
<EventTrigger.Actions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True" />
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger.Actions>
|
||||
</EventTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Button.Style>
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu.Resources>
|
||||
<Converters:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
</ContextMenu.Resources>
|
||||
<MenuItem Header="Start"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Start"
|
||||
IsEnabled="{Binding Data.StartTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="Main"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="Main"
|
||||
IsEnabled="{Binding Data.MainTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
<MenuItem Header="End"
|
||||
Command="{s:Action EnableSegment}"
|
||||
CommandParameter="End"
|
||||
IsEnabled="{Binding Data.EndTimelineSegmentViewModel.SegmentEnabled, Converter={StaticResource InverseBooleanConverter}, Source={StaticResource DataContextProxy}}" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<TextBlock FontSize="11">
|
||||
ADD SEGMENT
|
||||
</TextBlock>
|
||||
</Button>
|
||||
|
||||
<!-- Zoom control -->
|
||||
<Slider Grid.Column="1"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:SliderAssist.OnlyShowFocusVisualWhileDragging="True"
|
||||
Minimum="31"
|
||||
Maximum="350"
|
||||
Margin="10 0"
|
||||
Value="{Binding ProfileEditorService.PixelsPerSecond}"
|
||||
Foreground="{StaticResource SecondaryHueMidBrush}"
|
||||
Width="319" />
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Suspended overlay -->
|
||||
<Border Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Grid.RowSpan="2"
|
||||
Panel.ZIndex="3"
|
||||
Background="#CD353535"
|
||||
Visibility="{Binding SuspendedEditing, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="16">
|
||||
<materialDesign:PackIcon Kind="TimerOffOutline" Width="150" Height="150" HorizontalAlignment="Center" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline4TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0 10">
|
||||
Timeline suspended
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody1TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
|
||||
The profile is currently running in normal mode and the timeline cannot be edited.
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignBody2TextBlock}" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center">
|
||||
Press <Run Text="F5" FontWeight="Bold"/> to switch between editor mode and normal mode. Auto-switching can be disabled in the run menu.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</UserControl>
|
||||
@@ -1,29 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for LayerPropertiesView.xaml
|
||||
/// </summary>
|
||||
public partial class LayerPropertiesView : UserControl
|
||||
{
|
||||
public LayerPropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
// Keeping the scroll viewers in sync is up to the view, not a viewmodel concern
|
||||
private void TimelineScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource == TimelineHeaderScrollViewer)
|
||||
TimelineRailsScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
|
||||
else if (e.OriginalSource == PropertyTreeScrollViewer)
|
||||
TimelineRailsScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
|
||||
else if (e.OriginalSource == TimelineRailsScrollViewer)
|
||||
{
|
||||
TimelineHeaderScrollViewer.ScrollToHorizontalOffset(e.HorizontalOffset);
|
||||
PropertyTreeScrollViewer.ScrollToVerticalOffset(e.VerticalOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,682 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Extensions;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.LayerEffects;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using GongSolutions.Wpf.DragDrop;
|
||||
using Stylet;
|
||||
using static Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree.TreeGroupViewModel.LayerPropertyGroupType;
|
||||
using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
||||
{
|
||||
public class LayerPropertiesViewModel : Conductor<LayerPropertyGroupViewModel>.Collection.AllActive, IProfileEditorPanelViewModel, IDropTarget
|
||||
{
|
||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||
private LayerPropertyGroupViewModel _brushPropertyGroup;
|
||||
private bool _repeating;
|
||||
private bool _repeatSegment;
|
||||
private bool _repeatTimeline = true;
|
||||
private int _propertyTreeIndex;
|
||||
private int _rightSideIndex;
|
||||
private RenderProfileElement _selectedProfileElement;
|
||||
private DateTime _lastEffectsViewModelToggle;
|
||||
private double _treeViewModelHeight;
|
||||
|
||||
public LayerPropertiesViewModel(IProfileEditorService profileEditorService,
|
||||
ICoreService coreService,
|
||||
ISettingsService settingsService,
|
||||
ILayerPropertyVmFactory layerPropertyVmFactory,
|
||||
DataBindingsViewModel dataBindingsViewModel)
|
||||
{
|
||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||
|
||||
ProfileEditorService = profileEditorService;
|
||||
CoreService = coreService;
|
||||
SettingsService = settingsService;
|
||||
|
||||
PropertyChanged += HandlePropertyTreeIndexChanged;
|
||||
|
||||
// Left side
|
||||
TreeViewModel = _layerPropertyVmFactory.TreeViewModel(this, Items);
|
||||
TreeViewModel.ConductWith(this);
|
||||
EffectsViewModel = _layerPropertyVmFactory.EffectsViewModel(this);
|
||||
EffectsViewModel.ConductWith(this);
|
||||
|
||||
// Right side
|
||||
StartTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Start, Items);
|
||||
StartTimelineSegmentViewModel.ConductWith(this);
|
||||
MainTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.Main, Items);
|
||||
MainTimelineSegmentViewModel.ConductWith(this);
|
||||
EndTimelineSegmentViewModel = _layerPropertyVmFactory.TimelineSegmentViewModel(SegmentViewModelType.End, Items);
|
||||
EndTimelineSegmentViewModel.ConductWith(this);
|
||||
TimelineViewModel = _layerPropertyVmFactory.TimelineViewModel(this, Items);
|
||||
TimelineViewModel.ConductWith(this);
|
||||
DataBindingsViewModel = dataBindingsViewModel;
|
||||
DataBindingsViewModel.ConductWith(this);
|
||||
}
|
||||
|
||||
#region Child VMs
|
||||
|
||||
public TreeViewModel TreeViewModel { get; }
|
||||
public EffectsViewModel EffectsViewModel { get; }
|
||||
public TimelineSegmentViewModel StartTimelineSegmentViewModel { get; }
|
||||
public TimelineSegmentViewModel MainTimelineSegmentViewModel { get; }
|
||||
public TimelineSegmentViewModel EndTimelineSegmentViewModel { get; }
|
||||
public TimelineViewModel TimelineViewModel { get; }
|
||||
public DataBindingsViewModel DataBindingsViewModel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public IProfileEditorService ProfileEditorService { get; }
|
||||
public ICoreService CoreService { get; }
|
||||
public ISettingsService SettingsService { get; }
|
||||
|
||||
public bool Playing
|
||||
{
|
||||
get => ProfileEditorService.Playing;
|
||||
set
|
||||
{
|
||||
ProfileEditorService.Playing = value;
|
||||
NotifyOfPropertyChange(nameof(Playing));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Repeating
|
||||
{
|
||||
get => _repeating;
|
||||
set => SetAndNotify(ref _repeating, value);
|
||||
}
|
||||
|
||||
public bool RepeatSegment
|
||||
{
|
||||
get => _repeatSegment;
|
||||
set => SetAndNotify(ref _repeatSegment, value);
|
||||
}
|
||||
|
||||
public bool RepeatTimeline
|
||||
{
|
||||
get => _repeatTimeline;
|
||||
set => SetAndNotify(ref _repeatTimeline, value);
|
||||
}
|
||||
|
||||
public string FormattedCurrentTime => $"{Math.Floor(ProfileEditorService.CurrentTime.TotalSeconds):00}.{ProfileEditorService.CurrentTime.Milliseconds:000}";
|
||||
|
||||
public double TimeCaretPosition
|
||||
{
|
||||
get => ProfileEditorService.CurrentTime.TotalSeconds * ProfileEditorService.PixelsPerSecond;
|
||||
set => ProfileEditorService.CurrentTime = TimeSpan.FromSeconds(value / ProfileEditorService.PixelsPerSecond);
|
||||
}
|
||||
|
||||
public bool SuspendedEditing => ProfileEditorService.SuspendEditing;
|
||||
|
||||
public int PropertyTreeIndex
|
||||
{
|
||||
get => _propertyTreeIndex;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _propertyTreeIndex, value)) return;
|
||||
NotifyOfPropertyChange(nameof(PropertyTreeVisible));
|
||||
}
|
||||
}
|
||||
|
||||
public int RightSideIndex
|
||||
{
|
||||
get => _rightSideIndex;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _rightSideIndex, value)) return;
|
||||
NotifyOfPropertyChange(nameof(TimelineVisible));
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanToggleEffectsViewModel => SelectedProfileElement != null && DateTime.Now - _lastEffectsViewModelToggle > TimeSpan.FromMilliseconds(250);
|
||||
|
||||
public bool PropertyTreeVisible => PropertyTreeIndex == 0;
|
||||
public bool TimelineVisible => RightSideIndex == 0;
|
||||
|
||||
public RenderProfileElement SelectedProfileElement
|
||||
{
|
||||
get => _selectedProfileElement;
|
||||
set
|
||||
{
|
||||
if (!SetAndNotify(ref _selectedProfileElement, value)) return;
|
||||
NotifyOfPropertyChange(nameof(SelectedLayer));
|
||||
NotifyOfPropertyChange(nameof(SelectedFolder));
|
||||
NotifyOfPropertyChange(nameof(SelectedFolder));
|
||||
NotifyOfPropertyChange(nameof(CanToggleEffectsViewModel));
|
||||
}
|
||||
}
|
||||
|
||||
public Layer SelectedLayer => SelectedProfileElement as Layer;
|
||||
public Folder SelectedFolder => SelectedProfileElement as Folder;
|
||||
|
||||
public double TreeViewModelHeight
|
||||
{
|
||||
get => _treeViewModelHeight;
|
||||
set => SetAndNotify(ref _treeViewModelHeight, value);
|
||||
}
|
||||
|
||||
|
||||
#region Segments
|
||||
|
||||
public void EnableSegment(string segment)
|
||||
{
|
||||
if (segment == "Start")
|
||||
StartTimelineSegmentViewModel.EnableSegment();
|
||||
else if (segment == "Main")
|
||||
MainTimelineSegmentViewModel.EnableSegment();
|
||||
else if (segment == "End")
|
||||
EndTimelineSegmentViewModel.EnableSegment();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PopulateProperties(ProfileEditorService.SelectedProfileElement);
|
||||
|
||||
ProfileEditorService.SelectedProfileElementChanged += SelectedProfileEditorServiceOnSelectedProfileElementChanged;
|
||||
ProfileEditorService.CurrentTimeChanged += ProfileEditorServiceOnCurrentTimeChanged;
|
||||
ProfileEditorService.SuspendEditingChanged += ProfileEditorServiceOnSuspendEditingChanged;
|
||||
ProfileEditorService.SelectedDataBindingChanged += ProfileEditorServiceOnSelectedDataBindingChanged;
|
||||
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
ProfileEditorService.SelectedProfileElementChanged -= SelectedProfileEditorServiceOnSelectedProfileElementChanged;
|
||||
ProfileEditorService.CurrentTimeChanged -= ProfileEditorServiceOnCurrentTimeChanged;
|
||||
ProfileEditorService.SuspendEditingChanged -= ProfileEditorServiceOnSuspendEditingChanged;
|
||||
ProfileEditorService.SelectedDataBindingChanged -= ProfileEditorServiceOnSelectedDataBindingChanged;
|
||||
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
|
||||
PopulateProperties(null);
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
Pause();
|
||||
base.OnDeactivate();
|
||||
}
|
||||
|
||||
private void HandlePropertyTreeIndexChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PropertyTreeIndex) && PropertyTreeIndex == 1)
|
||||
EffectsViewModel.PopulateDescriptors();
|
||||
}
|
||||
|
||||
private void SelectedProfileEditorServiceOnSelectedProfileElementChanged(object sender, RenderProfileElementEventArgs e)
|
||||
{
|
||||
Execute.PostToUIThread(() => PopulateProperties(e.RenderProfileElement));
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnCurrentTimeChanged(object sender, EventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(FormattedCurrentTime));
|
||||
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnSuspendEditingChanged(object sender, EventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(SuspendedEditing));
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
NotifyOfPropertyChange(nameof(TimeCaretPosition));
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnSelectedDataBindingChanged(object sender, EventArgs e)
|
||||
{
|
||||
RightSideIndex = ProfileEditorService.SelectedDataBinding != null ? 1 : 0;
|
||||
}
|
||||
|
||||
#region View model managament
|
||||
|
||||
public List<LayerPropertyGroupViewModel> GetAllLayerPropertyGroupViewModels()
|
||||
{
|
||||
List<LayerPropertyGroupViewModel> groups = Items.ToList();
|
||||
List<LayerPropertyGroupViewModel> toAdd = groups.SelectMany(g => g.Items).Where(g => g is LayerPropertyGroupViewModel).Cast<LayerPropertyGroupViewModel>().ToList();
|
||||
groups.AddRange(toAdd);
|
||||
return groups;
|
||||
}
|
||||
|
||||
private void PopulateProperties(RenderProfileElement profileElement)
|
||||
{
|
||||
// Unsubscribe from old selected element
|
||||
if (SelectedProfileElement != null)
|
||||
SelectedProfileElement.LayerEffectsUpdated -= SelectedElementOnLayerEffectsUpdated;
|
||||
if (SelectedLayer != null)
|
||||
SelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated;
|
||||
|
||||
// Clear old properties
|
||||
Items.Clear();
|
||||
_brushPropertyGroup = null;
|
||||
|
||||
if (profileElement == null)
|
||||
return;
|
||||
|
||||
// Subscribe to new element
|
||||
SelectedProfileElement = profileElement;
|
||||
SelectedProfileElement.LayerEffectsUpdated += SelectedElementOnLayerEffectsUpdated;
|
||||
|
||||
if (SelectedLayer != null)
|
||||
{
|
||||
SelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated;
|
||||
|
||||
// Add the built-in root groups of the layer
|
||||
Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.General));
|
||||
Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.Transform));
|
||||
}
|
||||
|
||||
ApplyLayerBrush();
|
||||
ApplyEffects();
|
||||
}
|
||||
|
||||
private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e)
|
||||
{
|
||||
ApplyLayerBrush();
|
||||
}
|
||||
|
||||
private void SelectedElementOnLayerEffectsUpdated(object sender, EventArgs e)
|
||||
{
|
||||
ApplyEffects();
|
||||
}
|
||||
|
||||
public void ApplyLayerBrush()
|
||||
{
|
||||
if (SelectedLayer == null)
|
||||
return;
|
||||
|
||||
bool hideRenderRelatedProperties = SelectedLayer?.LayerBrush != null && !SelectedLayer.LayerBrush.SupportsTransformation;
|
||||
|
||||
SelectedLayer.General.ShapeType.IsHidden = hideRenderRelatedProperties;
|
||||
SelectedLayer.General.BlendMode.IsHidden = hideRenderRelatedProperties;
|
||||
SelectedLayer.Transform.IsHidden = hideRenderRelatedProperties;
|
||||
|
||||
if (_brushPropertyGroup != null)
|
||||
{
|
||||
Items.Remove(_brushPropertyGroup);
|
||||
_brushPropertyGroup = null;
|
||||
}
|
||||
|
||||
if (SelectedLayer.LayerBrush != null)
|
||||
{
|
||||
_brushPropertyGroup = _layerPropertyVmFactory.LayerPropertyGroupViewModel(SelectedLayer.LayerBrush.BaseProperties);
|
||||
Items.Add(_brushPropertyGroup);
|
||||
}
|
||||
|
||||
SortProperties();
|
||||
}
|
||||
|
||||
private void ApplyEffects()
|
||||
{
|
||||
if (SelectedProfileElement == null)
|
||||
return;
|
||||
|
||||
// // Remove VMs of effects no longer applied on the layer
|
||||
// List<LayerPropertyGroupViewModel> toRemove = Items
|
||||
// .Where(l => l.LayerPropertyGroup.LayerEffect != null && !SelectedProfileElement.LayerEffects.Contains(l.LayerPropertyGroup.LayerEffect))
|
||||
// .ToList();
|
||||
// Items.RemoveRange(toRemove);
|
||||
//
|
||||
// foreach (BaseLayerEffect layerEffect in SelectedProfileElement.LayerEffects)
|
||||
// {
|
||||
// if (Items.Any(l => l.LayerPropertyGroup.LayerEffect == layerEffect) || layerEffect.BaseProperties == null)
|
||||
// continue;
|
||||
//
|
||||
// Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(layerEffect.BaseProperties));
|
||||
// }
|
||||
|
||||
SortProperties();
|
||||
}
|
||||
|
||||
private void SortProperties()
|
||||
{
|
||||
// Get all non-effect properties
|
||||
List<LayerPropertyGroupViewModel> nonEffectProperties = Items
|
||||
.Where(l => l.TreeGroupViewModel.GroupType != LayerEffectRoot)
|
||||
.ToList();
|
||||
// // Order the effects
|
||||
// List<LayerPropertyGroupViewModel> effectProperties = Items
|
||||
// .Where(l => l.TreeGroupViewModel.GroupType == LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
|
||||
// .OrderBy(l => l.LayerPropertyGroup.LayerEffect.Order)
|
||||
// .ToList();
|
||||
|
||||
// Put the non-effect properties in front
|
||||
for (int index = 0; index < nonEffectProperties.Count; index++)
|
||||
{
|
||||
LayerPropertyGroupViewModel layerPropertyGroupViewModel = nonEffectProperties[index];
|
||||
if (Items.IndexOf(layerPropertyGroupViewModel) != index)
|
||||
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index);
|
||||
}
|
||||
|
||||
// // Put the effect properties after, sorted by their order
|
||||
// for (int index = 0; index < effectProperties.Count; index++)
|
||||
// {
|
||||
// LayerPropertyGroupViewModel layerPropertyGroupViewModel = effectProperties[index];
|
||||
// if (Items.IndexOf(layerPropertyGroupViewModel) != index + nonEffectProperties.Count)
|
||||
// ((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(layerPropertyGroupViewModel), index + nonEffectProperties.Count);
|
||||
// }
|
||||
}
|
||||
|
||||
public async void ToggleEffectsViewModel()
|
||||
{
|
||||
_lastEffectsViewModelToggle = DateTime.Now;
|
||||
NotifyOfPropertyChange(nameof(CanToggleEffectsViewModel));
|
||||
|
||||
await Task.Delay(300);
|
||||
NotifyOfPropertyChange(nameof(CanToggleEffectsViewModel));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drag and drop
|
||||
|
||||
public void DragOver(IDropInfo dropInfo)
|
||||
{
|
||||
// Workaround for https://github.com/punker76/gong-wpf-dragdrop/issues/344
|
||||
// Luckily we know the index can never be 1 so it's an easy enough fix
|
||||
if (dropInfo.InsertIndex == 1)
|
||||
return;
|
||||
|
||||
LayerPropertyGroupViewModel source = dropInfo.Data as LayerPropertyGroupViewModel;
|
||||
LayerPropertyGroupViewModel target = dropInfo.TargetItem as LayerPropertyGroupViewModel;
|
||||
|
||||
if (source == target ||
|
||||
target?.TreeGroupViewModel.GroupType != LayerEffectRoot ||
|
||||
source?.TreeGroupViewModel.GroupType != LayerEffectRoot)
|
||||
return;
|
||||
|
||||
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
|
||||
dropInfo.Effects = DragDropEffects.Move;
|
||||
}
|
||||
|
||||
public void Drop(IDropInfo dropInfo)
|
||||
{
|
||||
// Workaround for https://github.com/punker76/gong-wpf-dragdrop/issues/344
|
||||
// Luckily we know the index can never be 1 so it's an easy enough fix
|
||||
if (dropInfo.InsertIndex == 1)
|
||||
return;
|
||||
|
||||
LayerPropertyGroupViewModel source = dropInfo.Data as LayerPropertyGroupViewModel;
|
||||
LayerPropertyGroupViewModel target = dropInfo.TargetItem as LayerPropertyGroupViewModel;
|
||||
|
||||
if (source == target ||
|
||||
target?.TreeGroupViewModel.GroupType != LayerEffectRoot ||
|
||||
source?.TreeGroupViewModel.GroupType != LayerEffectRoot)
|
||||
return;
|
||||
|
||||
if (dropInfo.InsertPosition == RelativeInsertPosition.BeforeTargetItem)
|
||||
MoveBefore(source, target);
|
||||
else if (dropInfo.InsertPosition == RelativeInsertPosition.AfterTargetItem)
|
||||
MoveAfter(source, target);
|
||||
|
||||
ApplyCurrentEffectsOrder();
|
||||
ProfileEditorService.SaveSelectedProfileConfiguration();
|
||||
}
|
||||
|
||||
private void MoveBefore(LayerPropertyGroupViewModel source, LayerPropertyGroupViewModel target)
|
||||
{
|
||||
if (Items.IndexOf(target) == Items.IndexOf(source) + 1)
|
||||
return;
|
||||
|
||||
((BindableCollection<LayerPropertyGroupViewModel>) Items).Move(Items.IndexOf(source), Items.IndexOf(target));
|
||||
}
|
||||
|
||||
private void MoveAfter(LayerPropertyGroupViewModel source, LayerPropertyGroupViewModel target)
|
||||
{
|
||||
Items.Remove(source);
|
||||
Items.Insert(Items.IndexOf(target) + 1, source);
|
||||
}
|
||||
|
||||
private void ApplyCurrentEffectsOrder()
|
||||
{
|
||||
int order = 1;
|
||||
foreach (LayerPropertyGroupViewModel groupViewModel in Items.Where(p => p.TreeGroupViewModel.GroupType == LayerEffectRoot))
|
||||
{
|
||||
groupViewModel.UpdateOrder(order);
|
||||
order++;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Controls
|
||||
|
||||
public void PlayFromStart()
|
||||
{
|
||||
if (!Playing)
|
||||
ProfileEditorService.CurrentTime = TimeSpan.Zero;
|
||||
|
||||
Play();
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if (!IsActive)
|
||||
return;
|
||||
if (Playing)
|
||||
{
|
||||
Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
CoreService.FrameRendering += CoreServiceOnFrameRendering;
|
||||
Playing = true;
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (!Playing)
|
||||
return;
|
||||
|
||||
CoreService.FrameRendering -= CoreServiceOnFrameRendering;
|
||||
Playing = false;
|
||||
}
|
||||
|
||||
|
||||
public void GoToStart()
|
||||
{
|
||||
ProfileEditorService.CurrentTime = TimeSpan.Zero;
|
||||
}
|
||||
|
||||
public void GoToEnd()
|
||||
{
|
||||
if (SelectedProfileElement == null)
|
||||
return;
|
||||
|
||||
ProfileEditorService.CurrentTime = SelectedProfileElement.Timeline.EndSegmentEndPosition;
|
||||
}
|
||||
|
||||
public void GoToPreviousFrame()
|
||||
{
|
||||
if (SelectedProfileElement == null)
|
||||
return;
|
||||
|
||||
double frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
||||
double newTime = Math.Max(0, Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
||||
ProfileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||
}
|
||||
|
||||
public void GoToNextFrame()
|
||||
{
|
||||
if (SelectedProfileElement == null)
|
||||
return;
|
||||
|
||||
double frameTime = 1000.0 / SettingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
||||
double newTime = Math.Round((ProfileEditorService.CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
||||
newTime = Math.Min(newTime, SelectedProfileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds);
|
||||
ProfileEditorService.CurrentTime = TimeSpan.FromMilliseconds(newTime);
|
||||
}
|
||||
|
||||
public void CycleRepeating()
|
||||
{
|
||||
if (!Repeating)
|
||||
{
|
||||
RepeatTimeline = true;
|
||||
RepeatSegment = false;
|
||||
Repeating = true;
|
||||
}
|
||||
else if (RepeatTimeline)
|
||||
{
|
||||
RepeatTimeline = false;
|
||||
RepeatSegment = true;
|
||||
}
|
||||
else if (RepeatSegment)
|
||||
{
|
||||
RepeatTimeline = true;
|
||||
RepeatSegment = false;
|
||||
Repeating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetCurrentSegmentStart()
|
||||
{
|
||||
TimeSpan current = ProfileEditorService.CurrentTime;
|
||||
if (current < StartTimelineSegmentViewModel.SegmentEnd)
|
||||
return StartTimelineSegmentViewModel.SegmentStart;
|
||||
if (current < MainTimelineSegmentViewModel.SegmentEnd)
|
||||
return MainTimelineSegmentViewModel.SegmentStart;
|
||||
if (current < EndTimelineSegmentViewModel.SegmentEnd)
|
||||
return EndTimelineSegmentViewModel.SegmentStart;
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private TimeSpan GetCurrentSegmentEnd()
|
||||
{
|
||||
TimeSpan current = ProfileEditorService.CurrentTime;
|
||||
if (current < StartTimelineSegmentViewModel.SegmentEnd)
|
||||
return StartTimelineSegmentViewModel.SegmentEnd;
|
||||
if (current < MainTimelineSegmentViewModel.SegmentEnd)
|
||||
return MainTimelineSegmentViewModel.SegmentEnd;
|
||||
if (current < EndTimelineSegmentViewModel.SegmentEnd)
|
||||
return EndTimelineSegmentViewModel.SegmentEnd;
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
|
||||
{
|
||||
if (!ProfileEditorService.Playing)
|
||||
{
|
||||
CoreService.FrameRendering -= CoreServiceOnFrameRendering;
|
||||
return;
|
||||
}
|
||||
|
||||
TimeSpan newTime = ProfileEditorService.CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
|
||||
if (SelectedProfileElement != null)
|
||||
{
|
||||
if (Repeating && RepeatTimeline)
|
||||
{
|
||||
if (newTime > SelectedProfileElement.Timeline.Length)
|
||||
newTime = TimeSpan.Zero;
|
||||
}
|
||||
else if (Repeating && RepeatSegment)
|
||||
{
|
||||
if (newTime > GetCurrentSegmentEnd())
|
||||
newTime = GetCurrentSegmentStart();
|
||||
}
|
||||
else if (newTime > SelectedProfileElement.Timeline.Length)
|
||||
{
|
||||
newTime = SelectedProfileElement.Timeline.Length;
|
||||
Pause();
|
||||
}
|
||||
}
|
||||
|
||||
// Update current time on high priority to keep things buttery smooth as if you're using the mouse ༼ つ ◕_◕ ༽つ
|
||||
Execute.OnUIThreadSync(() => ProfileEditorService.CurrentTime = newTime);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Caret movement
|
||||
|
||||
public void TimelineMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).CaptureMouse();
|
||||
}
|
||||
|
||||
public void TimelineMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
((IInputElement) sender).ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
public void TimelineMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
// Get the parent grid, need that for our position
|
||||
IInputElement parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||
double x = Math.Max(0, e.GetPosition(parent).X);
|
||||
TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||
|
||||
// If holding down shift, snap to the closest segment or keyframe
|
||||
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
||||
{
|
||||
List<TimeSpan> snapTimes = Items.SelectMany(g => g.GetAllKeyframeViewModels(true)).Select(k => k.Position).ToList();
|
||||
TimeSpan snappedTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), true, false, snapTimes);
|
||||
ProfileEditorService.CurrentTime = snappedTime;
|
||||
return;
|
||||
}
|
||||
|
||||
// If holding down control, round to the closest 50ms
|
||||
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
||||
{
|
||||
TimeSpan roundedTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
||||
ProfileEditorService.CurrentTime = roundedTime;
|
||||
return;
|
||||
}
|
||||
|
||||
ProfileEditorService.CurrentTime = newTime;
|
||||
}
|
||||
}
|
||||
|
||||
public void TimelineJump(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// Get the parent grid, need that for our position
|
||||
IInputElement parent = (IInputElement) VisualTreeHelper.GetParent((DependencyObject) sender);
|
||||
double x = Math.Max(0, e.GetPosition(parent).X);
|
||||
TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
||||
|
||||
// Round the time to something that fits the current zoom level
|
||||
if (ProfileEditorService.PixelsPerSecond < 200)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
||||
else if (ProfileEditorService.PixelsPerSecond < 500)
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
||||
else
|
||||
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
||||
|
||||
ProfileEditorService.CurrentTime = newTime;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
||||
{
|
||||
public sealed class LayerPropertyGroupViewModel : Conductor<Screen>.Collection.AllActive
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||
private bool _isVisible;
|
||||
private TreeGroupViewModel _treeGroupViewModel;
|
||||
private TimelineGroupViewModel _timelineGroupViewModel;
|
||||
private bool _hasChildren;
|
||||
|
||||
public LayerPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||
|
||||
LayerPropertyGroup = layerPropertyGroup;
|
||||
IsVisible = !LayerPropertyGroup.IsHidden;
|
||||
|
||||
TreeGroupViewModel = _layerPropertyVmFactory.TreeGroupViewModel(this);
|
||||
TreeGroupViewModel.ConductWith(this);
|
||||
TimelineGroupViewModel = _layerPropertyVmFactory.TimelineGroupViewModel(this);
|
||||
TimelineGroupViewModel.ConductWith(this);
|
||||
}
|
||||
|
||||
public LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
|
||||
public TreeGroupViewModel TreeGroupViewModel
|
||||
{
|
||||
get => _treeGroupViewModel;
|
||||
set => SetAndNotify(ref _treeGroupViewModel, value);
|
||||
}
|
||||
|
||||
public TimelineGroupViewModel TimelineGroupViewModel
|
||||
{
|
||||
get => _timelineGroupViewModel;
|
||||
set => SetAndNotify(ref _timelineGroupViewModel, value);
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get => _isVisible;
|
||||
set => SetAndNotify(ref _isVisible, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => LayerPropertyGroup.ProfileElement.IsPropertyGroupExpanded(LayerPropertyGroup);
|
||||
set
|
||||
{
|
||||
LayerPropertyGroup.ProfileElement.SetPropertyGroupExpanded(LayerPropertyGroup, value);
|
||||
NotifyOfPropertyChange(nameof(IsExpanded));
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasChildren
|
||||
{
|
||||
get => _hasChildren;
|
||||
set => SetAndNotify(ref _hasChildren, value);
|
||||
}
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
LayerPropertyGroup.VisibilityChanged += LayerPropertyGroupOnVisibilityChanged;
|
||||
|
||||
PopulateChildren();
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
LayerPropertyGroup.VisibilityChanged -= LayerPropertyGroupOnVisibilityChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
public void UpdateOrder(int order)
|
||||
{
|
||||
// if (LayerPropertyGroup.LayerEffect != null)
|
||||
// LayerPropertyGroup.LayerEffect.Order = order;
|
||||
NotifyOfPropertyChange(nameof(IsExpanded));
|
||||
}
|
||||
|
||||
public List<ITimelineKeyframeViewModel> GetAllKeyframeViewModels(bool expandedOnly)
|
||||
{
|
||||
List<ITimelineKeyframeViewModel> result = new();
|
||||
if (expandedOnly && !IsExpanded)
|
||||
return result;
|
||||
|
||||
foreach (Screen child in Items)
|
||||
{
|
||||
if (child is LayerPropertyViewModel layerPropertyViewModel)
|
||||
result.AddRange(layerPropertyViewModel.TimelinePropertyViewModel.GetAllKeyframeViewModels());
|
||||
else if (child is LayerPropertyGroupViewModel layerPropertyGroupViewModel)
|
||||
result.AddRange(layerPropertyGroupViewModel.GetAllKeyframeViewModels(expandedOnly));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the keyframes between the <paramref name="start" /> and <paramref name="end" /> position from this property
|
||||
/// group
|
||||
/// </summary>
|
||||
/// <param name="start">The position at which to start removing keyframes, if null this will start at the first keyframe</param>
|
||||
/// <param name="end">The position at which to start removing keyframes, if null this will end at the last keyframe</param>
|
||||
public void WipeKeyframes(TimeSpan? start, TimeSpan? end)
|
||||
{
|
||||
foreach (Screen item in Items)
|
||||
{
|
||||
if (item is LayerPropertyViewModel layerPropertyViewModel)
|
||||
layerPropertyViewModel.TimelinePropertyViewModel.WipeKeyframes(start, end);
|
||||
else if (item is LayerPropertyGroupViewModel layerPropertyGroupViewModel)
|
||||
layerPropertyGroupViewModel.WipeKeyframes(start, end);
|
||||
}
|
||||
|
||||
TimelineGroupViewModel.UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shifts the keyframes between the <paramref name="start" /> and <paramref name="end" /> position by the provided
|
||||
/// <paramref name="amount" />
|
||||
/// </summary>
|
||||
/// <param name="start">The position at which to start shifting keyframes, if null this will start at the first keyframe</param>
|
||||
/// <param name="end">The position at which to start shifting keyframes, if null this will end at the last keyframe</param>
|
||||
/// <param name="amount">The amount to shift the keyframes for</param>
|
||||
public void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
|
||||
{
|
||||
foreach (Screen item in Items)
|
||||
{
|
||||
if (item is LayerPropertyViewModel layerPropertyViewModel)
|
||||
layerPropertyViewModel.TimelinePropertyViewModel.ShiftKeyframes(start, end, amount);
|
||||
else if (item is LayerPropertyGroupViewModel layerPropertyGroupViewModel)
|
||||
layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount);
|
||||
}
|
||||
|
||||
TimelineGroupViewModel.UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
private void LayerPropertyGroupOnVisibilityChanged(object sender, EventArgs e)
|
||||
{
|
||||
IsVisible = !LayerPropertyGroup.IsHidden;
|
||||
}
|
||||
|
||||
private void PopulateChildren()
|
||||
{
|
||||
// Get all properties and property groups and create VMs for them
|
||||
// The group has methods for getting this without reflection but then we lose the order of the properties as they are defined on the group
|
||||
foreach (PropertyInfo propertyInfo in LayerPropertyGroup.GetType().GetProperties())
|
||||
{
|
||||
if (Attribute.IsDefined(propertyInfo, typeof(LayerPropertyIgnoreAttribute)))
|
||||
continue;
|
||||
|
||||
if (typeof(ILayerProperty).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
ILayerProperty value = (ILayerProperty) propertyInfo.GetValue(LayerPropertyGroup);
|
||||
// Ensure a supported input VM was found, otherwise don't add it
|
||||
if (value != null && _profileEditorService.CanCreatePropertyInputViewModel(value))
|
||||
Items.Add(_layerPropertyVmFactory.LayerPropertyViewModel(value));
|
||||
}
|
||||
else if (typeof(LayerPropertyGroup).IsAssignableFrom(propertyInfo.PropertyType))
|
||||
{
|
||||
LayerPropertyGroup value = (LayerPropertyGroup) propertyInfo.GetValue(LayerPropertyGroup);
|
||||
if (value != null)
|
||||
Items.Add(_layerPropertyVmFactory.LayerPropertyGroupViewModel(value));
|
||||
}
|
||||
}
|
||||
|
||||
HasChildren = Items.Any(i => i is LayerPropertyViewModel {IsVisible: true} || i is LayerPropertyGroupViewModel {IsVisible: true});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties
|
||||
{
|
||||
public sealed class LayerPropertyViewModel : Screen
|
||||
{
|
||||
private bool _isExpanded;
|
||||
private bool _isHighlighted;
|
||||
private bool _isVisible;
|
||||
private ITimelinePropertyViewModel _timelinePropertyViewModel;
|
||||
private ITreePropertyViewModel _treePropertyViewModel;
|
||||
|
||||
|
||||
public LayerPropertyViewModel(ILayerProperty layerProperty, IPropertyVmFactory propertyVmFactory)
|
||||
{
|
||||
LayerProperty = layerProperty;
|
||||
|
||||
TreePropertyViewModel = propertyVmFactory.TreePropertyViewModel(LayerProperty, this);
|
||||
TreePropertyViewModel.ConductWith(this);
|
||||
TimelinePropertyViewModel = propertyVmFactory.TimelinePropertyViewModel(LayerProperty, this);
|
||||
TimelinePropertyViewModel.ConductWith(this);
|
||||
}
|
||||
|
||||
public ILayerProperty LayerProperty { get; }
|
||||
|
||||
public ITreePropertyViewModel TreePropertyViewModel
|
||||
{
|
||||
get => _treePropertyViewModel;
|
||||
set => SetAndNotify(ref _treePropertyViewModel, value);
|
||||
}
|
||||
|
||||
public ITimelinePropertyViewModel TimelinePropertyViewModel
|
||||
{
|
||||
get => _timelinePropertyViewModel;
|
||||
set => SetAndNotify(ref _timelinePropertyViewModel, value);
|
||||
}
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get => _isVisible;
|
||||
set => SetAndNotify(ref _isVisible, value);
|
||||
}
|
||||
|
||||
public bool IsHighlighted
|
||||
{
|
||||
get => _isHighlighted;
|
||||
set => SetAndNotify(ref _isHighlighted, value);
|
||||
}
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => _isExpanded;
|
||||
set => SetAndNotify(ref _isExpanded, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Controls
|
||||
{
|
||||
public class PropertyTimelineHeader : FrameworkElement
|
||||
{
|
||||
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(nameof(Foreground), typeof(Brush), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(nameof(Background), typeof(Brush), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty FontFamilyProperty = DependencyProperty.Register(nameof(FontFamily), typeof(FontFamily), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(TextBlock.FontFamilyProperty.DefaultMetadata.DefaultValue, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty PixelsPerSecondProperty = DependencyProperty.Register(nameof(PixelsPerSecond), typeof(int), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(nameof(HorizontalOffset), typeof(double), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty VisibleWidthProperty = DependencyProperty.Register(nameof(VisibleWidth), typeof(double), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty OffsetFirstValueProperty = DependencyProperty.Register(nameof(OffsetFirstValue), typeof(bool), typeof(PropertyTimelineHeader),
|
||||
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
private double _subd1;
|
||||
private double _subd2;
|
||||
private double _subd3;
|
||||
|
||||
public Brush Foreground
|
||||
{
|
||||
get => (Brush) GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush Background
|
||||
{
|
||||
get => (Brush) GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
public FontFamily FontFamily
|
||||
{
|
||||
get => (FontFamily) GetValue(FontFamilyProperty);
|
||||
set => SetValue(FontFamilyProperty, value);
|
||||
}
|
||||
|
||||
public int PixelsPerSecond
|
||||
{
|
||||
get => (int) GetValue(PixelsPerSecondProperty);
|
||||
set => SetValue(PixelsPerSecondProperty, value);
|
||||
}
|
||||
|
||||
public double HorizontalOffset
|
||||
{
|
||||
get => (double) GetValue(HorizontalOffsetProperty);
|
||||
set => SetValue(HorizontalOffsetProperty, value);
|
||||
}
|
||||
|
||||
public double VisibleWidth
|
||||
{
|
||||
get => (double) GetValue(VisibleWidthProperty);
|
||||
set => SetValue(VisibleWidthProperty, value);
|
||||
}
|
||||
|
||||
public bool OffsetFirstValue
|
||||
{
|
||||
get => (bool) GetValue(OffsetFirstValueProperty);
|
||||
set => SetValue(OffsetFirstValueProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
base.OnRender(drawingContext);
|
||||
UpdateTimeScale();
|
||||
|
||||
if (Background != null)
|
||||
drawingContext.DrawRectangle(Background, null, new Rect(0,0, ActualWidth, 30));
|
||||
|
||||
Pen linePen = new(Foreground, 1);
|
||||
double width = HorizontalOffset + VisibleWidth;
|
||||
int frameStart = 0;
|
||||
|
||||
double units = PixelsPerSecond / _subd1;
|
||||
double offsetUnits = frameStart * PixelsPerSecond % units;
|
||||
|
||||
// Labels
|
||||
double count = (width + offsetUnits) / units;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double x = i * units - offsetUnits;
|
||||
// Add a 100px margin to allow the text to partially render when needed
|
||||
if (x < HorizontalOffset - 100 || x > HorizontalOffset + width)
|
||||
continue;
|
||||
|
||||
TimeSpan t = TimeSpan.FromSeconds((i * units - offsetUnits) / PixelsPerSecond + frameStart);
|
||||
// 0.00 is always formatted as 0.00
|
||||
if (t == TimeSpan.Zero)
|
||||
RenderLabel(drawingContext, "0.00", x);
|
||||
else if (PixelsPerSecond > 200)
|
||||
RenderLabel(drawingContext, $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}", x);
|
||||
else if (PixelsPerSecond > 60)
|
||||
RenderLabel(drawingContext, $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}", x);
|
||||
else
|
||||
RenderLabel(drawingContext, $"{Math.Floor(t.TotalMinutes):0}:{t.Seconds:00}", x);
|
||||
}
|
||||
|
||||
// Large ticks
|
||||
units = PixelsPerSecond / _subd2;
|
||||
count = (width + offsetUnits) / units;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double x = i * units - offsetUnits;
|
||||
if (x == 0 && OffsetFirstValue)
|
||||
drawingContext.DrawLine(linePen, new Point(1, 20), new Point(1, 30));
|
||||
else if (x > HorizontalOffset && x < HorizontalOffset + width)
|
||||
drawingContext.DrawLine(linePen, new Point(x, 20), new Point(x, 30));
|
||||
}
|
||||
|
||||
// Small ticks
|
||||
double mul = _subd3 / _subd2;
|
||||
units = PixelsPerSecond / _subd3;
|
||||
count = (width + offsetUnits) / units;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (Math.Abs(i % mul) < 0.001) continue;
|
||||
double x = i * units - offsetUnits;
|
||||
if (x > HorizontalOffset && x < HorizontalOffset + width)
|
||||
drawingContext.DrawLine(linePen, new Point(x, 25), new Point(x, 30));
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLabel(DrawingContext drawingContext, string text, double x)
|
||||
{
|
||||
Typeface typeFace = new(FontFamily, new FontStyle(), new FontWeight(), new FontStretch());
|
||||
FormattedText formattedText = new(text, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, typeFace, 9, Foreground, null, VisualTreeHelper.GetDpi(this).PixelsPerDip);
|
||||
if (x == 0 && OffsetFirstValue)
|
||||
drawingContext.DrawText(formattedText, new Point(2, 5));
|
||||
else
|
||||
drawingContext.DrawText(formattedText, new Point(x - formattedText.Width / 2, 5));
|
||||
}
|
||||
|
||||
private void UpdateTimeScale()
|
||||
{
|
||||
object[] subds;
|
||||
if (PixelsPerSecond > 350)
|
||||
subds = new object[] {12d, 12d, 60d};
|
||||
else if (PixelsPerSecond > 250)
|
||||
subds = new object[] {6d, 12d, 60d};
|
||||
else if (PixelsPerSecond > 200)
|
||||
subds = new object[] {6d, 6d, 30d};
|
||||
else if (PixelsPerSecond > 150)
|
||||
subds = new object[] {4d, 4d, 20d};
|
||||
else if (PixelsPerSecond > 140)
|
||||
subds = new object[] {4d, 4d, 20d};
|
||||
else if (PixelsPerSecond > 90)
|
||||
subds = new object[] {2d, 4d, 20d};
|
||||
else if (PixelsPerSecond > 60)
|
||||
subds = new object[] {2d, 4d, 8d};
|
||||
else if (PixelsPerSecond > 40)
|
||||
subds = new object[] {1d, 2d, 10d};
|
||||
else if (PixelsPerSecond > 30)
|
||||
subds = new object[] {1d, 2d, 10d};
|
||||
else if (PixelsPerSecond > 10)
|
||||
subds = new object[] {1d / 2d, 1d / 2d, 1d / 2d};
|
||||
else if (PixelsPerSecond > 4)
|
||||
subds = new object[] {1d / 5d, 1d / 5d, 1d / 5d};
|
||||
else if (PixelsPerSecond > 3)
|
||||
subds = new object[] {1d / 10d, 1d / 10d, 1d / 5d};
|
||||
else if (PixelsPerSecond > 1)
|
||||
subds = new object[] {1d / 20d, 1d / 20d, 1d / 10d};
|
||||
else if (PixelsPerSecond >= 1)
|
||||
subds = new object[] {1d / 30d, 1d / 30d, 1d / 15d};
|
||||
else
|
||||
{
|
||||
// 1s per pixel
|
||||
subds = new object[] {1d / 60d, 1d / 60d, 1d / 15d};
|
||||
}
|
||||
|
||||
_subd1 = (double) subds[0]; // big ticks / labels
|
||||
_subd2 = (double) subds[1]; // medium ticks
|
||||
_subd3 = (double) subds[2]; // small ticks
|
||||
}
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs.TimelineSegmentDialogView"
|
||||
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.ProfileEditor.LayerProperties.Timeline.Dialogs"
|
||||
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:TimelineSegmentDialogViewModel}">
|
||||
<StackPanel Margin="16" Width="240">
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}">
|
||||
<Run Text="Update " /><Run Text="{Binding Segment.Segment, Mode=OneWay}" /><Run Text=" segment" />
|
||||
</TextBlock>
|
||||
|
||||
<TextBox materialDesign:HintAssist.Hint="Segment length"
|
||||
materialDesign:TextFieldAssist.SuffixText="seconds"
|
||||
Margin="0 16"
|
||||
Text="{Binding InputValue, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource MaterialDesignFilledTextBox}" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsCancel="True" Margin="0 8 8 0" Command="{s:Action Cancel}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
False
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
CANCEL
|
||||
</Button>
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}" IsDefault="True" Margin="0 8 0 0" Command="{s:Action Accept}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
True
|
||||
</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
ACCEPT
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using FluentValidation;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs
|
||||
{
|
||||
public class TimelineSegmentDialogViewModel : DialogViewModelBase
|
||||
{
|
||||
private string _inputValue;
|
||||
|
||||
public TimelineSegmentDialogViewModel(IModelValidator<TimelineSegmentDialogViewModel> validator, TimelineSegmentViewModel segment)
|
||||
: base(validator)
|
||||
{
|
||||
Segment = segment;
|
||||
InputValue = $"{Math.Floor(Segment.SegmentLength.TotalSeconds):00}.{Segment.SegmentLength.Milliseconds:000}";
|
||||
}
|
||||
|
||||
public TimelineSegmentViewModel Segment { get; }
|
||||
|
||||
public string InputValue
|
||||
{
|
||||
get => _inputValue;
|
||||
set => SetAndNotify(ref _inputValue, value);
|
||||
}
|
||||
|
||||
public async Task Accept()
|
||||
{
|
||||
await ValidateAsync();
|
||||
|
||||
if (HasErrors)
|
||||
return;
|
||||
|
||||
Segment.UpdateLength(TimelineSegmentDialogViewModelValidator.CreateTime(InputValue));
|
||||
Session.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public class TimelineSegmentDialogViewModelValidator : AbstractValidator<TimelineSegmentDialogViewModel>
|
||||
{
|
||||
private readonly Regex _inputRegex = new("^[.][-|0-9]+$|^-?[0-9]*[.]{0,1}[0-9]*$");
|
||||
|
||||
public TimelineSegmentDialogViewModelValidator()
|
||||
{
|
||||
CascadeMode = CascadeMode.Stop;
|
||||
|
||||
RuleFor(m => m.InputValue)
|
||||
.NotNull()
|
||||
.WithMessage("A timeline length is required");
|
||||
RuleFor(m => m.InputValue)
|
||||
.Must(ValidateTime)
|
||||
.WithMessage("Input cannot be converted to a time");
|
||||
Transform(m => m.InputValue, CreateTime)
|
||||
.GreaterThanOrEqualTo(TimeSpan.FromMilliseconds(100))
|
||||
.WithMessage("Minimum timeline length is 100ms");
|
||||
Transform(m => m.InputValue, CreateTime)
|
||||
.LessThanOrEqualTo(TimeSpan.FromHours(24))
|
||||
.WithMessage("Maximum timeline length is 24 hours");
|
||||
}
|
||||
|
||||
public static TimeSpan CreateTime(string s)
|
||||
{
|
||||
string[] parts = s.Split(".");
|
||||
|
||||
// Only seconds provided
|
||||
if (parts.Length == 1)
|
||||
return TimeSpan.FromSeconds(double.Parse(parts[0]));
|
||||
// Only milliseconds provided with a leading .
|
||||
if (string.IsNullOrEmpty(parts[0]))
|
||||
{
|
||||
// Add trailing zeros so 2.5 becomes 2.500, can't seem to make double.Parse do that
|
||||
while (parts[0].Length < 3) parts[0] += "0";
|
||||
return TimeSpan.FromMilliseconds(double.Parse(parts[1], CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Seconds and milliseconds provided
|
||||
// Add trailing zeros so 2.5 becomes 2.500, can't seem to make double.Parse do that
|
||||
while (parts[1].Length < 3) parts[1] += "0";
|
||||
return TimeSpan.FromSeconds(double.Parse(parts[0])).Add(TimeSpan.FromMilliseconds(double.Parse(parts[1])));
|
||||
}
|
||||
|
||||
private bool ValidateTime(string arg)
|
||||
{
|
||||
return _inputRegex.IsMatch(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.UI.Exceptions;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Models
|
||||
{
|
||||
public class KeyframesClipboardModel
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global - For JSON.NET
|
||||
public KeyframesClipboardModel()
|
||||
{
|
||||
ClipboardModels = new List<KeyframeClipboardModel>();
|
||||
}
|
||||
|
||||
public KeyframesClipboardModel(IEnumerable<ILayerPropertyKeyframe> keyframes)
|
||||
{
|
||||
ClipboardModels = new List<KeyframeClipboardModel>();
|
||||
foreach (ILayerPropertyKeyframe keyframe in keyframes.OrderBy(k => k.Position))
|
||||
ClipboardModels.Add(new KeyframeClipboardModel(keyframe));
|
||||
}
|
||||
|
||||
public List<KeyframeClipboardModel> ClipboardModels { get; set; }
|
||||
public bool HasBeenPasted { get; set; }
|
||||
|
||||
public List<ILayerPropertyKeyframe> Paste(RenderProfileElement target, TimeSpan pastePosition)
|
||||
{
|
||||
if (target == null) throw new ArgumentNullException(nameof(target));
|
||||
if (HasBeenPasted)
|
||||
throw new ArtemisUIException("Clipboard model can only be pasted once");
|
||||
|
||||
List<ILayerPropertyKeyframe> results = new();
|
||||
if (!ClipboardModels.Any())
|
||||
return results;
|
||||
|
||||
// Determine the offset by looking at the position of the first keyframe, start pasting from there
|
||||
TimeSpan offset = pastePosition - ClipboardModels.First().KeyframeEntity.Position;
|
||||
List<ILayerProperty> properties = target.GetAllLayerProperties();
|
||||
foreach (KeyframeClipboardModel clipboardModel in ClipboardModels)
|
||||
{
|
||||
ILayerPropertyKeyframe layerPropertyKeyframe = clipboardModel.Paste(properties, offset);
|
||||
if (layerPropertyKeyframe != null)
|
||||
results.Add(layerPropertyKeyframe);
|
||||
}
|
||||
|
||||
HasBeenPasted = true;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyframeClipboardModel
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Global - For JSON.NET
|
||||
public KeyframeClipboardModel()
|
||||
{
|
||||
}
|
||||
|
||||
public KeyframeClipboardModel(ILayerPropertyKeyframe layerPropertyKeyframe)
|
||||
{
|
||||
// FeatureId = layerPropertyKeyframe.UntypedLayerProperty.LayerPropertyGroup.Feature.Id;
|
||||
Path = layerPropertyKeyframe.UntypedLayerProperty.Path;
|
||||
KeyframeEntity = layerPropertyKeyframe.GetKeyframeEntity();
|
||||
}
|
||||
|
||||
public string FeatureId { get; set; }
|
||||
public string Path { get; set; }
|
||||
public KeyframeEntity KeyframeEntity { get; set; }
|
||||
|
||||
public ILayerPropertyKeyframe Paste(List<ILayerProperty> properties, TimeSpan offset)
|
||||
{
|
||||
// ILayerProperty property = properties.FirstOrDefault(p => p.LayerPropertyGroup.Feature.Id == FeatureId && p.Path == Path);
|
||||
// if (property != null)
|
||||
// {
|
||||
// KeyframeEntity.Position += offset;
|
||||
// ILayerPropertyKeyframe keyframe = property.AddKeyframeEntity(KeyframeEntity);
|
||||
// KeyframeEntity.Position -= offset;
|
||||
//
|
||||
// return keyframe;
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Artemis.Core;
|
||||
using Humanizer;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public class TimelineEasingViewModel : PropertyChangedBase
|
||||
{
|
||||
private bool _isEasingModeSelected;
|
||||
|
||||
public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected)
|
||||
{
|
||||
_isEasingModeSelected = isSelected;
|
||||
|
||||
EasingFunction = easingFunction;
|
||||
Description = easingFunction.Humanize();
|
||||
|
||||
EasingPoints = new PointCollection();
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
int x = i;
|
||||
double y = Easings.Interpolate(i / 10.0, EasingFunction) * 10;
|
||||
EasingPoints.Add(new Point(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
public Easings.Functions EasingFunction { get; }
|
||||
public PointCollection EasingPoints { get; }
|
||||
public string Description { get; }
|
||||
|
||||
public bool IsEasingModeSelected
|
||||
{
|
||||
get => _isEasingModeSelected;
|
||||
set
|
||||
{
|
||||
_isEasingModeSelected = value;
|
||||
if (value) OnEasingModeSelected();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler EasingModeSelected;
|
||||
|
||||
protected virtual void OnEasingModeSelected()
|
||||
{
|
||||
EasingModeSelected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.TimelineGroupView"
|
||||
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.ProfileEditor.LayerProperties.Timeline"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:layerProperties="clr-namespace:Artemis.UI.Screens.ProfileEditor.LayerProperties"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:TimelineGroupViewModel}"
|
||||
Visibility="{Binding LayerPropertyGroupViewModel.IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="24" />
|
||||
<RowDefinition Height="1" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ItemsControl Grid.Row="0"
|
||||
Height="24"
|
||||
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}"
|
||||
ItemsSource="{Binding KeyframePositions}"
|
||||
Background="{DynamicResource MaterialDesignToolBarBackground}"
|
||||
HorizontalAlignment="Stretch">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="{x:Type ContentPresenter}">
|
||||
<Setter Property="Canvas.Left" Value="{Binding}" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Ellipse Fill="{StaticResource MaterialDesignCheckBoxDisabled}"
|
||||
Stroke="White"
|
||||
StrokeThickness="0"
|
||||
Width="10"
|
||||
Height="10"
|
||||
Margin="-5,6,0,0" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Rectangle Grid.Row="1" HorizontalAlignment="Stretch" Fill="{DynamicResource MaterialDesignDivider}" Height="1" />
|
||||
|
||||
<ItemsControl Grid.Row="2"
|
||||
ItemsSource="{Binding LayerPropertyGroupViewModel.Items}"
|
||||
Visibility="{Binding LayerPropertyGroupViewModel.IsExpanded, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.Resources>
|
||||
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyGroupViewModel}">
|
||||
<ContentControl s:View.Model="{Binding TimelineGroupViewModel}"
|
||||
IsTabStop="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
Visibility="{Binding IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"/>
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="{x:Type layerProperties:LayerPropertyViewModel}">
|
||||
<ContentControl s:View.Model="{Binding TimelinePropertyViewModel}"
|
||||
IsTabStop="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
Visibility="{Binding IsVisible, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.Resources>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
-73
@@ -1,73 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public sealed class TimelineGroupViewModel : Screen
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
|
||||
public TimelineGroupViewModel(LayerPropertyGroupViewModel layerPropertyGroupViewModel, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
|
||||
LayerPropertyGroupViewModel = layerPropertyGroupViewModel;
|
||||
LayerPropertyGroup = LayerPropertyGroupViewModel.LayerPropertyGroup;
|
||||
KeyframePositions = new BindableCollection<double>();
|
||||
|
||||
UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
public LayerPropertyGroupViewModel LayerPropertyGroupViewModel { get; }
|
||||
public LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
|
||||
public BindableCollection<double> KeyframePositions { get; }
|
||||
|
||||
public void UpdateKeyframePositions()
|
||||
{
|
||||
KeyframePositions.Clear();
|
||||
KeyframePositions.AddRange(LayerPropertyGroupViewModel
|
||||
.GetAllKeyframeViewModels(false)
|
||||
.Select(p => p.Position.TotalSeconds * _profileEditorService.PixelsPerSecond));
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
LayerPropertyGroupViewModel.PropertyChanged += LayerPropertyGroupViewModelOnPropertyChanged;
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
LayerPropertyGroupViewModel.PropertyChanged -= LayerPropertyGroupViewModelOnPropertyChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void LayerPropertyGroupViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(LayerPropertyGroupViewModel.IsExpanded))
|
||||
UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateKeyframePositions();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
||||
{
|
||||
public sealed class TimelineKeyframeViewModel<T> : Screen, ITimelineKeyframeViewModel
|
||||
{
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
|
||||
private bool _isSelected;
|
||||
private string _timestamp;
|
||||
private double _x;
|
||||
|
||||
public TimelineKeyframeViewModel(LayerPropertyKeyframe<T> layerPropertyKeyframe, IProfileEditorService profileEditorService)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
LayerPropertyKeyframe = layerPropertyKeyframe;
|
||||
EasingViewModels = new BindableCollection<TimelineEasingViewModel>();
|
||||
}
|
||||
|
||||
public LayerPropertyKeyframe<T> LayerPropertyKeyframe { get; }
|
||||
public BindableCollection<TimelineEasingViewModel> EasingViewModels { get; }
|
||||
|
||||
public double X
|
||||
{
|
||||
get => _x;
|
||||
set => SetAndNotify(ref _x, value);
|
||||
}
|
||||
|
||||
public string Timestamp
|
||||
{
|
||||
get => _timestamp;
|
||||
set => SetAndNotify(ref _timestamp, value);
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set => SetAndNotify(ref _isSelected, value);
|
||||
}
|
||||
|
||||
public TimeSpan Position => LayerPropertyKeyframe.Position;
|
||||
public ILayerPropertyKeyframe Keyframe => LayerPropertyKeyframe;
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
_profileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
LayerPropertyKeyframe.PropertyChanged += LayerPropertyKeyframeOnPropertyChanged;
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
protected override void OnClose()
|
||||
{
|
||||
_profileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
||||
LayerPropertyKeyframe.PropertyChanged -= LayerPropertyKeyframeOnPropertyChanged;
|
||||
|
||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
||||
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Update()
|
||||
{
|
||||
X = _profileEditorService.PixelsPerSecond * LayerPropertyKeyframe.Position.TotalSeconds;
|
||||
Timestamp = $"{Math.Floor(LayerPropertyKeyframe.Position.TotalSeconds):00}.{LayerPropertyKeyframe.Position.Milliseconds:000}";
|
||||
}
|
||||
|
||||
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
|
||||
private void LayerPropertyKeyframeOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(LayerPropertyKeyframe.Position))
|
||||
Update();
|
||||
}
|
||||
|
||||
#region Easing
|
||||
|
||||
public void PopulateEasingViewModels()
|
||||
{
|
||||
if (EasingViewModels.Any())
|
||||
return;
|
||||
|
||||
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
|
||||
.Cast<Easings.Functions>()
|
||||
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
|
||||
|
||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
|
||||
}
|
||||
|
||||
public void ClearEasingViewModels()
|
||||
{
|
||||
EasingViewModels.Clear();
|
||||
}
|
||||
|
||||
private void TimelineEasingViewModelOnEasingModeSelected(object sender, EventArgs e)
|
||||
{
|
||||
SelectEasingMode((TimelineEasingViewModel) sender);
|
||||
}
|
||||
|
||||
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
|
||||
{
|
||||
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
|
||||
// Set every selection to false except on the VM that made the change
|
||||
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
||||
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
||||
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Movement
|
||||
|
||||
private TimeSpan? _offset;
|
||||
|
||||
public void ReleaseMovement()
|
||||
{
|
||||
_offset = null;
|
||||
}
|
||||
|
||||
public void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source)
|
||||
{
|
||||
if (source == this)
|
||||
{
|
||||
_offset = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_offset != null)
|
||||
return;
|
||||
|
||||
_offset = LayerPropertyKeyframe.Position - source.Position;
|
||||
}
|
||||
|
||||
public void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source)
|
||||
{
|
||||
if (source == this || _offset == null)
|
||||
return;
|
||||
|
||||
UpdatePosition(source.Position + _offset.Value);
|
||||
}
|
||||
|
||||
public void UpdatePosition(TimeSpan position)
|
||||
{
|
||||
if (position < TimeSpan.Zero)
|
||||
LayerPropertyKeyframe.Position = TimeSpan.Zero;
|
||||
else if (position > _profileEditorService.SelectedProfileElement.Timeline.Length)
|
||||
LayerPropertyKeyframe.Position = _profileEditorService.SelectedProfileElement.Timeline.Length;
|
||||
else
|
||||
LayerPropertyKeyframe.Position = position;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
public void Delete(bool save = true)
|
||||
{
|
||||
LayerPropertyKeyframe.LayerProperty.RemoveKeyframe(LayerPropertyKeyframe);
|
||||
if (save)
|
||||
_profileEditorService.SaveSelectedProfileElement();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public interface ITimelineKeyframeViewModel : IScreen
|
||||
{
|
||||
bool IsSelected { get; set; }
|
||||
TimeSpan Position { get; }
|
||||
ILayerPropertyKeyframe Keyframe { get; }
|
||||
|
||||
#region Movement
|
||||
|
||||
void SaveOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||
void ApplyOffsetToKeyframe(ITimelineKeyframeViewModel source);
|
||||
void UpdatePosition(TimeSpan position);
|
||||
void ReleaseMovement();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Context menu actions
|
||||
|
||||
void PopulateEasingViewModels();
|
||||
void ClearEasingViewModels();
|
||||
void Delete(bool save = true);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1110 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user