Profile editor - Integrated visual scripting

This commit is contained in:
Robert committed 2022-04-03 22:21:56 +02:00
1 parent 372991a69b
commit 66ea718316
96 files changed
+2413 -238

No files matched your search

@@ -0,0 +1,19 @@
using System;
using Artemis.UI.Shared;
namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition
{
public class ConditionTypeViewModel : ViewModelBase
{
public ConditionTypeViewModel(string name, string description, Type? conditionType)
{
Name = name;
Description = description;
ConditionType = conditionType;
}
public string Name { get; }
public string Description { get; }
public Type? ConditionType { get; }
}
}
@@ -4,28 +4,104 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:displayCondition="clr-namespace:Artemis.UI.Screens.ProfileEditor.DisplayCondition"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.Controls.DataModelPicker;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="650"
x:Class="Artemis.UI.Screens.ProfileEditor.DisplayCondition.DisplayConditionScriptView"
x:DataType="displayCondition:DisplayConditionScriptViewModel">
<Grid>
<Grid IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNotNull}}">
<ContentControl Content="{CompiledBinding NodeScriptViewModel}" />
<Button Classes="icon-button"
ToolTip.Tip="Open editor"
VerticalAlignment="Top"
HorizontalAlignment="Right"
Margin="4"
Command="{Binding OpenEditor}">
<avalonia:MaterialIcon Kind="OpenInNew"></avalonia:MaterialIcon>
<UserControl.Styles>
<Style Selector="ComboBox.condition-type /template/ ContentControl#ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type displayCondition:ConditionTypeViewModel}">
<TextBlock Text="{CompiledBinding Name}" VerticalAlignment="Center" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Styles>
<ScrollViewer>
<DockPanel LastChildFill="False">
<DockPanel.Styles>
<Style Selector="DockPanel > TextBlock">
<Setter Property="Margin" Value="0 5"></Setter>
</Style>
</DockPanel.Styles>
<TextBlock DockPanel.Dock="Top">Condition type</TextBlock>
<ComboBox Name="ConditionType"
DockPanel.Dock="Top"
Classes="condition-type"
PlaceholderText="Select a condition type"
Items="{CompiledBinding ConditionTypeViewModels}"
SelectedItem="{CompiledBinding SelectedConditionTypeViewModel}"
HorizontalAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type displayCondition:ConditionTypeViewModel}">
<StackPanel Spacing="5">
<TextBlock Text="{CompiledBinding Name}" TextWrapping="Wrap" MaxWidth="350" />
<TextBlock Text="{CompiledBinding Description}" Foreground="{DynamicResource TextFillColorSecondaryBrush}" TextWrapping="Wrap" MaxWidth="350" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- Event options -->
<DockPanel IsVisible="{CompiledBinding ShowEventOptions}" DockPanel.Dock="Top" HorizontalAlignment="Stretch">
<TextBlock DockPanel.Dock="Top">Triggered by event</TextBlock>
<dataModelPicker:DataModelPickerButton Placeholder="Select an event"
DockPanel.Dock="Top"
HorizontalAlignment="Stretch" />
<TextBlock DockPanel.Dock="Top">When the event fires..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<ComboBoxItem>
Play the timeline once
</ComboBoxItem>
<ComboBoxItem>
Toggle the element on or off
</ComboBoxItem>
</ComboBox>
<TextBlock DockPanel.Dock="Top">And if already playing..</TextBlock>
<ComboBox PlaceholderText="Select a play mode" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<ComboBoxItem>
Restart the timeline
</ComboBoxItem>
<ComboBoxItem>
Play a second copy
</ComboBoxItem>
<ComboBoxItem>
Do nothing
</ComboBoxItem>
</ComboBox>
</DockPanel>
<!-- Static options -->
<DockPanel IsVisible="{CompiledBinding ShowStaticOptions}" HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<TextBlock DockPanel.Dock="Top">While condition is met..</TextBlock>
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a play mode" HorizontalAlignment="Stretch">
<ComboBoxItem>Play the main segment once</ComboBoxItem>
<ComboBoxItem>Repeat the main segment</ComboBoxItem>
</ComboBox>
<TextBlock DockPanel.Dock="Top">And when no longer met..</TextBlock>
<ComboBox DockPanel.Dock="Top" PlaceholderText="Select a stop mode" HorizontalAlignment="Stretch">
<ComboBoxItem>Finish the main segment</ComboBoxItem>
<ComboBoxItem>Skip forward to the end segment</ComboBoxItem>
</ComboBox>
<DockPanel DockPanel.Dock="Top" Margin="0 5">
<avalonia:MaterialIcon Kind="InfoCircle" Margin="5 0"></avalonia:MaterialIcon>
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap">
The start- and end-segments are always played once.
</TextBlock>
</DockPanel>
</DockPanel>
<Button DockPanel.Dock="Bottom" ToolTip.Tip="Open editor" Margin="0 15 0 5" Command="{Binding OpenEditor}" HorizontalAlignment="Stretch">
Edit condition script
</Button>
</Grid>
<Button VerticalAlignment="Center"
HorizontalAlignment="Center"
Command="{Binding EnableConditions}"
IsVisible="{CompiledBinding NodeScriptViewModel, Converter={x:Static ObjectConverters.IsNull}}">
Enable display conditions
</Button>
</Grid>
</DockPanel>
</ScrollViewer>
</UserControl>
@@ -1,11 +1,13 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Artemis.Core;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.VisualScripting;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services.Interfaces;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.ProfileEditor.Commands;
using Avalonia.Controls.Mixins;
@@ -16,40 +18,77 @@ namespace Artemis.UI.Screens.ProfileEditor.DisplayCondition;
public class DisplayConditionScriptViewModel : ActivatableViewModelBase
{
private readonly IProfileEditorService _profileEditorService;
private readonly ObservableAsPropertyHelper<bool> _showEventOptions;
private readonly ObservableAsPropertyHelper<bool> _showStaticOptions;
private readonly IWindowService _windowService;
private ObservableAsPropertyHelper<NodeScriptViewModel?>? _nodeScriptViewModel;
private RenderProfileElement? _profileElement;
private ObservableAsPropertyHelper<ConditionTypeViewModel?>? _selectedConditionTypeViewModel;
public DisplayConditionScriptViewModel(IProfileEditorService profileEditorService, INodeVmFactory nodeVmFactory, IWindowService windowService)
{
_profileEditorService = profileEditorService;
_windowService = windowService;
ConditionTypeViewModels = new ObservableCollection<ConditionTypeViewModel>
{
new("None", "The element is always active.", null),
new("Regular", "The element is activated when the provided visual script ends in true.", typeof(StaticCondition)),
new("Event", "The element is activated when the selected event fires.\r\n" +
"Events that contain data can conditionally trigger the layer using a visual script.", typeof(EventCondition))
};
this.WhenActivated(d =>
{
profileEditorService.ProfileElement.Subscribe(p => _profileElement = p).DisposeWith(d);
_nodeScriptViewModel = profileEditorService.ProfileElement
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
.Switch()
.Select(c => c is StaticCondition staticCondition ? nodeVmFactory.NodeScriptViewModel(staticCondition.Script, true) : null)
.Select(c => c is INodeScriptCondition {NodeScript: NodeScript nodeScript} ? nodeVmFactory.NodeScriptViewModel(nodeScript, true) : null)
.ToProperty(this, vm => vm.NodeScriptViewModel)
.DisposeWith(d);
_selectedConditionTypeViewModel = profileEditorService.ProfileElement
.Select(p => p?.WhenAnyValue(element => element.DisplayCondition) ?? Observable.Never<ICondition?>())
.Switch()
.Select(c => c != null ? ConditionTypeViewModels.FirstOrDefault(vm => vm.ConditionType == c.GetType()) : null)
.ToProperty(this, vm => vm.SelectedConditionTypeViewModel)
.DisposeWith(d);
});
_showStaticOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
.Select(c => c != null && c.ConditionType == typeof(StaticCondition))
.ToProperty(this, vm => vm.ShowStaticOptions);
_showEventOptions = this.WhenAnyValue(vm => vm.SelectedConditionTypeViewModel)
.Select(c => c != null && c.ConditionType == typeof(EventCondition))
.ToProperty(this, vm => vm.ShowEventOptions);
}
public NodeScriptViewModel? NodeScriptViewModel => _nodeScriptViewModel?.Value;
public ObservableCollection<ConditionTypeViewModel> ConditionTypeViewModels { get; }
public async Task EnableConditions()
public ConditionTypeViewModel? SelectedConditionTypeViewModel
{
bool confirmed = await _windowService.ShowConfirmContentDialog(
"Display conditions",
"Do you want to enable display conditions for this element? \r\n" +
"Using display conditions you can dynamically hide or show layers and folders depending on certain parameters."
);
get => _selectedConditionTypeViewModel?.Value;
set
{
if (_profileElement == null)
return;
if (confirmed && _profileElement != null)
_profileEditorService.ExecuteCommand(new ChangeElementDisplayCondition(_profileElement, new StaticCondition(_profileElement)));
ICondition? condition = null;
if (value?.ConditionType == typeof(StaticCondition))
condition = new StaticCondition(_profileElement);
else if (value?.ConditionType == typeof(EventCondition))
condition = new EventCondition(_profileElement);
_profileEditorService.ExecuteCommand(new ChangeConditionType(_profileElement, condition));
}
}
public bool ShowStaticOptions => _showStaticOptions.Value;
public bool ShowEventOptions => _showEventOptions.Value;
public async Task OpenEditor()
{
if (_profileElement?.DisplayCondition is StaticCondition staticCondition)