Skip to content

Commit 26ca287

Browse files
Merge pull request #14 from StansAssets/feat/display-settings-in-preferences-v2
feat: display settings in Preferences or Project Settings
2 parents 022dcd6 + 1bce0dc commit 26ca287

11 files changed

+356
-26
lines changed

Editor/UIToolkit/Controls.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#if UNITY_2019_4_OR_NEWER
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using StansAssets.Foundation.UIElements;
7+
using UnityEngine.Assertions;
8+
using UnityEngine.UIElements;
9+
10+
namespace StansAssets.Plugins.Editor
11+
{
12+
/// <summary>
13+
/// Tab controller based on <see cref="ButtonStrip"/> to switch between tabs
14+
/// and <see cref="ScrollView"/> to display their contents
15+
/// </summary>
16+
public class TabController
17+
{
18+
readonly Dictionary<string, VisualElement> m_Tabs = new Dictionary<string, VisualElement>();
19+
20+
readonly ButtonStrip m_TabsButtons;
21+
readonly ScrollView m_TabsContainer;
22+
23+
/// <summary>
24+
/// Available tabs' labels.
25+
/// </summary>
26+
public IEnumerable<string> Tabs => m_Tabs.Keys;
27+
28+
/// <summary>
29+
/// Active tab label from <see cref="Tabs"/>.
30+
/// </summary>
31+
public string ActiveTab => m_TabsButtons.Value;
32+
33+
/// <summary>
34+
/// This constructor will looking for already existing elements:
35+
/// <see cref="ButtonStrip"/> (without name) and <see cref="ScrollView"/> named "tabs-container"
36+
/// The purpose of this is to support <see cref="PackageSettingsWindow{TWindow}"/>.
37+
/// </summary>
38+
/// <param name="root">Element that contains <see cref="ButtonStrip"/> and <see cref="ScrollView"/> named tabs-container</param>
39+
public TabController(VisualElement root)
40+
{
41+
m_TabsButtons = root.Q<ButtonStrip>();
42+
m_TabsContainer = root.Q<ScrollView>("tabs-container");
43+
44+
Init();
45+
}
46+
47+
/// <summary>
48+
/// Add tab to the window top bar.
49+
/// </summary>
50+
/// <param name="label">Tab label.</param>
51+
/// <param name="content">Tab content.</param>
52+
/// <exception cref="ArgumentException">Will throw tab with the same label was already added.</exception>
53+
public void AddTab(string label, VisualElement content)
54+
{
55+
if (!m_Tabs.ContainsKey(label))
56+
{
57+
m_TabsButtons.AddChoice(label, label);
58+
m_Tabs.Add(label, content);
59+
content.viewDataKey = label;
60+
}
61+
else
62+
{
63+
throw new ArgumentException($"Tab '{label}' already added", nameof(label));
64+
}
65+
}
66+
67+
/// <summary>
68+
/// Activate tab by label
69+
/// </summary>
70+
/// <param name="label">Early specified tab label</param>
71+
public void ActivateTab(string label)
72+
{
73+
if (!m_Tabs.ContainsKey(label))
74+
{
75+
return;
76+
}
77+
78+
m_TabsButtons.SetValue(label);
79+
}
80+
81+
/// <summary>
82+
/// Set the flexible growth property of tabs content container
83+
/// </summary>
84+
/// <param name="styleFloat"></param>
85+
public void ContentContainerFlexGrow(StyleFloat styleFloat)
86+
{
87+
m_TabsContainer.contentContainer.style.flexGrow = styleFloat;
88+
}
89+
90+
/// <summary>
91+
/// Refresh current tab
92+
/// </summary>
93+
public void RefreshActiveTab()
94+
{
95+
if (string.IsNullOrEmpty(ActiveTab))
96+
{
97+
return;
98+
}
99+
100+
foreach (var tab in m_Tabs)
101+
{
102+
tab.Value.RemoveFromHierarchy();
103+
}
104+
105+
var element = m_Tabs.First(i => i.Key.Equals(m_TabsButtons.Value)).Value;
106+
m_TabsContainer.Add(element);
107+
}
108+
109+
void Init()
110+
{
111+
Assert.IsNotNull(m_TabsButtons);
112+
Assert.IsNotNull(m_TabsContainer);
113+
114+
m_TabsButtons.CleanUp();
115+
m_TabsButtons.OnButtonClick += RefreshActiveTab;
116+
117+
RefreshActiveTab();
118+
}
119+
}
120+
}
121+
122+
#endif

Editor/UIToolkit/Controls/TabController.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/UIToolkit/Extensions.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using UnityEngine.UIElements;
2+
3+
namespace StansAssets.Plugins.Editor
4+
{
5+
public static class ListViewExtensions
6+
{
7+
/// <summary>
8+
/// Rebuild/refresh ListView in compatible mode with Unity 2019/2021 editor
9+
/// </summary>
10+
/// <param name="listView"></param>
11+
public static void RebuildInCompatibleMode(this ListView listView)
12+
{
13+
#if UNITY_2019_4_40
14+
listView.Refresh();
15+
#else
16+
listView.Rebuild();
17+
#endif
18+
}
19+
}
20+
}

Editor/UIToolkit/Extensions/ListViewExtentions.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#if UNITY_2019_4_OR_NEWER
2+
3+
using JetBrains.Annotations;
4+
using StansAssets.Foundation.Editor;
5+
using UnityEditor;
6+
using UnityEngine.UIElements;
7+
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
8+
9+
namespace StansAssets.Plugins.Editor
10+
{
11+
[UsedImplicitly]
12+
sealed class AboutPreferencesWindow : PackagePreferencesWindow
13+
{
14+
protected override PackageInfo GetPackageInfo()
15+
=> PackageManagerUtility.GetPackageInfo(PluginsDevKitPackage.Name);
16+
17+
protected override string SettingsPath => $"{PluginsDevKitPackage.RootMenu}/{GetPackageInfo().displayName}";
18+
protected override SettingsScope Scope => SettingsScope.User;
19+
20+
protected override void OnActivate(string searchContext, VisualElement rootElement)
21+
{
22+
ContentContainerFlexGrow(1);
23+
AddTab("About", new AboutTab());
24+
}
25+
26+
protected override void OnDeactivate() { }
27+
}
28+
}
29+
30+
#endif

Editor/UIToolkit/PreferencesWindow/AboutPreferencesWindow.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#if UNITY_2019_4_OR_NEWER
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using StansAssets.Foundation.Editor;
7+
using UnityEditor;
8+
using UnityEditor.UIElements;
9+
using UnityEngine.UIElements;
10+
11+
namespace StansAssets.Plugins.Editor
12+
{
13+
/// <summary>
14+
/// Base class for Plugin Preferences Window
15+
/// </summary>
16+
public abstract class PackagePreferencesWindow
17+
{
18+
TabController m_TabController;
19+
20+
/// <summary>
21+
/// Structure describing a Unity Package.
22+
/// </summary>
23+
protected abstract UnityEditor.PackageManager.PackageInfo GetPackageInfo();
24+
25+
26+
/// <summary>
27+
/// Gets Path used to place the SettingsProvider in the tree view of the Preferences or Project Settings window.
28+
/// The path should be unique among all other settings paths and should use "/" as its separator.
29+
/// </summary>
30+
protected abstract string SettingsPath { get; }
31+
32+
/// <summary>
33+
/// Gets the Scope of the SettingsProvider. The Scope determines whether the SettingsProvider appears
34+
/// in the Preferences window (SettingsScope.User) or the Settings window (SettingsScope.Project).
35+
/// </summary>
36+
protected abstract SettingsScope Scope { get; }
37+
38+
/// <summary>
39+
/// Add tab to the window top bar.
40+
/// </summary>
41+
/// <param name="label">Tab label.</param>
42+
/// <param name="content">Tab content.</param>
43+
/// <exception cref="ArgumentException">Will throw tab with the same label was already added.</exception>
44+
protected void AddTab(string label, VisualElement content)
45+
{
46+
m_TabController.AddTab(label, content);
47+
}
48+
49+
/// <summary>
50+
/// Activate tab by name
51+
/// </summary>
52+
/// <param name="name">Early specified tab name</param>
53+
protected void ActivateTab(string name)
54+
{
55+
m_TabController.ActivateTab(name);
56+
}
57+
58+
/// <summary>
59+
/// Set the flexible growth property of tabs content container
60+
/// </summary>
61+
/// <param name="styleFloat"></param>
62+
protected void ContentContainerFlexGrow(StyleFloat styleFloat)
63+
{
64+
m_TabController.ContentContainerFlexGrow(styleFloat);
65+
}
66+
67+
/// <summary>
68+
/// Overrides SettingsProvider.OnActivate.
69+
/// </summary>
70+
protected abstract void OnActivate(string searchContext, VisualElement rootElement);
71+
72+
/// <summary>
73+
/// Overrides SettingsProvider.OnDeactivate.
74+
/// </summary>
75+
protected abstract void OnDeactivate();
76+
77+
void OnActivateWindow(string searchContext, VisualElement rootElement)
78+
{
79+
UIToolkitEditorUtility.CloneTreeAndApplyStyle(rootElement,
80+
$"{PluginsDevKitPackage.UIToolkitPath}/SettingsWindow/PackageSettingsWindow");
81+
82+
// Hide search bar from PackageSettingsWindow. In preferences we already have search bar
83+
// and it's value in "searchContext" parameter
84+
var searchBar = rootElement.Q<ToolbarSearchField>();
85+
if (searchBar != null)
86+
{
87+
searchBar.style.visibility = Visibility.Hidden;
88+
}
89+
90+
var packageInfo = GetPackageInfo();
91+
rootElement.Q<Label>("display-name").text = packageInfo.displayName;
92+
rootElement.Q<Label>("description").text = packageInfo.description;
93+
rootElement.Q<Label>("version").text = $"Version: {packageInfo.version}";
94+
95+
m_TabController = new TabController(rootElement);
96+
}
97+
98+
void OnActivateHandler(string searchContext, VisualElement rootElement)
99+
{
100+
OnActivate(searchContext, rootElement);
101+
102+
EditorApplication.delayCall += () =>
103+
{
104+
m_TabController.RefreshActiveTab();
105+
};
106+
}
107+
108+
SettingsProvider ConstructSettingsProvider()
109+
{
110+
var packageInfo = GetPackageInfo();
111+
var settingsProvider = new SettingsProvider(SettingsPath, Scope, packageInfo.keywords)
112+
{
113+
label = packageInfo.displayName,
114+
};
115+
116+
settingsProvider.activateHandler += OnActivateWindow;
117+
settingsProvider.activateHandler += OnActivateHandler;
118+
settingsProvider.deactivateHandler += OnDeactivate;
119+
120+
return settingsProvider;
121+
}
122+
123+
[SettingsProviderGroup]
124+
static SettingsProvider[] RegisterSettingsProviderGroup()
125+
{
126+
var baseType = typeof(PackagePreferencesWindow);
127+
var group = new List<SettingsProvider>();
128+
129+
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
130+
{
131+
var derivedTypes = assembly.GetTypes()
132+
.Where(t => baseType.IsAssignableFrom(t) && t != baseType)
133+
.ToArray();
134+
135+
foreach (var derivedType in derivedTypes)
136+
{
137+
var instance = (PackagePreferencesWindow)Activator.CreateInstance(derivedType);
138+
var settingsProvider = instance.ConstructSettingsProvider();
139+
group.Add(settingsProvider);
140+
}
141+
}
142+
143+
return group.ToArray();
144+
}
145+
}
146+
}
147+
148+
#endif

Editor/UIToolkit/PreferencesWindow/PackagePreferencesWindow.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)