Skip to content

Commit 519fcb5

Browse files
committed
[Editor] Settings window
1 parent e04b49a commit 519fcb5

File tree

10 files changed

+262
-1
lines changed

10 files changed

+262
-1
lines changed

sources/editor/Stride.Core.Assets.Editor.Avalonia/Converters/NodePathToObject.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public sealed class NodePathToObject : OneWayValueConverter<NodePathToObject>
5555
nameof(NodeViewModel.IsVisible) => node.IsVisible,
5656
nameof(NodeViewModel.Name) => node.Name,
5757
nameof(NodeViewModel.NodeValue) => node.NodeValue,
58+
nameof(NodeViewModel.Parent) => node.Parent,
5859
nameof(NodeViewModel.Root) => node.Root,
5960
nameof(NodeViewModel.VisibleChildrenCount) => node.VisibleChildrenCount,
6061
_ => throw new ArgumentException($"Unsupported {name} property.")

sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/DefaultPropertyTemplateProviders.axaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1493,7 +1493,7 @@
14931493
</DataTemplate>
14941494
</caev:ContentReferenceTemplateProvider>
14951495

1496-
<!-- Provider for nulable struct -->
1496+
<!-- Provider for nullable struct -->
14971497
<caev:NullableTemplateProvider x:Key="NullableStructPropertyTemplateProvider" caev:PropertyViewHelper.TemplateCategory="PropertyEditor">
14981498
<DataTemplate DataType="cpqvm:NodeViewModel">
14991499
<DockPanel>
@@ -1523,6 +1523,16 @@
15231523
</DataTemplate>
15241524
</caev:NullableTemplateProvider>
15251525

1526+
<caev:SettingsStringFromAcceptableValuesTemplateProvider x:Key="StringFromAcceptableValuesEditorTemplate" OverrideRule="All" caev:PropertyViewHelper.TemplateCategory="PropertyEditor">
1527+
<DataTemplate DataType="cpqvm:NodeViewModel">
1528+
<Border>
1529+
<ComboBox Margin="2"
1530+
ItemsSource="{Binding ConverterParameter=Parent.[AcceptableValues], Converter={caec:NodePathToObject}}"
1531+
SelectedItem="{Binding NodeValue}"/>
1532+
</Border>
1533+
</DataTemplate>
1534+
</caev:SettingsStringFromAcceptableValuesTemplateProvider>
1535+
15261536
<!-- Provider for unloadable object -->
15271537
<caev:UnloadableObjectTemplateProvider x:Key="YamlProxyTemplateProvider" OverrideRule="All" caev:PropertyViewHelper.TemplateCategory="PropertyHeader">
15281538
<DataTemplate DataType="cpqvm:NodeViewModel">
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using Stride.Core.Assets.Editor.Quantum.NodePresenters.Keys;
5+
using Stride.Core.Presentation.Quantum.ViewModels;
6+
7+
namespace Stride.Core.Assets.Editor.Avalonia.Views;
8+
9+
public class SettingsStringFromAcceptableValuesTemplateProvider : NodeViewModelTemplateProvider
10+
{
11+
public override string Name => "StringFromAcceptableValues";
12+
13+
public override bool MatchNode(NodeViewModel node)
14+
{
15+
return node.Parent != null && (node.Parent.AssociatedData.TryGetValue(SettingsData.HasAcceptableValues, out var hasAcceptableValues) && (bool)hasAcceptableValues);
16+
}
17+
}

sources/editor/Stride.GameStudio.Avalonia/Services/EditorDialogService.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Avalonia.Controls;
55
using Stride.Core.Assets.Editor.Services;
6+
using Stride.Core.Assets.Editor.Settings;
67
using Stride.Core.Assets.Editor.ViewModels;
78
using Stride.Core.Presentation.Avalonia.Services;
89
using Stride.Core.Presentation.Services;
@@ -89,4 +90,17 @@ public void ShowProgressWindow(WorkProgressViewModel workProgress)
8990
workProgress.NotifyWindowClosed();
9091
}
9192
}
93+
94+
public async Task ShowSettingsWindowAsync()
95+
{
96+
if (MainWindow is null) return;
97+
98+
await Dispatcher.InvokeTask(async () =>
99+
{
100+
await new SettingsWindow
101+
{
102+
DataContext = new SettingsViewModel(serviceProvider, EditorSettings.SettingsContainer.CurrentProfile)
103+
}.ShowDialog(MainWindow);
104+
});
105+
}
92106
}

sources/editor/Stride.GameStudio.Avalonia/ViewModels/MainViewModel.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public MainViewModel(IViewModelServiceProvider serviceProvider)
4343
ExitCommand = new AnonymousCommand(serviceProvider, OnExit, () => DialogService.HasMainWindow);
4444
OpenCommand = new AnonymousTaskCommand<UFile?>(serviceProvider, OnOpen);
4545
OpenDebugWindowCommand = new AnonymousTaskCommand(serviceProvider, OnOpenDebugWindow, () => DialogService.HasMainWindow);
46+
OpenSettingsWindowCommand = new AnonymousTaskCommand(serviceProvider, OnOpenSettingsWindow, () => DialogService.HasMainWindow);
4647
OpenWebPageCommand = new AnonymousTaskCommand<string>(serviceProvider, OnOpenWebPage);
4748

4849
Status = new StatusViewModel(ServiceProvider);
@@ -75,6 +76,8 @@ public string Title
7576

7677
public ICommandBase OpenDebugWindowCommand { get; }
7778

79+
public ICommandBase OpenSettingsWindowCommand { get; }
80+
7881
public ICommandBase OpenWebPageCommand { get; }
7982

8083
private EditorDialogService DialogService => ServiceProvider.Get<EditorDialogService>();
@@ -148,6 +151,11 @@ private async Task OnOpenDebugWindow()
148151
await DialogService.ShowDebugWindowAsync();
149152
}
150153

154+
private async Task OnOpenSettingsWindow()
155+
{
156+
await DialogService.ShowSettingsWindowAsync();
157+
}
158+
151159
private async Task OnOpenWebPage(string url)
152160
{
153161
try

sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
<MenuItem Header="{sd:LocalizeString Redo, Context=Menu}"
5353
Command="{Binding Session?.ActionHistory?.RedoCommand, TargetNullValue={x:Static cpc:DisabledCommand.Instance}}"
5454
HotKey="{x:Static gsvw:MainView.RedoGesture}" InputGesture="{Binding $self.HotKey, Mode=OneTime}"/>
55+
<Separator/>
56+
<MenuItem Header="{sd:LocalizeString _Settings..., Context=Menu}"
57+
Command="{Binding OpenSettingsWindowCommand}"
58+
IsEnabled="{Binding Session, Mode=OneWay, Converter={sd:ObjectToBool}}"/>
5559
</MenuItem>
5660
<!-- Help menu -->
5761
<MenuItem Header="{sd:LocalizeString _Help, Context=Menu}">
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<Window xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:sd="http://schemas.stride3d.net/xaml/presentation"
6+
xmlns:caeb="using:Stride.Core.Assets.Editor.Avalonia.Behaviors"
7+
xmlns:caevm="using:Stride.Core.Assets.Editor.ViewModels"
8+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
9+
x:Class="Stride.GameStudio.Avalonia.Views.SettingsWindow"
10+
x:DataType="caevm:SettingsViewModel"
11+
Title="SettingsWindow"
12+
Width="800" Height="600" WindowStartupLocation="CenterOwner">
13+
<Window.Resources>
14+
<ResourceDictionary>
15+
<ResourceDictionary.MergedDictionaries>
16+
<ResourceInclude Source="avares://Stride.Core.Assets.Editor.Avalonia/Themes/PropertyViewTheme.axaml"/>
17+
</ResourceDictionary.MergedDictionaries>
18+
</ResourceDictionary>
19+
</Window.Resources>
20+
<DockPanel>
21+
<UniformGrid DockPanel.Dock="Bottom"
22+
Rows="1"
23+
HorizontalAlignment="Right"
24+
Margin="16">
25+
<Button Content="{sd:LocalizeString Save and close, Context=Button}"
26+
Margin="8,0,0,0" Padding="16,4">
27+
<Interaction.Behaviors>
28+
<sd:ButtonCloseWindowBehavior Command="{Binding ValidateChangesCommand}"/>
29+
</Interaction.Behaviors>
30+
</Button>
31+
<Button Content="{sd:LocalizeString Cancel, Context=Button}"
32+
Margin="8,0,0,0" Padding="16,4"
33+
IsCancel="True">
34+
<Interaction.Behaviors>
35+
<sd:ButtonCloseWindowBehavior Command="{Binding DiscardChangesCommand}"/>
36+
</Interaction.Behaviors>
37+
</Button>
38+
</UniformGrid>
39+
40+
<Grid ColumnDefinitions="*,Auto,2*">
41+
<TreeView Grid.Column="0"
42+
ItemsSource="{Binding Categories}"
43+
SelectedItem="{Binding SelectedCategory}">
44+
<Control.DataTemplates>
45+
<TreeDataTemplate DataType="{x:Type caevm:SettingsCategoryViewModel}"
46+
ItemsSource="{Binding SubCategories}">
47+
<StackPanel Orientation="Horizontal">
48+
<TextBlock Text="{Binding Name}" />
49+
</StackPanel>
50+
</TreeDataTemplate>
51+
</Control.DataTemplates>
52+
</TreeView>
53+
54+
<GridSplitter Grid.Column="1" ResizeBehavior="PreviousAndNext" VerticalAlignment="Stretch" />
55+
56+
<sd:PropertyView Grid.Column="2"
57+
ItemsSource="{Binding ViewModel?.RootNode.Children}">
58+
<Interaction.Behaviors>
59+
<caeb:PropertyViewAutoExpandNodesBehavior ViewModel="{Binding ViewModel, Mode=OneWay}" />
60+
</Interaction.Behaviors>
61+
</sd:PropertyView>
62+
</Grid>
63+
</DockPanel>
64+
</Window>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using Avalonia.Controls;
5+
6+
namespace Stride.GameStudio.Avalonia.Views;
7+
8+
public partial class SettingsWindow : Window
9+
{
10+
public SettingsWindow()
11+
{
12+
InitializeComponent();
13+
}
14+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using Avalonia.Controls;
5+
using Avalonia.Interactivity;
6+
7+
namespace Stride.Core.Presentation.Avalonia.Behaviors;
8+
9+
public sealed class ButtonCloseWindowBehavior : CloseWindowBehavior<Button>
10+
{
11+
/// <inheritdoc/>
12+
protected override void OnAttached()
13+
{
14+
base.OnAttached();
15+
AssociatedObject!.Click += ButtonClicked;
16+
}
17+
18+
/// <inheritdoc/>
19+
protected override void OnDetaching()
20+
{
21+
AssociatedObject!.Click -= ButtonClicked;
22+
base.OnDetaching();
23+
}
24+
25+
/// <summary>
26+
/// Raised when the associated button is clicked. Close the containing window
27+
/// </summary>
28+
private void ButtonClicked(object? sender, RoutedEventArgs e)
29+
{
30+
CloseWindow();
31+
}
32+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
2+
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
3+
4+
using System.Diagnostics;
5+
using System.Windows.Input;
6+
using Avalonia;
7+
using Avalonia.Controls;
8+
using Avalonia.Input;
9+
using Avalonia.Xaml.Interactivity;
10+
11+
namespace Stride.Core.Presentation.Avalonia.Behaviors;
12+
13+
public abstract class CloseWindowBehavior<T> : StyledElementBehavior<T>
14+
where T : Visual
15+
{
16+
public static readonly StyledProperty<ICommand?> CommandProperty =
17+
AvaloniaProperty.Register<CloseWindowBehavior<T>, ICommand?>(nameof(Command));
18+
19+
public static readonly StyledProperty<object?> CommandParameterProperty =
20+
AvaloniaProperty.Register<CloseWindowBehavior<T>, object?>(nameof(CommandParameter));
21+
22+
static CloseWindowBehavior()
23+
{
24+
CommandProperty.Changed.AddClassHandler<CloseWindowBehavior<T>, ICommand?>(OnCommandChanged);
25+
CommandParameterProperty.Changed.AddClassHandler<CloseWindowBehavior<T>, object?>(OnCommandParameterChanged);
26+
}
27+
28+
public ICommand? Command
29+
{
30+
get => GetValue(CommandProperty);
31+
set => SetValue(CommandProperty, value);
32+
}
33+
34+
public object? CommandParameter
35+
{
36+
get => GetValue(CommandParameterProperty);
37+
set => SetValue(CommandParameterProperty, value);
38+
}
39+
40+
/// <inheritdoc/>
41+
protected override void OnAttached()
42+
{
43+
base.OnAttached();
44+
if (Command is not null)
45+
{
46+
UpdateIsEnabled();
47+
}
48+
}
49+
50+
protected void CloseWindow()
51+
{
52+
if (Command is not null && Command.CanExecute(CommandParameter))
53+
{
54+
Command.Execute(CommandParameter);
55+
}
56+
57+
if (TopLevel.GetTopLevel(AssociatedObject) is not Window window)
58+
{
59+
throw new InvalidOperationException("The button attached to this behavior is not in a window");
60+
}
61+
62+
window.Close();
63+
}
64+
65+
private static void OnCommandChanged(CloseWindowBehavior<T> sender, AvaloniaPropertyChangedEventArgs<ICommand?> e)
66+
{
67+
if (e.OldValue.Value is { } oldCommand)
68+
{
69+
oldCommand.CanExecuteChanged -= CommandCanExecuteChanged;
70+
}
71+
if (e.NewValue.Value is { } newCommand)
72+
{
73+
newCommand.CanExecuteChanged += CommandCanExecuteChanged;
74+
}
75+
return;
76+
77+
void CommandCanExecuteChanged(object? _, EventArgs __)
78+
{
79+
sender.UpdateIsEnabled();
80+
}
81+
}
82+
83+
private static void OnCommandParameterChanged(CloseWindowBehavior<T> sender, AvaloniaPropertyChangedEventArgs<object?> e)
84+
{
85+
if (sender.Command is not null)
86+
{
87+
sender.UpdateIsEnabled();
88+
}
89+
}
90+
91+
private void UpdateIsEnabled()
92+
{
93+
Debug.Assert(Command is not null);
94+
95+
AssociatedObject?.SetCurrentValue(InputElement.IsEnabledProperty, Command.CanExecute(CommandParameter));
96+
}
97+
}

0 commit comments

Comments
 (0)