Skip to content

Commit 502874c

Browse files
authored
feat: Settings Window (#2816)
1 parent 472e2f5 commit 502874c

File tree

23 files changed

+794
-118
lines changed

23 files changed

+794
-118
lines changed

sources/core/Stride.Core.Design/Settings/SettingsKey.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
22
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
33

4-
using Stride.Core.Annotations;
54
using Stride.Core.Extensions;
65
using Stride.Core.IO;
76
using Stride.Core.Yaml;
@@ -78,7 +77,7 @@ protected SettingsKey(UFile name, SettingsContainer container, Func<object> defa
7877
/// <summary>
7978
/// Gets or sets the description of this <see cref="SettingsKey"/>.
8079
/// </summary>
81-
public string Description { get; set; }
80+
public string? Description { get; set; }
8281

8382
/// <summary>
8483
/// Gets an enumeration of acceptable values for this <see cref="SettingsKey"/>.
@@ -94,7 +93,7 @@ protected SettingsKey(UFile name, SettingsContainer container, Func<object> defa
9493
/// <summary>
9594
/// Raised when the value of the settings key has been modified and the method <see cref="SettingsProfile.ValidateSettingsChanges"/> has been invoked.
9695
/// </summary>
97-
public event EventHandler<ChangesValidatedEventArgs> ChangesValidated;
96+
public event EventHandler<ChangesValidatedEventArgs>? ChangesValidated;
9897

9998
/// <summary>
10099
/// Converts a value of a different type to the type associated with this <see cref="SettingsKey"/>. If the conversion is not possible,
@@ -166,21 +165,20 @@ public SettingsKey(UFile name, SettingsContainer container, Func<object> default
166165
}
167166

168167
/// <inheritdoc/>
169-
[NotNull]
170168
public override Type Type { get { return typeof(T); } }
171169

172170
/// <summary>
173171
/// Gets the default value of this settings key.
174172
/// </summary>
175-
public T DefaultValue { get { return DefaultObjectValueCallback != null ? (T)DefaultObjectValueCallback() : (T)DefaultObjectValue; } }
173+
public T DefaultValue { get { return DefaultObjectValueCallback?.Invoke() is T value ? value : (T)DefaultObjectValue; } }
176174

177175
/// <summary>
178176
/// Gets or sets a function that returns an enumation of acceptable values for this <see cref="SettingsKey{T}"/>.
179177
/// </summary>
180-
public Func<IEnumerable<T>> GetAcceptableValues { get; set; }
178+
public Func<IEnumerable<T>>? GetAcceptableValues { get; set; }
181179

182180
/// <inheritdoc/>
183-
public override IEnumerable<object> AcceptableValues { get { return GetAcceptableValues != null ? (IEnumerable<object>)GetAcceptableValues() : Enumerable.Empty<object>(); } }
181+
public override IEnumerable<object> AcceptableValues { get { return (IEnumerable<object>?)GetAcceptableValues?.Invoke() ?? []; } }
184182

185183
/// <summary>
186184
/// Gets the value of this settings key in the given profile.
@@ -191,9 +189,8 @@ public SettingsKey(UFile name, SettingsContainer container, Func<object> default
191189
/// <exception cref="KeyNotFoundException">No value can be found in the given profile matching this settings key.</exception>
192190
public T GetValue(SettingsProfile profile, bool searchInParentProfile)
193191
{
194-
object value;
195192
profile = ResolveProfile(profile);
196-
if (profile.GetValue(Name, out value, searchInParentProfile, false))
193+
if (profile.GetValue(Name, out var value, searchInParentProfile, false))
197194
{
198195
try
199196
{

sources/core/Stride.Core.Design/Settings/SettingsProfile.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
22
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
33

4-
using Stride.Core.Annotations;
4+
using System.Diagnostics.CodeAnalysis;
55
using Stride.Core.IO;
66
using Stride.Core.Serialization;
77
using Stride.Core.Transactions;
@@ -20,7 +20,7 @@ public class SettingsProfile : IDisposable
2020
private readonly SortedList<UFile, SettingsEntry> settings = [];
2121
private readonly HashSet<UFile> modifiedSettings = [];
2222
private readonly SettingsProfile? parentProfile;
23-
private FileSystemWatcher fileWatcher;
23+
private FileSystemWatcher? fileWatcher;
2424
private UFile filePath;
2525
private bool monitorFileModification;
2626

@@ -53,7 +53,7 @@ internal SettingsProfile(SettingsContainer container, SettingsProfile? parentPro
5353
/// <summary>
5454
/// Raised when the file corresponding to this profile is modified on the disk, and <see cref="MonitorFileModification"/> is <c>true</c>.
5555
/// </summary>
56-
public event EventHandler<FileModifiedEventArgs> FileModified;
56+
public event EventHandler<FileModifiedEventArgs>? FileModified;
5757

5858
/// <summary>
5959
/// Gets the collection of <see cref="SettingsEntry"/> currently existing in this <see cref="SettingsProfile"/>.
@@ -208,7 +208,7 @@ internal void RegisterEntry(SettingsEntry entry)
208208
/// <param name="searchInParent">Indicates whether to search in the parent profile, if the name is not found in this profile.</param>
209209
/// <param name="createInCurrentProfile">If true, the list will be created in the current profile, from the value of its parent profile.</param>
210210
/// <returns><c>true</c> if an entry matching the name is found, <c>false</c> otherwise.</returns>
211-
internal bool GetValue(UFile name, out object? value, bool searchInParent, bool createInCurrentProfile)
211+
internal bool GetValue(UFile name, [MaybeNullWhen(false)] out object value, bool searchInParent, bool createInCurrentProfile)
212212
{
213213
#if NET6_0_OR_GREATER
214214
ArgumentNullException.ThrowIfNull(name);

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.")
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<ResourceDictionary xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:sd="http://schemas.stride3d.net/xaml/presentation"
4+
xmlns:caecp="using:Stride.Core.Assets.Editor.Components.Properties"
5+
xmlns:caev="using:Stride.Core.Assets.Editor.Avalonia.Views"
6+
xmlns:cpqvm="using:Stride.Core.Presentation.Quantum.ViewModels">
7+
<ControlTheme x:Key="PropertyExpanderTheme" TargetType="Expander">
8+
<Setter Property="Padding" Value="10,0,0,0"/>
9+
<Setter Property="Template">
10+
<ControlTemplate TargetType="Expander">
11+
<Border BorderThickness="0" Margin="0" Padding="0"
12+
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
13+
<ContentPresenter Content="{TemplateBinding Content}"
14+
ContentTemplate="{TemplateBinding ContentTemplate}"
15+
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
16+
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
17+
IsVisible="{TemplateBinding IsExpanded}"
18+
Focusable="False"/>
19+
</Border>
20+
</ControlTemplate>
21+
</Setter>
22+
</ControlTheme>
23+
<ControlTheme x:Key="{x:Type sd:PropertyView}" TargetType="sd:PropertyView" BasedOn="{StaticResource {x:Type ItemsControl}}">
24+
<Setter Property="HorizontalAlignment" Value="Stretch"/>
25+
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
26+
<Setter Property="Template">
27+
<ControlTemplate TargetType="sd:PropertyView">
28+
<Border Padding="{TemplateBinding Padding}"
29+
Background="{TemplateBinding Background}"
30+
BorderBrush="{TemplateBinding BorderBrush}"
31+
BorderThickness="{TemplateBinding BorderThickness}"
32+
CornerRadius="{TemplateBinding CornerRadius}">
33+
<ScrollViewer AllowAutoHide="False"
34+
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
35+
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" >
36+
<ItemsPresenter Name="PART_ItemsPresenter"
37+
ItemsPanel="{TemplateBinding ItemsPanel}" />
38+
</ScrollViewer>
39+
</Border>
40+
</ControlTemplate>
41+
</Setter>
42+
</ControlTheme>
43+
<ControlTheme x:Key="{x:Type sd:PropertyViewItem}" TargetType="sd:PropertyViewItem"
44+
x:DataType="cpqvm:NodeViewModel">
45+
<Setter Property="Background" Value="Transparent"/>
46+
<Setter Property="Increment" Value="12"/>
47+
<Setter Property="HorizontalAlignment" Value="Stretch"/>
48+
<Setter Property="HeaderTemplate">
49+
<TreeDataTemplate ItemsSource="{Binding Children, Mode=OneWay}" DataType="cpqvm:NodeViewModel">
50+
<ContentPresenter Content="{Binding}"
51+
ContentTemplate="{x:Static caev:PropertyViewHelper.HeaderProviders}"/>
52+
</TreeDataTemplate>
53+
</Setter>
54+
<Setter Property="Template">
55+
<ControlTemplate TargetType="sd:PropertyViewItem">
56+
<Border BorderBrush="{TemplateBinding Border.BorderBrush}"
57+
BorderThickness="{TemplateBinding Border.BorderThickness}"
58+
IsVisible="{Binding IsVisible}">
59+
<DockPanel>
60+
<Border DockPanel.Dock="Top" Background="{TemplateBinding Background}">
61+
<ContentPresenter Name="PART_Header"
62+
Content="{TemplateBinding HeaderedContentControl.Header}"
63+
ContentTemplate="{TemplateBinding HeaderTemplate}"
64+
HorizontalAlignment="{TemplateBinding Control.HorizontalAlignment}"
65+
IsVisible="{sd:MultiBinding {Binding !$parent[sd:PropertyView].((caecp:SessionObjectPropertiesViewModel)DataContext).ShowOverridesOnly, FallbackValue={sd:True}},
66+
{Binding [IsOverridden]},
67+
{Binding ![HasBase]},
68+
Converter={sd:OrMulti}}"/>
69+
</Border>
70+
<Expander IsEnabled="True" IsExpanded="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
71+
HorizontalAlignment="Stretch"
72+
Theme="{StaticResource PropertyExpanderTheme}">
73+
<StackPanel>
74+
<ItemsPresenter Name="ItemsHost"/>
75+
<ContentPresenter Name="PART_Footer"
76+
Content="{TemplateBinding HeaderedContentControl.Header}"
77+
ContentTemplate="{x:Static caev:PropertyViewHelper.FooterProviders}"
78+
HorizontalAlignment="{TemplateBinding Control.HorizontalAlignment}"
79+
IsVisible="{sd:MultiBinding {Binding !$parent[sd:PropertyView].((caecp:SessionObjectPropertiesViewModel)DataContext).ShowOverridesOnly, FallbackValue={sd:True}},
80+
{Binding [IsOverridden]},
81+
{Binding ![HasBase]},
82+
Converter={sd:OrMulti}}"/>
83+
</StackPanel>
84+
</Expander>
85+
</DockPanel>
86+
</Border>
87+
</ControlTemplate>
88+
</Setter>
89+
</ControlTheme>
90+
</ResourceDictionary>

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">

sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/PropertyTemplateProviderSelector.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
22
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
33

4-
using System.Linq;
54
using System.Runtime.CompilerServices;
65
using Avalonia.Controls;
76
using Stride.Core.Presentation.Avalonia.Views;
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+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
namespace Stride.Core.Assets.Editor.Quantum.NodePresenters.Keys;
5+
6+
public static class SettingsData
7+
{
8+
public const string HasAcceptableValues = nameof(HasAcceptableValues);
9+
public const string AcceptableValues = nameof(AcceptableValues);
10+
public static readonly PropertyKey<bool> HasAcceptableValuesKey = new(HasAcceptableValues, typeof(SettingsData));
11+
public static readonly PropertyKey<IEnumerable<object>> AcceptableValuesKey = new(AcceptableValues, typeof(SettingsData));
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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.Assets.Editor.Settings;
6+
using Stride.Core.Presentation.Quantum.Presenters;
7+
8+
namespace Stride.Core.Assets.Editor.Quantum.NodePresenters.Updaters;
9+
10+
public sealed class SettingsPropertyNodeUpdater : NodePresenterUpdaterBase
11+
{
12+
public override void UpdateNode(INodePresenter node)
13+
{
14+
if (node.Value is PackageSettingsWrapper.SettingsKeyWrapper settingsKey)
15+
{
16+
var acceptableValues = settingsKey.Key.AcceptableValues.ToList();
17+
node.AttachedProperties.Add(SettingsData.HasAcceptableValuesKey, acceptableValues.Count > 0);
18+
node.AttachedProperties.Add(SettingsData.AcceptableValuesKey, acceptableValues);
19+
}
20+
}
21+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.Settings;
5+
using Stride.Core.Translation;
6+
7+
namespace Stride.Core.Assets.Editor.Settings;
8+
9+
public static class EditorSettings
10+
{
11+
private static SettingsProfile? profile;
12+
public static SettingsContainer SettingsContainer { get; } = new();
13+
14+
// Categories
15+
public static readonly string Interface = Tr._p("Settings", "Interface");
16+
17+
static EditorSettings()
18+
{
19+
Language = new SettingsKey<string>("Interface/Language", SettingsContainer, "MachineDefault")
20+
{
21+
DisplayName = $"{Interface}/{Tr._p("Settings", "Language")}",
22+
};
23+
}
24+
25+
public static SettingsKey<string> Language { get; }
26+
27+
public static bool NeedRestart { get; set; }
28+
29+
public static void Initialize()
30+
{
31+
profile = SettingsContainer.LoadSettingsProfile(EditorPath.EditorConfigPath, true) ?? SettingsContainer.CreateSettingsProfile(true);
32+
33+
// Settings that requires a restart must register here
34+
Language.ChangesValidated += (_, _) => NeedRestart = true;
35+
}
36+
37+
public static void Save()
38+
{
39+
SettingsContainer.SaveSettingsProfile(profile!, EditorPath.EditorConfigPath);
40+
}
41+
}

0 commit comments

Comments
 (0)