Skip to content

Commit

Permalink
Merge pull request #14 from StansAssets/feat/display-settings-in-pref…
Browse files Browse the repository at this point in the history
…erences-v2

feat: display settings in Preferences or Project Settings
  • Loading branch information
pavlo-klymentenko authored Aug 15, 2023
2 parents 022dcd6 + 1bce0dc commit 26ca287
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 26 deletions.
3 changes: 3 additions & 0 deletions Editor/UIToolkit/Controls.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 122 additions & 0 deletions Editor/UIToolkit/Controls/TabController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#if UNITY_2019_4_OR_NEWER

using System;
using System.Collections.Generic;
using System.Linq;
using StansAssets.Foundation.UIElements;
using UnityEngine.Assertions;
using UnityEngine.UIElements;

namespace StansAssets.Plugins.Editor
{
/// <summary>
/// Tab controller based on <see cref="ButtonStrip"/> to switch between tabs
/// and <see cref="ScrollView"/> to display their contents
/// </summary>
public class TabController
{
readonly Dictionary<string, VisualElement> m_Tabs = new Dictionary<string, VisualElement>();

readonly ButtonStrip m_TabsButtons;
readonly ScrollView m_TabsContainer;

/// <summary>
/// Available tabs' labels.
/// </summary>
public IEnumerable<string> Tabs => m_Tabs.Keys;

/// <summary>
/// Active tab label from <see cref="Tabs"/>.
/// </summary>
public string ActiveTab => m_TabsButtons.Value;

/// <summary>
/// This constructor will looking for already existing elements:
/// <see cref="ButtonStrip"/> (without name) and <see cref="ScrollView"/> named "tabs-container"
/// The purpose of this is to support <see cref="PackageSettingsWindow{TWindow}"/>.
/// </summary>
/// <param name="root">Element that contains <see cref="ButtonStrip"/> and <see cref="ScrollView"/> named tabs-container</param>
public TabController(VisualElement root)
{
m_TabsButtons = root.Q<ButtonStrip>();
m_TabsContainer = root.Q<ScrollView>("tabs-container");

Init();
}

/// <summary>
/// Add tab to the window top bar.
/// </summary>
/// <param name="label">Tab label.</param>
/// <param name="content">Tab content.</param>
/// <exception cref="ArgumentException">Will throw tab with the same label was already added.</exception>
public void AddTab(string label, VisualElement content)
{
if (!m_Tabs.ContainsKey(label))
{
m_TabsButtons.AddChoice(label, label);
m_Tabs.Add(label, content);
content.viewDataKey = label;
}
else
{
throw new ArgumentException($"Tab '{label}' already added", nameof(label));
}
}

/// <summary>
/// Activate tab by label
/// </summary>
/// <param name="label">Early specified tab label</param>
public void ActivateTab(string label)
{
if (!m_Tabs.ContainsKey(label))
{
return;
}

m_TabsButtons.SetValue(label);
}

/// <summary>
/// Set the flexible growth property of tabs content container
/// </summary>
/// <param name="styleFloat"></param>
public void ContentContainerFlexGrow(StyleFloat styleFloat)
{
m_TabsContainer.contentContainer.style.flexGrow = styleFloat;
}

/// <summary>
/// Refresh current tab
/// </summary>
public void RefreshActiveTab()
{
if (string.IsNullOrEmpty(ActiveTab))
{
return;
}

foreach (var tab in m_Tabs)
{
tab.Value.RemoveFromHierarchy();
}

var element = m_Tabs.First(i => i.Key.Equals(m_TabsButtons.Value)).Value;
m_TabsContainer.Add(element);
}

void Init()
{
Assert.IsNotNull(m_TabsButtons);
Assert.IsNotNull(m_TabsContainer);

m_TabsButtons.CleanUp();
m_TabsButtons.OnButtonClick += RefreshActiveTab;

RefreshActiveTab();
}
}
}

#endif
3 changes: 3 additions & 0 deletions Editor/UIToolkit/Controls/TabController.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Editor/UIToolkit/Extensions.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions Editor/UIToolkit/Extensions/ListViewExtentions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using UnityEngine.UIElements;

namespace StansAssets.Plugins.Editor
{
public static class ListViewExtensions
{
/// <summary>
/// Rebuild/refresh ListView in compatible mode with Unity 2019/2021 editor
/// </summary>
/// <param name="listView"></param>
public static void RebuildInCompatibleMode(this ListView listView)
{
#if UNITY_2019_4_40
listView.Refresh();
#else
listView.Rebuild();
#endif
}
}
}
3 changes: 3 additions & 0 deletions Editor/UIToolkit/Extensions/ListViewExtentions.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions Editor/UIToolkit/PreferencesWindow/AboutPreferencesWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#if UNITY_2019_4_OR_NEWER

using JetBrains.Annotations;
using StansAssets.Foundation.Editor;
using UnityEditor;
using UnityEngine.UIElements;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;

namespace StansAssets.Plugins.Editor
{
[UsedImplicitly]
sealed class AboutPreferencesWindow : PackagePreferencesWindow
{
protected override PackageInfo GetPackageInfo()
=> PackageManagerUtility.GetPackageInfo(PluginsDevKitPackage.Name);

protected override string SettingsPath => $"{PluginsDevKitPackage.RootMenu}/{GetPackageInfo().displayName}";
protected override SettingsScope Scope => SettingsScope.User;

protected override void OnActivate(string searchContext, VisualElement rootElement)
{
ContentContainerFlexGrow(1);
AddTab("About", new AboutTab());
}

protected override void OnDeactivate() { }
}
}

#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

148 changes: 148 additions & 0 deletions Editor/UIToolkit/PreferencesWindow/PackagePreferencesWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#if UNITY_2019_4_OR_NEWER

using System;
using System.Collections.Generic;
using System.Linq;
using StansAssets.Foundation.Editor;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace StansAssets.Plugins.Editor
{
/// <summary>
/// Base class for Plugin Preferences Window
/// </summary>
public abstract class PackagePreferencesWindow
{
TabController m_TabController;

/// <summary>
/// Structure describing a Unity Package.
/// </summary>
protected abstract UnityEditor.PackageManager.PackageInfo GetPackageInfo();


/// <summary>
/// Gets Path used to place the SettingsProvider in the tree view of the Preferences or Project Settings window.
/// The path should be unique among all other settings paths and should use "/" as its separator.
/// </summary>
protected abstract string SettingsPath { get; }

/// <summary>
/// Gets the Scope of the SettingsProvider. The Scope determines whether the SettingsProvider appears
/// in the Preferences window (SettingsScope.User) or the Settings window (SettingsScope.Project).
/// </summary>
protected abstract SettingsScope Scope { get; }

/// <summary>
/// Add tab to the window top bar.
/// </summary>
/// <param name="label">Tab label.</param>
/// <param name="content">Tab content.</param>
/// <exception cref="ArgumentException">Will throw tab with the same label was already added.</exception>
protected void AddTab(string label, VisualElement content)
{
m_TabController.AddTab(label, content);
}

/// <summary>
/// Activate tab by name
/// </summary>
/// <param name="name">Early specified tab name</param>
protected void ActivateTab(string name)
{
m_TabController.ActivateTab(name);
}

/// <summary>
/// Set the flexible growth property of tabs content container
/// </summary>
/// <param name="styleFloat"></param>
protected void ContentContainerFlexGrow(StyleFloat styleFloat)
{
m_TabController.ContentContainerFlexGrow(styleFloat);
}

/// <summary>
/// Overrides SettingsProvider.OnActivate.
/// </summary>
protected abstract void OnActivate(string searchContext, VisualElement rootElement);

/// <summary>
/// Overrides SettingsProvider.OnDeactivate.
/// </summary>
protected abstract void OnDeactivate();

void OnActivateWindow(string searchContext, VisualElement rootElement)
{
UIToolkitEditorUtility.CloneTreeAndApplyStyle(rootElement,
$"{PluginsDevKitPackage.UIToolkitPath}/SettingsWindow/PackageSettingsWindow");

// Hide search bar from PackageSettingsWindow. In preferences we already have search bar
// and it's value in "searchContext" parameter
var searchBar = rootElement.Q<ToolbarSearchField>();
if (searchBar != null)
{
searchBar.style.visibility = Visibility.Hidden;
}

var packageInfo = GetPackageInfo();
rootElement.Q<Label>("display-name").text = packageInfo.displayName;
rootElement.Q<Label>("description").text = packageInfo.description;
rootElement.Q<Label>("version").text = $"Version: {packageInfo.version}";

m_TabController = new TabController(rootElement);
}

void OnActivateHandler(string searchContext, VisualElement rootElement)
{
OnActivate(searchContext, rootElement);

EditorApplication.delayCall += () =>
{
m_TabController.RefreshActiveTab();
};
}

SettingsProvider ConstructSettingsProvider()
{
var packageInfo = GetPackageInfo();
var settingsProvider = new SettingsProvider(SettingsPath, Scope, packageInfo.keywords)
{
label = packageInfo.displayName,
};

settingsProvider.activateHandler += OnActivateWindow;
settingsProvider.activateHandler += OnActivateHandler;
settingsProvider.deactivateHandler += OnDeactivate;

return settingsProvider;
}

[SettingsProviderGroup]
static SettingsProvider[] RegisterSettingsProviderGroup()
{
var baseType = typeof(PackagePreferencesWindow);
var group = new List<SettingsProvider>();

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var derivedTypes = assembly.GetTypes()
.Where(t => baseType.IsAssignableFrom(t) && t != baseType)
.ToArray();

foreach (var derivedType in derivedTypes)
{
var instance = (PackagePreferencesWindow)Activator.CreateInstance(derivedType);
var settingsProvider = instance.ConstructSettingsProvider();
group.Add(settingsProvider);
}
}

return group.ToArray();
}
}
}

#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 26ca287

Please sign in to comment.