mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Core - Reworked brush/effect property storage
Profile editor - Added brush selection Profile editor - Added playback controls
This commit is contained in:
1 parent
022beb6a48
commit
1832a25426
64 files changed
+1168
-481
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using Artemis.Core;
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
@@ -6,11 +7,14 @@ namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||
/// <summary>
|
||||
/// Represents a profile editor command that can be used to change the brush of a layer.
|
||||
/// </summary>
|
||||
public class ChangeLayerBrush : IProfileEditorCommand
|
||||
public class ChangeLayerBrush : IProfileEditorCommand, IDisposable
|
||||
{
|
||||
private readonly Layer _layer;
|
||||
private readonly LayerBrushDescriptor _layerBrushDescriptor;
|
||||
private readonly LayerBrushDescriptor? _previousDescriptor;
|
||||
private readonly BaseLayerBrush? _previousBrush;
|
||||
|
||||
private BaseLayerBrush? _newBrush;
|
||||
private bool _executed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="ChangeLayerBrush" /> class.
|
||||
@@ -19,7 +23,7 @@ public class ChangeLayerBrush : IProfileEditorCommand
|
||||
{
|
||||
_layer = layer;
|
||||
_layerBrushDescriptor = layerBrushDescriptor;
|
||||
_previousDescriptor = layer.LayerBrush?.Descriptor;
|
||||
_previousBrush = _layer.LayerBrush;
|
||||
}
|
||||
|
||||
#region Implementation of IProfileEditorCommand
|
||||
@@ -30,14 +34,32 @@ public class ChangeLayerBrush : IProfileEditorCommand
|
||||
/// <inheritdoc />
|
||||
public void Execute()
|
||||
{
|
||||
_layer.ChangeLayerBrush(_layerBrushDescriptor);
|
||||
// Create the new brush
|
||||
_newBrush ??= _layerBrushDescriptor.CreateInstance(_layer, null);
|
||||
// Change the brush to the new brush
|
||||
_layer.ChangeLayerBrush(_newBrush);
|
||||
|
||||
_executed = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Undo()
|
||||
{
|
||||
if (_previousDescriptor != null)
|
||||
_layer.ChangeLayerBrush(_previousDescriptor);
|
||||
_layer.ChangeLayerBrush(_previousBrush);
|
||||
_executed = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_executed)
|
||||
_previousBrush?.Dispose();
|
||||
else
|
||||
_newBrush?.Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -3,22 +3,86 @@ using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access the the profile editor back-end logic.
|
||||
/// </summary>
|
||||
public interface IProfileEditorService : IArtemisSharedUIService
|
||||
{
|
||||
public interface IProfileEditorService : IArtemisSharedUIService
|
||||
{
|
||||
IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
|
||||
IObservable<RenderProfileElement?> ProfileElement { get; }
|
||||
IObservable<ProfileEditorHistory?> History { get; }
|
||||
IObservable<TimeSpan> Time { get; }
|
||||
IObservable<double> PixelsPerSecond { get; }
|
||||
/// <summary>
|
||||
/// Gets an observable of the currently selected profile configuration.
|
||||
/// </summary>
|
||||
IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
|
||||
|
||||
void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
||||
void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
|
||||
void ChangeTime(TimeSpan time);
|
||||
/// <summary>
|
||||
/// Gets an observable of the currently selected profile element.
|
||||
/// </summary>
|
||||
IObservable<RenderProfileElement?> ProfileElement { get; }
|
||||
|
||||
void ExecuteCommand(IProfileEditorCommand command);
|
||||
void SaveProfile();
|
||||
Task SaveProfileAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets an observable of the current editor history.
|
||||
/// </summary>
|
||||
IObservable<ProfileEditorHistory?> History { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an observable of the profile preview playback time.
|
||||
/// </summary>
|
||||
IObservable<TimeSpan> Time { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an observable of the profile preview playing state.
|
||||
/// </summary>
|
||||
IObservable<bool> Playing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an observable of the zoom level.
|
||||
/// </summary>
|
||||
IObservable<double> PixelsPerSecond { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Changes the selected profile by its <see cref="Core.ProfileConfiguration" />.
|
||||
/// </summary>
|
||||
/// <param name="profileConfiguration">The profile configuration of the profile to select.</param>
|
||||
void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the selected profile element.
|
||||
/// </summary>
|
||||
/// <param name="renderProfileElement">The profile element to select.</param>
|
||||
void ChangeCurrentProfileElement(RenderProfileElement? renderProfileElement);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current profile preview playback time.
|
||||
/// </summary>
|
||||
/// <param name="time">The new time.</param>
|
||||
void ChangeTime(TimeSpan time);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the provided command and adds it to the history.
|
||||
/// </summary>
|
||||
/// <param name="command">The command to execute.</param>
|
||||
void ExecuteCommand(IProfileEditorCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the current profile.
|
||||
/// </summary>
|
||||
void SaveProfile();
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously saves the current profile.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the save action.</returns>
|
||||
Task SaveProfileAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Resumes profile preview playback.
|
||||
/// </summary>
|
||||
void Play();
|
||||
|
||||
/// <summary>
|
||||
/// Pauses profile preview playback.
|
||||
/// </summary>
|
||||
void Pause();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services.Interfaces;
|
||||
using Serilog;
|
||||
|
||||
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
||||
|
||||
@@ -15,19 +16,27 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
private readonly Dictionary<ProfileConfiguration, ProfileEditorHistory> _profileEditorHistories = new();
|
||||
private readonly BehaviorSubject<RenderProfileElement?> _profileElementSubject = new(null);
|
||||
private readonly BehaviorSubject<TimeSpan> _timeSubject = new(TimeSpan.Zero);
|
||||
private readonly BehaviorSubject<bool> _playingSubject = new(false);
|
||||
private readonly BehaviorSubject<bool> _suspendedEditingSubject = new(false);
|
||||
private readonly BehaviorSubject<double> _pixelsPerSecondSubject = new(300);
|
||||
private readonly ILogger _logger;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IModuleService _moduleService;
|
||||
private readonly IWindowService _windowService;
|
||||
|
||||
public ProfileEditorService(IProfileService profileService, IWindowService windowService)
|
||||
public ProfileEditorService(ILogger logger, IProfileService profileService, IModuleService moduleService, IWindowService windowService)
|
||||
{
|
||||
_logger = logger;
|
||||
_profileService = profileService;
|
||||
_moduleService = moduleService;
|
||||
_windowService = windowService;
|
||||
|
||||
ProfileConfiguration = _profileConfigurationSubject.AsObservable();
|
||||
ProfileElement = _profileElementSubject.AsObservable();
|
||||
History = Observable.Defer(() => Observable.Return(GetHistory(_profileConfigurationSubject.Value))).Concat(ProfileConfiguration.Select(GetHistory));
|
||||
Time = _timeSubject.AsObservable();
|
||||
Playing = _playingSubject.AsObservable();
|
||||
SuspendedEditing = _suspendedEditingSubject.AsObservable();
|
||||
PixelsPerSecond = _pixelsPerSecondSubject.AsObservable();
|
||||
}
|
||||
|
||||
@@ -47,10 +56,46 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
public IObservable<RenderProfileElement?> ProfileElement { get; }
|
||||
public IObservable<ProfileEditorHistory?> History { get; }
|
||||
public IObservable<TimeSpan> Time { get; }
|
||||
public IObservable<bool> Playing { get; }
|
||||
public IObservable<bool> SuspendedEditing { get; }
|
||||
public IObservable<double> PixelsPerSecond { get; }
|
||||
|
||||
public void ChangeCurrentProfileConfiguration(ProfileConfiguration? profileConfiguration)
|
||||
{
|
||||
if (ReferenceEquals(_profileConfigurationSubject.Value, profileConfiguration))
|
||||
return;
|
||||
|
||||
_logger.Verbose("ChangeCurrentProfileConfiguration {profile}", profileConfiguration);
|
||||
|
||||
// Stop playing and save the current profile
|
||||
Pause();
|
||||
if (_profileConfigurationSubject.Value?.Profile != null)
|
||||
_profileConfigurationSubject.Value.Profile.LastSelectedProfileElement = _profileElementSubject.Value;
|
||||
SaveProfile();
|
||||
|
||||
// No need to deactivate the profile, if needed it will be deactivated next update
|
||||
if (_profileConfigurationSubject.Value != null)
|
||||
_profileConfigurationSubject.Value.IsBeingEdited = false;
|
||||
|
||||
// Deselect whatever profile element was active
|
||||
ChangeCurrentProfileElement(null);
|
||||
|
||||
// The new profile may need activation
|
||||
if (profileConfiguration != null)
|
||||
{
|
||||
profileConfiguration.IsBeingEdited = true;
|
||||
_moduleService.SetActivationOverride(profileConfiguration.Module);
|
||||
_profileService.ActivateProfile(profileConfiguration);
|
||||
_profileService.RenderForEditor = true;
|
||||
|
||||
if (profileConfiguration.Profile?.LastSelectedProfileElement is RenderProfileElement renderProfileElement)
|
||||
ChangeCurrentProfileElement(renderProfileElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_moduleService.SetActivationOverride(null);
|
||||
_profileService.RenderForEditor = false;
|
||||
}
|
||||
_profileConfigurationSubject.OnNext(profileConfiguration);
|
||||
}
|
||||
|
||||
@@ -61,6 +106,7 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
|
||||
public void ChangeTime(TimeSpan time)
|
||||
{
|
||||
Tick(time);
|
||||
_timeSubject.OnNext(time);
|
||||
}
|
||||
|
||||
@@ -101,4 +147,48 @@ internal class ProfileEditorService : IProfileEditorService
|
||||
{
|
||||
await Task.Run(SaveProfile);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Play()
|
||||
{
|
||||
if (!_playingSubject.Value)
|
||||
_playingSubject.OnNext(true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Pause()
|
||||
{
|
||||
if (_playingSubject.Value)
|
||||
_playingSubject.OnNext(false);
|
||||
}
|
||||
|
||||
private void Tick(TimeSpan time)
|
||||
{
|
||||
if (_profileConfigurationSubject.Value?.Profile == null || _suspendedEditingSubject.Value)
|
||||
return;
|
||||
|
||||
TickProfileElement(_profileConfigurationSubject.Value.Profile.GetRootFolder(), time);
|
||||
}
|
||||
|
||||
private void TickProfileElement(ProfileElement profileElement, TimeSpan time)
|
||||
{
|
||||
if (profileElement is not RenderProfileElement renderElement)
|
||||
return;
|
||||
|
||||
if (renderElement.Suspended)
|
||||
{
|
||||
renderElement.Disable();
|
||||
}
|
||||
else
|
||||
{
|
||||
renderElement.Enable();
|
||||
renderElement.Timeline.Override(
|
||||
time,
|
||||
(renderElement != _profileElementSubject.Value || renderElement.Timeline.Length < time) && renderElement.Timeline.PlayMode == TimelinePlayMode.Repeat
|
||||
);
|
||||
|
||||
foreach (ProfileElement child in renderElement.Children)
|
||||
TickProfileElement(child, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,8 @@
|
||||
</Styles.Resources>
|
||||
<StyleInclude Source="/Styles/Border.axaml" />
|
||||
<StyleInclude Source="/Styles/Button.axaml" />
|
||||
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
||||
<StyleInclude Source="/Styles/TextBox.axaml" />
|
||||
<StyleInclude Source="/Styles/Condensed.axaml" />
|
||||
<StyleInclude Source="/Styles/TextBlock.axaml" />
|
||||
<StyleInclude Source="/Styles/Sidebar.axaml" />
|
||||
<StyleInclude Source="/Styles/InfoBar.axaml" />
|
||||
<StyleInclude Source="/Styles/TreeView.axaml" />
|
||||
|
||||
+26
-7
@@ -1,6 +1,7 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:controls1="clr-namespace:Artemis.UI.Shared.Controls">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="50">
|
||||
<StackPanel Spacing="5">
|
||||
@@ -24,6 +25,9 @@
|
||||
<ComboBoxItem>Bluheheheheh</ComboBoxItem>
|
||||
<ComboBoxItem>Bluhgfdgdsheheh</ComboBoxItem>
|
||||
</ComboBox>
|
||||
|
||||
<controls:ColorPickerButton Color="Firebrick"></controls:ColorPickerButton>
|
||||
<controls:ColorPickerButton Color="Firebrick" Classes="condensed"></controls:ColorPickerButton>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
@@ -31,19 +35,34 @@
|
||||
<!-- Add Styles Here -->
|
||||
<Style Selector="TextBox.condensed">
|
||||
<Setter Property="Padding" Value="4 2" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="MinHeight" Value="25" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|NumberBox.condensed /template/ TextBox#InputBox">
|
||||
<Setter Property="Padding" Value="4 2" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="MinHeight" Value="25" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="MinHeight" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="ComboBox.condensed">
|
||||
<Setter Property="Padding" Value="4 2" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Height" Value="25" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls1|EnumComboBox.condensed > ComboBox">
|
||||
<Setter Property="Padding" Value="4 2" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="controls|ColorPickerButton.condensed">
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.72" ScaleY="0.72"></ScaleTransform>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</Styles>
|
||||
@@ -12,6 +12,7 @@
|
||||
<ItemGroup>
|
||||
<None Remove="Artemis.UI.Avalonia.csproj.DotSettings" />
|
||||
<None Remove="Artemis.UI.csproj.DotSettings" />
|
||||
<None Remove="Assets\Images\Logo\application.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.11" />
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using FluentAvalonia.UI.Media;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converts <see cref="SKColor" /> into <see cref="Color2" />.
|
||||
/// </summary>
|
||||
public class SKColorToColor2Converter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is SKColor skColor)
|
||||
return new Color2(skColor.Red, skColor.Green, skColor.Blue, skColor.Alpha);
|
||||
return new Color2();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is Color2 color2)
|
||||
return new SKColor(color2.R, color2.G, color2.B, color2.A);
|
||||
return SKColor.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Artemis.UI.Converters;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Converts <see cref="SKColor" />into <see cref="T:System.String" />.
|
||||
/// </summary>
|
||||
public class SKColorToStringConverter : IValueConverter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value as string))
|
||||
return SKColor.Empty;
|
||||
|
||||
return SKColor.TryParse((string) value!, out SKColor color) ? color : SKColor.Empty;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
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.DefaultTypes.PropertyInput.BoolPropertyInputView">
|
||||
TODO
|
||||
<controls:EnumComboBox Classes="condensed" Width="200" Value="{Binding SelectedBooleanOption}" VerticalAlignment="Center" />
|
||||
</UserControl>
|
||||
@@ -1,13 +1,31 @@
|
||||
using Artemis.Core;
|
||||
using System;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using Artemis.UI.Shared.Services.PropertyInput;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.DefaultTypes.PropertyInput;
|
||||
|
||||
public class BoolPropertyInputViewModel : PropertyInputViewModel<bool>
|
||||
{
|
||||
private BooleanOptions _selectedBooleanOption;
|
||||
|
||||
public BoolPropertyInputViewModel(LayerProperty<bool> layerProperty, IProfileEditorService profileEditorService, IPropertyInputService propertyInputService)
|
||||
: base(layerProperty, profileEditorService, propertyInputService)
|
||||
{
|
||||
this.WhenAnyValue(vm => vm.InputValue).Subscribe(v => SelectedBooleanOption = v ? BooleanOptions.True : BooleanOptions.False);
|
||||
this.WhenAnyValue(vm => vm.SelectedBooleanOption).Subscribe(v => InputValue = v == BooleanOptions.True);
|
||||
}
|
||||
|
||||
public BooleanOptions SelectedBooleanOption
|
||||
{
|
||||
get => _selectedBooleanOption;
|
||||
set => this.RaiseAndSetIfChanged(ref _selectedBooleanOption, value);
|
||||
}
|
||||
}
|
||||
|
||||
public enum BooleanOptions
|
||||
{
|
||||
True,
|
||||
False
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
</UserControl.Styles>
|
||||
<ComboBox Classes="brush condensed"
|
||||
Width="200"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Items="{Binding Descriptors}"
|
||||
SelectedItem="{Binding SelectedDescriptor}">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
||||
@@ -66,6 +66,16 @@ public class BrushPropertyInputViewModel : PropertyInputViewModel<LayerBrushRefe
|
||||
Dispatcher.UIThread.InvokeAsync(() => _windowService.CreateContentDialog().WithViewModel(out LayerBrushPresetViewModel _, ("layerBrush", layer.LayerBrush)).ShowAsync());
|
||||
}
|
||||
|
||||
#region Overrides of PropertyInputViewModel<LayerBrushReference>
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInputValueChanged()
|
||||
{
|
||||
this.RaisePropertyChanged(nameof(SelectedDescriptor));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateDescriptorsIfChanged(PluginFeatureEventArgs e)
|
||||
{
|
||||
if (e.PluginFeature is not LayerBrushProvider)
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
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.DefaultTypes.PropertyInput.EnumPropertyInputView">
|
||||
TODO
|
||||
</UserControl>
|
||||
<controls:EnumComboBox Classes="condensed" Width="200" Value="{Binding InputValue}" VerticalAlignment="Center" />
|
||||
</UserControl>
|
||||
@@ -2,7 +2,31 @@
|
||||
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:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.DefaultTypes.PropertyInput.SKColorPropertyInputView">
|
||||
TODO
|
||||
</UserControl>
|
||||
<UserControl.Resources>
|
||||
<converters:SKColorToStringConverter x:Key="SKColorToStringConverter" />
|
||||
<converters:SKColorToColor2Converter x:Key="SKColorToColor2Converter" />
|
||||
</UserControl.Resources>
|
||||
<Grid Height="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox Classes="condensed"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Text="{Binding InputValue, Converter={StaticResource SKColorToStringConverter}}"/>
|
||||
<controls:ColorPickerButton Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Color="{Binding InputValue, Converter={StaticResource SKColorToColor2Converter}}"
|
||||
Classes="condensed"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="-6 0 -8 0"/>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.MainWindow"
|
||||
Icon="/Assets/Images/Logo/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis 2.0">
|
||||
<!-- Use a panel here so the main window can host ContentDialogs -->
|
||||
<Panel>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Screens.Device;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Screens.ProfileEditor;
|
||||
@@ -67,6 +69,8 @@ namespace Artemis.UI.Ninject.Factories
|
||||
{
|
||||
ProfileElementPropertyViewModel ProfileElementPropertyViewModel(ILayerProperty layerProperty);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush layerBrush);
|
||||
ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerEffect layerEffect);
|
||||
|
||||
TreeGroupViewModel TreeGroupViewModel(ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel);
|
||||
// TimelineGroupViewModel TimelineGroupViewModel(ProfileElementPropertiesViewModel profileElementPropertiesViewModel);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
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/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis | Debugger"
|
||||
Width="1200"
|
||||
Height="800">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
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/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis | Device Properties"
|
||||
Width="1250"
|
||||
Height="900">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
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/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="{Binding DisplayName}"
|
||||
Width="800"
|
||||
Height="800"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<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="48"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.Playback.PlaybackView">
|
||||
<DockPanel Margin="8 0">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<Button Classes="icon-button icon-button-large"
|
||||
ToolTip.Tip="Play from start (Shift+Space)" Command="{Binding PlayFromStart}"
|
||||
Focusable="False">
|
||||
<avalonia:MaterialIcon Kind="StepForward" />
|
||||
</Button>
|
||||
<Button Classes="icon-button icon-button-large"
|
||||
ToolTip.Tip="Toggle play/pause (Space)" Command="{Binding TogglePlay}" Focusable="False">
|
||||
<StackPanel>
|
||||
<avalonia:MaterialIcon Kind="Play" IsVisible="{Binding !Playing}" />
|
||||
<avalonia:MaterialIcon Kind="Pause" IsVisible="{Binding Playing}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Classes="icon-button icon-button-large" ToolTip.Tip="Go to start" Command="{Binding GoToStart}" Focusable="False">
|
||||
<avalonia:MaterialIcon Kind="SkipBackward" />
|
||||
</Button>
|
||||
<Button Classes="icon-button icon-button-large" ToolTip.Tip="Go to end" Command="{Binding GoToEnd}" Focusable="False">
|
||||
<avalonia:MaterialIcon Kind="SkipForward" />
|
||||
</Button>
|
||||
<Button Classes="icon-button icon-button-large" ToolTip.Tip="Previous frame" Command="{Binding GoToPreviousFrame}" Focusable="False">
|
||||
<avalonia:MaterialIcon Kind="SkipPrevious" />
|
||||
</Button>
|
||||
<Button Classes="icon-button icon-button-large" ToolTip.Tip="Next frame" Command="{Binding GoToNextFrame}" Focusable="False">
|
||||
<avalonia:MaterialIcon Kind="SkipNext" />
|
||||
</Button>
|
||||
<ToggleButton Classes="icon-button icon-button-large"
|
||||
IsChecked="{Binding Repeating}"
|
||||
Focusable="False"
|
||||
Command="{Binding CycleRepeating}">
|
||||
<ToolTip.Tip>
|
||||
<StackPanel>
|
||||
<StackPanel IsVisible="{Binding Repeating}">
|
||||
<TextBlock Text="Repeat entire timeline"
|
||||
IsVisible="{Binding RepeatTimeline}" />
|
||||
<TextBlock Text="Repeat segment"
|
||||
IsVisible="{Binding RepeatSegment}" />
|
||||
</StackPanel>
|
||||
<TextBlock IsVisible="{Binding !Repeating}">
|
||||
Don't repeat the timeline
|
||||
</TextBlock>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
This setting only applies to the editor and does not affect the repeat mode during normal profile playback
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</ToolTip.Tip>
|
||||
<Grid>
|
||||
<avalonia:MaterialIcon Kind="Repeat" IsVisible="{Binding RepeatTimeline}" />
|
||||
<avalonia:MaterialIcon Kind="RepeatOne" IsVisible="{Binding RepeatSegment}" />
|
||||
</Grid>
|
||||
|
||||
</ToggleButton>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="h4" Text="{Binding FormattedCurrentTime, FallbackValue=00.000}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0"/>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Playback
|
||||
{
|
||||
public partial class PlaybackView : ReactiveUserControl<PlaybackViewModel>
|
||||
{
|
||||
public PlaybackView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Playback;
|
||||
|
||||
public class PlaybackViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private RenderProfileElement? _profileElement;
|
||||
private ObservableAsPropertyHelper<TimeSpan>? _currentTime;
|
||||
private ObservableAsPropertyHelper<string?>? _formattedCurrentTime;
|
||||
private ObservableAsPropertyHelper<bool>? _playing;
|
||||
private bool _repeating;
|
||||
private bool _repeatTimeline;
|
||||
private bool _repeatSegment;
|
||||
|
||||
public PlaybackViewModel(ICoreService coreService, IProfileEditorService profileEditorService, ISettingsService settingsService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_profileEditorService = profileEditorService;
|
||||
_settingsService = settingsService;
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
_profileEditorService.ProfileElement.Subscribe(e => _profileElement = e).DisposeWith(d);
|
||||
_currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d);
|
||||
_formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d);
|
||||
_playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d);
|
||||
|
||||
Observable.FromEventPattern<FrameRenderingEventArgs>(x => coreService.FrameRendering += x, x => coreService.FrameRendering -= x)
|
||||
.Subscribe(e => CoreServiceOnFrameRendering(e.EventArgs))
|
||||
.DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public TimeSpan CurrentTime => _currentTime?.Value ?? TimeSpan.Zero;
|
||||
public string? FormattedCurrentTime => _formattedCurrentTime?.Value;
|
||||
public bool Playing => _playing?.Value ?? false;
|
||||
|
||||
public bool Repeating
|
||||
{
|
||||
get => _repeating;
|
||||
set => this.RaiseAndSetIfChanged(ref _repeating, value);
|
||||
}
|
||||
|
||||
public bool RepeatTimeline
|
||||
{
|
||||
get => _repeatTimeline;
|
||||
set => this.RaiseAndSetIfChanged(ref _repeatTimeline, value);
|
||||
}
|
||||
|
||||
public bool RepeatSegment
|
||||
{
|
||||
get => _repeatSegment;
|
||||
set => this.RaiseAndSetIfChanged(ref _repeatSegment, value);
|
||||
}
|
||||
|
||||
public void PlayFromStart()
|
||||
{
|
||||
GoToStart();
|
||||
if (!Playing)
|
||||
_profileEditorService.Play();
|
||||
}
|
||||
|
||||
public void TogglePlay()
|
||||
{
|
||||
if (!Playing)
|
||||
_profileEditorService.Play();
|
||||
else
|
||||
_profileEditorService.Pause();
|
||||
}
|
||||
|
||||
public void GoToStart()
|
||||
{
|
||||
_profileEditorService.ChangeTime(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
public void GoToEnd()
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return;
|
||||
|
||||
_profileEditorService.ChangeTime(_profileElement.Timeline.EndSegmentEndPosition);
|
||||
}
|
||||
|
||||
public void GoToPreviousFrame()
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return;
|
||||
|
||||
double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
||||
double newTime = Math.Max(0, Math.Round((CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
||||
_profileEditorService.ChangeTime(TimeSpan.FromMilliseconds(newTime));
|
||||
}
|
||||
|
||||
public void GoToNextFrame()
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return;
|
||||
|
||||
double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
||||
double newTime = Math.Round((CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
||||
newTime = Math.Min(newTime, _profileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds);
|
||||
_profileEditorService.ChangeTime(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()
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
|
||||
return TimeSpan.Zero;
|
||||
if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
|
||||
return _profileElement.Timeline.MainSegmentStartPosition;
|
||||
if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
|
||||
return _profileElement.Timeline.EndSegmentStartPosition;
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private TimeSpan GetCurrentSegmentEnd()
|
||||
{
|
||||
if (_profileElement == null)
|
||||
return TimeSpan.Zero;
|
||||
|
||||
if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
|
||||
return _profileElement.Timeline.StartSegmentEndPosition;
|
||||
if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
|
||||
return _profileElement.Timeline.MainSegmentEndPosition;
|
||||
if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
|
||||
return _profileElement.Timeline.EndSegmentEndPosition;
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
|
||||
private void CoreServiceOnFrameRendering(FrameRenderingEventArgs e)
|
||||
{
|
||||
if (!Playing)
|
||||
return;
|
||||
|
||||
TimeSpan newTime = CurrentTime.Add(TimeSpan.FromSeconds(e.DeltaTime));
|
||||
if (_profileElement != null)
|
||||
{
|
||||
if (Repeating && RepeatTimeline)
|
||||
{
|
||||
if (newTime > _profileElement.Timeline.Length)
|
||||
newTime = TimeSpan.Zero;
|
||||
}
|
||||
else if (Repeating && RepeatSegment)
|
||||
{
|
||||
if (newTime > GetCurrentSegmentEnd())
|
||||
newTime = GetCurrentSegmentStart();
|
||||
}
|
||||
else if (newTime > _profileElement.Timeline.Length)
|
||||
{
|
||||
newTime = _profileElement.Timeline.Length;
|
||||
_profileEditorService.Pause();
|
||||
}
|
||||
}
|
||||
|
||||
_profileEditorService.ChangeTime(newTime);
|
||||
}
|
||||
}
|
||||
+38
-11
@@ -3,18 +3,45 @@
|
||||
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.ProfileEditor.ProfileElementProperties"
|
||||
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.ProfileEditor.ProfileElementProperties.ProfileElementPropertiesView">
|
||||
<Grid ColumnDefinitions="*,Auto,*">
|
||||
<ItemsControl Grid.Column="0" Items="{Binding PropertyGroupViewModels}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||
</TreeDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<GridSplitter Grid.Column="1"></GridSplitter>
|
||||
</Grid>
|
||||
<Grid ColumnDefinitions="*,Auto,*" RowDefinitions="48,*">
|
||||
<ContentControl Grid.Row="0" Content="{Binding PlaybackViewModel}"></ContentControl>
|
||||
|
||||
</UserControl>
|
||||
<GridSplitter Grid.Row="0" Grid.Column="1" Cursor="SizeWestEast" Foreground="Transparent" Background="Transparent" />
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Name="TreeScrollViewer"
|
||||
Offset="{Binding #TimelineScrollViewer.Offset, Mode=OneWay}"
|
||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||
<ItemsControl Items="{Binding PropertyGroupViewModels}" Padding="0 0 8 0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||
</TreeDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" Cursor="SizeWestEast" Foreground="Transparent" Background="{DynamicResource CardStrokeColorDefaultSolidBrush}" />
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Name="TimelineScrollViewer"
|
||||
Offset="{Binding #TreeScrollViewer.Offset, Mode=OneWay}"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
|
||||
<ItemsControl Items="{Binding PropertyGroupViewModels}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<TreeDataTemplate DataType="{x:Type local:ProfileElementPropertyGroupViewModel}" ItemsSource="{Binding Children}">
|
||||
<ContentControl Content="{Binding TreeGroupViewModel}" />
|
||||
</TreeDataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
+12
-13
@@ -1,18 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties
|
||||
{
|
||||
public partial class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPropertiesViewModel>
|
||||
{
|
||||
public ProfileElementPropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
public class ProfileElementPropertiesView : ReactiveUserControl<ProfileElementPropertiesViewModel>
|
||||
{
|
||||
public ProfileElementPropertiesView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
+43
-49
@@ -6,9 +6,10 @@ using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Screens.ProfileEditor.Playback;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||
using ReactiveUI;
|
||||
@@ -17,20 +18,18 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties;
|
||||
|
||||
public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
||||
{
|
||||
private readonly Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel> _cachedViewModels;
|
||||
private readonly ILayerPropertyVmFactory _layerPropertyVmFactory;
|
||||
private readonly IProfileEditorService _profileEditorService;
|
||||
private ProfileElementPropertyGroupViewModel? _brushPropertyGroup;
|
||||
private ObservableAsPropertyHelper<RenderProfileElement?>? _profileElement;
|
||||
private readonly Dictionary<RenderProfileElement, List<ProfileElementPropertyGroupViewModel>> _profileElementGroups;
|
||||
private ObservableCollection<ProfileElementPropertyGroupViewModel> _propertyGroupViewModels;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory)
|
||||
public ProfileElementPropertiesViewModel(IProfileEditorService profileEditorService, ILayerPropertyVmFactory layerPropertyVmFactory, PlaybackViewModel playbackViewModel)
|
||||
{
|
||||
_profileEditorService = profileEditorService;
|
||||
_layerPropertyVmFactory = layerPropertyVmFactory;
|
||||
PropertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
|
||||
_profileElementGroups = new Dictionary<RenderProfileElement, List<ProfileElementPropertyGroupViewModel>>();
|
||||
_propertyGroupViewModels = new ObservableCollection<ProfileElementPropertyGroupViewModel>();
|
||||
_cachedViewModels = new Dictionary<LayerPropertyGroup, ProfileElementPropertyGroupViewModel>();
|
||||
PlaybackViewModel = playbackViewModel;
|
||||
|
||||
// Subscribe to events of the latest selected profile element - borrowed from https://stackoverflow.com/a/63950940
|
||||
this.WhenAnyValue(vm => vm.ProfileElement)
|
||||
@@ -47,10 +46,11 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
||||
.Subscribe(_ => UpdateGroups());
|
||||
// React to service profile element changes as long as the VM is active
|
||||
|
||||
this.WhenActivated(d => _profileElement = _profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d));
|
||||
this.WhenActivated(d => _profileElement = profileEditorService.ProfileElement.ToProperty(this, vm => vm.ProfileElement).DisposeWith(d));
|
||||
this.WhenAnyValue(vm => vm.ProfileElement).Subscribe(_ => UpdateGroups());
|
||||
}
|
||||
|
||||
public PlaybackViewModel PlaybackViewModel { get; }
|
||||
public RenderProfileElement? ProfileElement => _profileElement?.Value;
|
||||
public Layer? Layer => _profileElement?.Value as Layer;
|
||||
|
||||
@@ -68,57 +68,51 @@ public class ProfileElementPropertiesViewModel : ActivatableViewModelBase
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_profileElementGroups.TryGetValue(ProfileElement, out List<ProfileElementPropertyGroupViewModel>? viewModels))
|
||||
{
|
||||
viewModels = new List<ProfileElementPropertyGroupViewModel>();
|
||||
_profileElementGroups[ProfileElement] = viewModels;
|
||||
}
|
||||
|
||||
List<LayerPropertyGroup> groups = new();
|
||||
|
||||
ObservableCollection<ProfileElementPropertyGroupViewModel> viewModels = new();
|
||||
if (Layer != null)
|
||||
{
|
||||
// Add default layer groups
|
||||
groups.Add(Layer.General);
|
||||
groups.Add(Layer.Transform);
|
||||
// Add brush group
|
||||
// Add base VMs
|
||||
viewModels.Add(GetOrCreateViewModel(Layer.General, null, null));
|
||||
viewModels.Add(GetOrCreateViewModel(Layer.Transform, null, null));
|
||||
|
||||
// Add brush VM if the brush has properties
|
||||
if (Layer.LayerBrush?.BaseProperties != null)
|
||||
groups.Add(Layer.LayerBrush.BaseProperties);
|
||||
viewModels.Add(GetOrCreateViewModel(Layer.LayerBrush.BaseProperties, Layer.LayerBrush, null));
|
||||
}
|
||||
|
||||
// Add effect groups
|
||||
foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects)
|
||||
{
|
||||
// Add effect VMs
|
||||
foreach (BaseLayerEffect layerEffect in ProfileElement.LayerEffects.OrderBy(e => e.Order))
|
||||
if (layerEffect.BaseProperties != null)
|
||||
groups.Add(layerEffect.BaseProperties);
|
||||
}
|
||||
viewModels.Add(GetOrCreateViewModel(layerEffect.BaseProperties, null, layerEffect));
|
||||
|
||||
// Remove redundant VMs
|
||||
viewModels.RemoveAll(vm => !groups.Contains(vm.LayerPropertyGroup));
|
||||
|
||||
// Create VMs for missing groups
|
||||
foreach (LayerPropertyGroup group in groups)
|
||||
// Map the most recent collection of VMs to the current list of VMs, making as little changes to the collection as possible
|
||||
for (int index = 0; index < viewModels.Count; index++)
|
||||
{
|
||||
if (viewModels.All(vm => vm.LayerPropertyGroup != group))
|
||||
viewModels.Add(_layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(group));
|
||||
ProfileElementPropertyGroupViewModel profileElementPropertyGroupViewModel = viewModels[index];
|
||||
if (index > PropertyGroupViewModels.Count - 1)
|
||||
PropertyGroupViewModels.Add(profileElementPropertyGroupViewModel);
|
||||
else if (!ReferenceEquals(PropertyGroupViewModels[index], profileElementPropertyGroupViewModel))
|
||||
PropertyGroupViewModels[index] = profileElementPropertyGroupViewModel;
|
||||
}
|
||||
|
||||
// Get all non-effect properties
|
||||
List<ProfileElementPropertyGroupViewModel> nonEffectProperties = viewModels
|
||||
.Where(l => l.TreeGroupViewModel.GroupType != LayerPropertyGroupType.LayerEffectRoot)
|
||||
.ToList();
|
||||
// Order the effects
|
||||
List<ProfileElementPropertyGroupViewModel> effectProperties = viewModels
|
||||
.Where(l => l.TreeGroupViewModel.GroupType == LayerPropertyGroupType.LayerEffectRoot && l.LayerPropertyGroup.LayerEffect != null)
|
||||
.OrderBy(l => l.LayerPropertyGroup.LayerEffect?.Order)
|
||||
.ToList();
|
||||
while (PropertyGroupViewModels.Count > viewModels.Count)
|
||||
PropertyGroupViewModels.RemoveAt(PropertyGroupViewModels.Count - 1);
|
||||
}
|
||||
|
||||
ObservableCollection<ProfileElementPropertyGroupViewModel> propertyGroupViewModels = new();
|
||||
foreach (ProfileElementPropertyGroupViewModel viewModel in nonEffectProperties)
|
||||
propertyGroupViewModels.Add(viewModel);
|
||||
foreach (ProfileElementPropertyGroupViewModel viewModel in effectProperties)
|
||||
propertyGroupViewModels.Add(viewModel);
|
||||
private ProfileElementPropertyGroupViewModel GetOrCreateViewModel(LayerPropertyGroup layerPropertyGroup, BaseLayerBrush? layerBrush, BaseLayerEffect? layerEffect)
|
||||
{
|
||||
if (_cachedViewModels.TryGetValue(layerPropertyGroup, out ProfileElementPropertyGroupViewModel? cachedVm))
|
||||
return cachedVm;
|
||||
|
||||
PropertyGroupViewModels = propertyGroupViewModels;
|
||||
ProfileElementPropertyGroupViewModel createdVm;
|
||||
if (layerBrush != null)
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerBrush);
|
||||
else if (layerEffect != null)
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup, layerEffect);
|
||||
else
|
||||
createdVm = _layerPropertyVmFactory.ProfileElementPropertyGroupViewModel(layerPropertyGroup);
|
||||
|
||||
_cachedViewModels[layerPropertyGroup] = createdVm;
|
||||
return createdVm;
|
||||
}
|
||||
}
|
||||
+17
@@ -3,6 +3,8 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
using Artemis.UI.Shared;
|
||||
@@ -33,8 +35,23 @@ public class ProfileElementPropertyGroupViewModel : ViewModelBase
|
||||
PopulateChildren();
|
||||
}
|
||||
|
||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerBrush layerBrush)
|
||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||
{
|
||||
LayerBrush = layerBrush;
|
||||
}
|
||||
|
||||
public ProfileElementPropertyGroupViewModel(LayerPropertyGroup layerPropertyGroup, ILayerPropertyVmFactory layerPropertyVmFactory, IPropertyInputService propertyInputService, BaseLayerEffect layerEffect)
|
||||
: this(layerPropertyGroup, layerPropertyVmFactory, propertyInputService)
|
||||
{
|
||||
LayerEffect = layerEffect;
|
||||
}
|
||||
|
||||
public ObservableCollection<ViewModelBase> Children { get; }
|
||||
public LayerPropertyGroup LayerPropertyGroup { get; }
|
||||
public BaseLayerBrush? LayerBrush { get; }
|
||||
public BaseLayerEffect? LayerEffect { get; }
|
||||
|
||||
public TreeGroupViewModel TreeGroupViewModel { get; }
|
||||
|
||||
public bool IsVisible
|
||||
|
||||
+3
-1
@@ -1,9 +1,11 @@
|
||||
using ReactiveUI;
|
||||
using Artemis.Core;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree;
|
||||
|
||||
public interface ITreePropertyViewModel : IReactiveObject
|
||||
{
|
||||
ILayerProperty BaseLayerProperty { get; }
|
||||
bool HasDataBinding { get; }
|
||||
double GetDepth();
|
||||
}
|
||||
+16
-16
@@ -70,24 +70,24 @@
|
||||
<Grid IsVisible="{Binding GroupType, Converter={StaticResource EnumBoolConverter}, ConverterParameter={x:Static viewModel:LayerPropertyGroupType.LayerBrushRoot}}"
|
||||
ColumnDefinitions="Auto,Auto,Auto,*">
|
||||
<controls:ArtemisIcon Grid.Column="0"
|
||||
Icon="{Binding LayerPropertyGroup.LayerBrush.Descriptor.Icon}"
|
||||
Icon="{Binding LayerBrush.Descriptor.Icon}"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0 5 5 0" />
|
||||
<TextBlock Grid.Column="1"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.LayerBrush.Descriptor.Description}"
|
||||
ToolTip.Tip="{Binding LayerBrush.Descriptor.Description}"
|
||||
Margin="0 5 0 0">
|
||||
Brush - 
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2"
|
||||
Text="{Binding LayerPropertyGroup.LayerBrush.Descriptor.DisplayName}"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.LayerBrush.Descriptor.Description}"
|
||||
Text="{Binding LayerBrush.Descriptor.DisplayName}"
|
||||
ToolTip.Tip="{Binding LayerBrush.Descriptor.Description}"
|
||||
Margin="0 5 0 0" />
|
||||
|
||||
<StackPanel Grid.Column="3"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="{Binding LayerPropertyGroup.LayerBrush.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
IsVisible="{Binding LayerBrush.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock VerticalAlignment="Center">Extra options available!</TextBlock>
|
||||
<avalonia:MaterialIcon Kind="ChevronRight" VerticalAlignment="Center">
|
||||
<avalonia:MaterialIcon.RenderTransform>
|
||||
@@ -108,31 +108,31 @@
|
||||
<controls:ArtemisIcon
|
||||
Grid.Column="0"
|
||||
Cursor="SizeNorthSouth"
|
||||
Icon="{Binding LayerPropertyGroup.LayerEffect.Descriptor.Icon}"
|
||||
Icon="{Binding LayerEffect.Descriptor.Icon}"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Margin="0 5 5 0"
|
||||
Background="Transparent" />
|
||||
<TextBlock Grid.Column="1" ToolTip.Tip="{Binding LayerPropertyGroup.LayerEffect.Descriptor.Description}" Margin="0 5 0 0">
|
||||
<TextBlock Grid.Column="1" ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}" Margin="0 5 0 0">
|
||||
Effect
|
||||
</TextBlock>
|
||||
<TextBlock Grid.Column="2"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.LayerEffect.Descriptor.Description}"
|
||||
ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}"
|
||||
Margin="3 5">
|
||||
-
|
||||
</TextBlock>
|
||||
|
||||
<!-- Show either the descriptors display name or, if set, the effect name -->
|
||||
<TextBlock Grid.Column="3"
|
||||
Text="{Binding LayerPropertyGroup.LayerEffect.Descriptor.DisplayName}"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.LayerEffect.Descriptor.Description}"
|
||||
Text="{Binding LayerEffect.Descriptor.DisplayName}"
|
||||
ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}"
|
||||
Margin="0 5"
|
||||
IsVisible="{Binding !LayerPropertyGroup.LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
IsVisible="{Binding !LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
<TextBlock Grid.Column="4"
|
||||
Text="{Binding LayerPropertyGroup.LayerEffect.Name}"
|
||||
ToolTip.Tip="{Binding LayerPropertyGroup.LayerEffect.Descriptor.Description}"
|
||||
Text="{Binding LayerEffect.Name}"
|
||||
ToolTip.Tip="{Binding LayerEffect.Descriptor.Description}"
|
||||
Margin="0 5"
|
||||
IsVisible="{Binding LayerPropertyGroup.LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
IsVisible="{Binding LayerEffect.Name, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<StackPanel Grid.Column="5" Orientation="Horizontal">
|
||||
<ToggleButton
|
||||
@@ -140,7 +140,7 @@
|
||||
ToolTip.Tip="Toggle suspended state"
|
||||
Width="18"
|
||||
Height="18"
|
||||
IsChecked="{Binding !LayerPropertyGroup.LayerEffect.Suspended}"
|
||||
IsChecked="{Binding !LayerEffect.Suspended}"
|
||||
VerticalAlignment="Center" Padding="-25"
|
||||
Margin="5 0"
|
||||
Command="{Binding SuspendedToggled}">
|
||||
@@ -160,7 +160,7 @@
|
||||
Height="24"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenEffectSettings}"
|
||||
IsVisible="{Binding LayerPropertyGroup.LayerEffect.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
IsVisible="{Binding LayerEffect.ConfigurationDialog, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<avalonia:MaterialIcon Kind="Settings" Height="16" Width="16" />
|
||||
</Button>
|
||||
<Button Classes="icon-button"
|
||||
|
||||
+13
-10
@@ -46,14 +46,16 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
|
||||
public ProfileElementPropertyGroupViewModel ProfileElementPropertyGroupViewModel { get; }
|
||||
public LayerPropertyGroup LayerPropertyGroup => ProfileElementPropertyGroupViewModel.LayerPropertyGroup;
|
||||
public BaseLayerBrush? LayerBrush => ProfileElementPropertyGroupViewModel.LayerBrush;
|
||||
public BaseLayerEffect? LayerEffect => ProfileElementPropertyGroupViewModel.LayerEffect;
|
||||
|
||||
public ObservableCollection<ViewModelBase>? Children => ProfileElementPropertyGroupViewModel.IsExpanded ? ProfileElementPropertyGroupViewModel.Children : null;
|
||||
|
||||
public LayerPropertyGroupType GroupType { get; private set; }
|
||||
|
||||
public async Task OpenBrushSettings()
|
||||
{
|
||||
BaseLayerBrush? layerBrush = LayerPropertyGroup.LayerBrush;
|
||||
if (layerBrush?.ConfigurationDialog is not LayerBrushConfigurationDialog configurationViewModel)
|
||||
if (LayerBrush?.ConfigurationDialog is not LayerBrushConfigurationDialog configurationViewModel)
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -65,8 +67,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
|
||||
// Find the BaseLayerBrush parameter, it is required by the base constructor so its there for sure
|
||||
ParameterInfo brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType));
|
||||
ConstructorArgument argument = new(brushParameter.Name!, layerBrush);
|
||||
BrushConfigurationViewModel viewModel = (BrushConfigurationViewModel) layerBrush.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
||||
ConstructorArgument argument = new(brushParameter.Name!, LayerBrush);
|
||||
BrushConfigurationViewModel viewModel =
|
||||
(BrushConfigurationViewModel) LayerBrush.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
||||
|
||||
_brushConfigurationWindowViewModel = new BrushConfigurationWindowViewModel(viewModel, configurationViewModel);
|
||||
await _windowService.ShowDialogAsync(_brushConfigurationWindowViewModel);
|
||||
@@ -82,8 +85,7 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
|
||||
public async Task OpenEffectSettings()
|
||||
{
|
||||
BaseLayerEffect? layerEffect = LayerPropertyGroup.LayerEffect;
|
||||
if (layerEffect?.ConfigurationDialog is not LayerEffectConfigurationDialog configurationViewModel)
|
||||
if (LayerEffect?.ConfigurationDialog is not LayerEffectConfigurationDialog configurationViewModel)
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -95,8 +97,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
|
||||
// Find the BaseLayerEffect parameter, it is required by the base constructor so its there for sure
|
||||
ParameterInfo effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
|
||||
ConstructorArgument argument = new(effectParameter.Name!, layerEffect);
|
||||
EffectConfigurationViewModel viewModel = (EffectConfigurationViewModel) layerEffect.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
||||
ConstructorArgument argument = new(effectParameter.Name!, LayerEffect);
|
||||
EffectConfigurationViewModel viewModel =
|
||||
(EffectConfigurationViewModel) LayerEffect.Descriptor.Provider.Plugin.Kernel!.Get(configurationViewModel.Type, argument);
|
||||
|
||||
_effectConfigurationWindowViewModel = new EffectConfigurationWindowViewModel(viewModel, configurationViewModel);
|
||||
await _windowService.ShowDialogAsync(_effectConfigurationWindowViewModel);
|
||||
@@ -145,9 +148,9 @@ public class TreeGroupViewModel : ActivatableViewModelBase
|
||||
GroupType = LayerPropertyGroupType.General;
|
||||
else if (LayerPropertyGroup is LayerTransformProperties)
|
||||
GroupType = LayerPropertyGroupType.Transform;
|
||||
else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerBrush != null)
|
||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerBrush != null)
|
||||
GroupType = LayerPropertyGroupType.LayerBrushRoot;
|
||||
else if (LayerPropertyGroup.Parent == null && LayerPropertyGroup.LayerEffect != null)
|
||||
else if (LayerPropertyGroup.Parent == null && ProfileElementPropertyGroupViewModel.LayerEffect != null)
|
||||
GroupType = LayerPropertyGroupType.LayerEffectRoot;
|
||||
else
|
||||
GroupType = LayerPropertyGroupType.None;
|
||||
|
||||
+16
-2
@@ -1,13 +1,27 @@
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using Artemis.Core;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
|
||||
{
|
||||
public partial class TreePropertyView : ReactiveUserControl<IActivatableViewModel>
|
||||
public partial class TreePropertyView : ReactiveUserControl<ITreePropertyViewModel>
|
||||
{
|
||||
public TreePropertyView()
|
||||
{
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
if (ViewModel != null)
|
||||
{
|
||||
Observable.FromEventPattern<LayerPropertyEventArgs>(e => ViewModel.BaseLayerProperty.CurrentValueSet += e, e => ViewModel.BaseLayerProperty.CurrentValueSet -= e)
|
||||
.Subscribe(_ => this.BringIntoView())
|
||||
.DisposeWith(d);
|
||||
}
|
||||
});
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
@@ -16,4 +30,4 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileElementProperties.Tree
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -16,6 +16,8 @@ internal class TreePropertyViewModel<T> : ActivatableViewModelBase, ITreePropert
|
||||
public LayerProperty<T> LayerProperty { get; }
|
||||
public ProfileElementPropertyViewModel ProfileElementPropertyViewModel { get; }
|
||||
public PropertyInputViewModel<T>? PropertyInputViewModel { get; }
|
||||
|
||||
public ILayerProperty BaseLayerProperty => LayerProperty;
|
||||
public bool HasDataBinding => LayerProperty.HasDataBinding;
|
||||
|
||||
public double GetDepth()
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
</UserControl.Styles>
|
||||
<Grid ColumnDefinitions="4*,Auto,*" Classes="editor-grid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentControl Content="{Binding MenuBarViewModel}"></ContentControl>
|
||||
<Grid Grid.Column="0" RowDefinitions="3*,Auto,*">
|
||||
@@ -66,8 +66,8 @@
|
||||
|
||||
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
|
||||
|
||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4">
|
||||
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"></ContentControl>
|
||||
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
|
||||
<ContentControl Content="{Binding ProfileElementPropertiesViewModel}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@@ -85,6 +85,6 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding StatusBarViewModel}"></ContentControl>
|
||||
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{Binding StatusBarViewModel}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -126,7 +126,7 @@ namespace Artemis.UI.Screens.Root
|
||||
{
|
||||
_trayIcon = new TrayIcon
|
||||
{
|
||||
Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/bow.ico"))),
|
||||
Icon = new WindowIcon(_assetLoader.Open(new Uri("avares://Artemis.UI/Assets/Images/Logo/application.ico"))),
|
||||
Command = ReactiveCommand.Create(OpenMainWindow)
|
||||
};
|
||||
_trayIcon.Menu = (NativeMenu?) Application.Current!.FindResource("TrayIconMenu");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Avalonia.Svg.Skia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Root.SplashView"
|
||||
Icon="/Assets/Images/Logo/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Title="Artemis 2.0"
|
||||
Height="450"
|
||||
Width="400"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850"
|
||||
x:Class="Artemis.UI.Screens.Sidebar.ProfileConfigurationEditView"
|
||||
Title="{Binding DisplayName}"
|
||||
Icon="/Assets/Images/Logo/bow.ico"
|
||||
Icon="/Assets/Images/Logo/application.ico"
|
||||
Width="800"
|
||||
MinWidth="420"
|
||||
Height="850">
|
||||
|
||||
Reference in new issue
Block a user