Skip to content

Commit

Permalink
Merge pull request #707 from uni-bremen-agst/705-mode-selection-space
Browse files Browse the repository at this point in the history
Fix player menu issues
  • Loading branch information
koschke authored Feb 17, 2024
2 parents b61b995 + d0596d8 commit 5546ef6
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 50 deletions.
11 changes: 9 additions & 2 deletions Assets/SEE/GameObjects/Menu/PlayerMenu.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using InControl;
using SEE.Controls;
using SEE.Controls.Actions;
using SEE.Controls.KeyActions;
using SEE.Game;
using SEE.UI.Menu;
using SEE.UI.StateIndicator;
Expand Down Expand Up @@ -135,7 +137,7 @@ bool Visit(AbstractActionStateType child, AbstractActionStateType parent)
}

/// <summary>
/// This creates and returns the <see cref="StateIndicator.ActionStateIndicator"/>, which displays the current mode.
/// This creates and returns the <see cref="ActionStateIndicator"/>, which displays the current mode.
/// The indicator will either be attached to the given GameObject or to a new GameObject if
/// <paramref name="attachTo"/> is null.
/// </summary>
Expand Down Expand Up @@ -180,7 +182,12 @@ private void Update()
}
}

if (SEEInput.ToggleMenu())
// When the menu is shown, we still want the ToggleMenu key to work,
// except if the search field is focussed. Hence, we cannot rely on
// SEEInput here, but need to check the key directly.
bool shouldReactToToggleMenu = (modeMenu.ShowMenu && !modeMenu.IsSearchFocused)
|| (!modeMenu.ShowMenu && SEEInput.KeyboardShortcutsEnabled);
if (shouldReactToToggleMenu && KeyBindings.IsDown(KeyAction.ToggleMenu))
{
modeMenu.ToggleMenu();
}
Expand Down
6 changes: 5 additions & 1 deletion Assets/SEE/UI/Menu/Desktop/SimpleListMenuDesktop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public partial class SimpleListMenu<T> where T : MenuEntry
/// </summary>
/// <param name="entry">The menu entry.</param>
/// <returns>The game object of the entry.</returns>
public GameObject EntryGameObject(T entry) => EntryList.transform.Find(entry.Title).gameObject;
public GameObject EntryGameObject(T entry) => EntryList.transform.Find(entry.Title)?.gameObject;

/// <summary>
/// Initializes the menu.
Expand Down Expand Up @@ -144,6 +144,10 @@ protected virtual void AddButton(T entry)
/// <param name="entry">The menu entry.</param>
protected virtual void DestroyButton(T entry)
{
if (entry == null)
{
return;
}
Destroyer.Destroy(EntryGameObject(entry));
}

Expand Down
124 changes: 88 additions & 36 deletions Assets/SEE/UI/Menu/NestedMenu.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using FuzzySharp;
using SEE.Controls;
using SEE.DataModel.GraphSearch;
Expand Down Expand Up @@ -29,7 +31,7 @@ public class NestedListMenu : NestedListMenu<MenuEntry>
public class NestedListMenu<T> : SimpleListMenu<T> where T : MenuEntry
{
/// <summary>
/// The menu levels we have ascended through.
/// The menu levels we have descended through.
/// </summary>
private readonly Stack<MenuLevel> levels = new();

Expand Down Expand Up @@ -66,12 +68,22 @@ public class NestedListMenu<T> : SimpleListMenu<T> where T : MenuEntry
/// </summary>
private bool searchActive;

/// <summary>
/// A semaphore to prevent multiple searches from happening at the same time.
/// </summary>
private SemaphoreSlim searchSemaphore = new(1, 1);

/// <summary>
/// True, if the search-input is focused, else false.
/// </summary>
public bool IsSearchFocused => searchInput.isFocused;

public override void SelectEntry(T entry)
{
if (entry is NestedMenuEntry<T> nestedEntry)
{
// If this contains another menu level, repopulate list with new level after saving the current one
AscendLevel(nestedEntry);
DescendLevel(nestedEntry);
}
else
{
Expand All @@ -83,7 +95,7 @@ public override void SelectEntry(T entry)
/// <summary>
/// Appends the <see cref="backMenuCommand"/> to the keywords.
/// </summary>
/// <returns></returns>
/// <returns>The titles the menu should listen to</returns>
protected override IEnumerable<string> GetKeywords()
{
return base.GetKeywords().Append(backMenuCommand);
Expand All @@ -93,7 +105,7 @@ protected override void HandleKeyword(PhraseRecognizedEventArgs args)
{
if (args.text == backMenuCommand)
{
DescendLevel();
AscendLevel();
}
else
{
Expand All @@ -102,10 +114,12 @@ protected override void HandleKeyword(PhraseRecognizedEventArgs args)
}

/// <summary>
/// Ascends up in the menu hierarchy by creating a new level from the given <paramref name="nestedEntry"/>.
/// Descends down in the menu hierarchy by creating a new level from the given <paramref name="nestedEntry"/>.
/// </summary>
/// <param name="nestedEntry">The entry from which to construct the new level</param>
private void AscendLevel(NestedMenuEntry<T> nestedEntry)
/// <param name="withBreadcrumb">Whether to include a breadcrumb indicating the hierarchy
/// in the description of the newly descended level</param>
private void DescendLevel(NestedMenuEntry<T> nestedEntry, bool withBreadcrumb = true)
{
levels.Push(new MenuLevel(Title, Description, Icon, Entries));
while (Entries.Count != 0)
Expand All @@ -116,7 +130,7 @@ private void AscendLevel(NestedMenuEntry<T> nestedEntry)
// TODO: Instead of abusing the description for this, use a proper individual text object
// (Maybe displaying it above the title in a different color or something would work,
// as the title is technically the last element in the breadcrumb)
string breadcrumb = GetBreadcrumb();
string breadcrumb = withBreadcrumb ? GetBreadcrumb() : string.Empty;
Description = nestedEntry.Description + (breadcrumb.Length > 0 ? $"\n{GetBreadcrumb()}" : "");
Icon = nestedEntry.Icon;
nestedEntry.InnerEntries.ForEach(AddEntry);
Expand All @@ -132,9 +146,10 @@ private void AscendLevel(NestedMenuEntry<T> nestedEntry)
private string GetBreadcrumb() => string.Join(" / ", levels.Reverse().Select(x => x.Title));

/// <summary>
/// Descends down a level in the menu hierarchy and removes the entry from the <see cref="levels"/>.
/// Ascends up a level in the menu hierarchy and removes the current level from the <see cref="levels"/>.
/// </summary>
private void DescendLevel()
/// <param name="exitOnEmpty">Whether to exit the menu when the top level is reached</param>
private void AscendLevel(bool exitOnEmpty = true)
{
if (levels.Count != 0)
{
Expand All @@ -148,15 +163,15 @@ private void DescendLevel()
}
level.Entries.ForEach(AddEntry);
}
else
else if (exitOnEmpty)
{
ShowMenu = false;
}
MenuTooltip.Hide();
}

/// <summary>
/// Resets to the lowest level, i.e. resets the menu to the state it was in before any
/// Resets to the highest level, i.e. resets the menu to the state it was in before any
/// <see cref="NestedMenuEntry"/> was clicked.
/// </summary>
public void ResetToBase()
Expand All @@ -167,7 +182,7 @@ public void ResetToBase()
}
while (levels.Count > 0)
{
DescendLevel();
AscendLevel();
}
}

Expand All @@ -185,23 +200,37 @@ protected override void StartDesktop()
{
if (searchField.gameObject.TryGetComponentOrLog(out searchInput))
{
searchInput.onValueChanged.AddListener(SearchTextEntered);
searchInput.onValueChanged.AddListener(_ => SearchTextEnteredAsync().Forget());
MenuManager.onCancel.AddListener(() =>
{
searchActive = false;
searchInput.text = string.Empty;
});
}
}
MenuManager.onCancel.AddListener(DescendLevel); // Go one level higher when clicking "back"
else
{
Debug.Log("Search field must be present in the prefab for the nested menu to work properly.");
}
MenuManager.onCancel.AddListener(() => AscendLevel()); // Go one level higher when clicking "back"
if (ResetLevelOnClose)
{
// When closing the menu, its level will be reset to the top.
MenuManager.onConfirm.AddListener(ResetToBase);
}

// If the menu is enabled, keyboard shortcuts must be disabled and vice versa.
OnShowMenuChanged += () => SEEInput.KeyboardShortcutsEnabled = !ShowMenu;
OnShowMenuChanged += () =>
{
// If the menu is enabled, keyboard shortcuts must be disabled and vice versa.
SEEInput.KeyboardShortcutsEnabled = !ShowMenu;
// Additionally, if the menu is disabled, the search input must be cleared
// and the level must be reset to the top.
if (!ShowMenu)
{
searchInput.text = string.Empty;
ResetToBase();
}
};
}

/// <summary>
Expand All @@ -210,12 +239,12 @@ protected override void StartDesktop()
/// <returns>All leaf-entries of the nestedMenu.</returns>
private IEnumerable<T> GetAllEntries()
{
IList<T> allEntries = levels.LastOrDefault()?.Entries ?? Entries;
return GetAllEntries(allEntries);
IList<T> allMenuEntries = levels.LastOrDefault()?.Entries ?? Entries;
return GetAllEntries(allMenuEntries);
}

/// <summary>
/// Searchs through the complete tree of the nestedMenu and selects all MenuEntries.
/// Searches through the complete tree of the nestedMenu and selects all MenuEntries.
/// </summary>
/// <param name="startingEntries">the entries to research.</param>
/// <returns>All leafEntries of the nestedMenu.</returns>
Expand All @@ -239,31 +268,54 @@ private static IEnumerable<T> GetAllEntries(IEnumerable<T> startingEntries)

/// <summary>
/// The action which is called by typing inside of the fuzzy-search input field.
/// Displays all results of the fuzzySearch inside of the menu ordered by matching-specifity.
/// Displays all results of the fuzzySearch inside of the menu ordered by matching-specificity.
/// </summary>
/// <param name="text">the text inside of the fuzzy-search.</param>
private void SearchTextEntered(string text)
/// <remarks>
/// This method is async because the menu may need an additional frame to reset properly.
/// </remarks>
private async UniTaskVoid SearchTextEnteredAsync()
{
if (searchActive)
if (!await searchSemaphore.WaitAsync(0))
{
DescendLevel();
// If the semaphore is locked, a search is already in progress.
return;
}

searchActive = text.Length != 0;
if (text.Length == 0)
try
{
return;
}
if (searchActive)
{
AscendLevel(exitOnEmpty: false);
// We need to wait for the next frame to ensure that the level has been reset properly.
await UniTask.WaitForEndOfFrame();
}

allEntries ??= GetAllEntries().ToDictionary(x => x.Title, x => x);
IEnumerable<T> results = Process.ExtractTop(GraphSearch.FilterString(text), allEntries.Keys, cutoff: 10)
.OrderByDescending(x => x.Score)
.Select(x => allEntries[x.Value])
.ToList();
if (!ShowMenu)
{
// If the menu has been hidden in the meantime, we don't want to search.
return;
}

NestedMenuEntry<T> resultEntry = new(results, "Results", $"Found {results.Count()} help pages.",
default, default, Resources.Load<Sprite>("Materials/Notification/info"));
AscendLevel(resultEntry);
searchActive = searchInput.text.Length != 0;
if (searchInput.text.Length == 0)
{
return;
}

allEntries ??= GetAllEntries().ToDictionary(x => x.Title, x => x);
IEnumerable<T> results = Process.ExtractTop(GraphSearch.FilterString(searchInput.text), allEntries.Keys, cutoff: 10)
.OrderByDescending(x => x.Score)
.Select(x => allEntries[x.Value])
.ToList();

NestedMenuEntry<T> resultEntry = new(results, Title, Description,
default, default, Icon);
DescendLevel(resultEntry, withBreadcrumb: false);
}
finally
{
searchSemaphore.Release();
}
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Axivion/axivion-jenkins.bat
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ if "%AXIVION_DASHBOARD_URL%"=="" (
)

if "%UNITY%"=="" (
set "UNITY=C:\Program Files\Unity\Hub\Editor\2022.3.18f1"
set "UNITY=C:\Program Files\Unity\Hub\Editor\2022.3.20f1"
)

if not exist "%UNITY%" (
Expand Down
6 changes: 3 additions & 3 deletions Packages/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
"com.unity.ide.visualstudio": "2.0.22",
"com.unity.ide.vscode": "1.2.5",
"com.unity.inputsystem": "1.7.0",
"com.unity.netcode.gameobjects": "1.7.1",
"com.unity.netcode.gameobjects": "1.8.0",
"com.unity.postprocessing": "3.4.0",
"com.unity.shadergraph": "14.0.10",
"com.unity.test-framework": "1.1.33",
"com.unity.testtools.codecoverage": "1.2.5",
"com.unity.textmeshpro": "3.0.7",
"com.unity.textmeshpro": "3.0.8",
"com.unity.timeline": "1.7.6",
"com.unity.toolchain.linux-x86_64": "2.0.6",
"com.unity.ugui": "1.0.0",
"com.unity.xr.interaction.toolkit": "2.5.2",
"com.unity.xr.legacyinputhelpers": "2.1.10",
"com.unity.xr.management": "4.4.1",
"com.unity.xr.openxr": "1.9.1",
"com.unity.xr.openxr": "1.10.0",
"judah4.hsvcolorpickerunity": "https://github.com/judah4/HSV-Color-Picker-Unity.git#upm",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
Expand Down
8 changes: 4 additions & 4 deletions Packages/packages-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@
"url": "https://packages.unity.com"
},
"com.unity.netcode.gameobjects": {
"version": "1.7.1",
"version": "1.8.0",
"depth": 0,
"source": "registry",
"dependencies": {
Expand Down Expand Up @@ -259,7 +259,7 @@
"url": "https://packages.unity.com"
},
"com.unity.textmeshpro": {
"version": "3.0.7",
"version": "3.0.8",
"depth": 0,
"source": "registry",
"dependencies": {
Expand Down Expand Up @@ -310,7 +310,7 @@
}
},
"com.unity.xr.core-utils": {
"version": "2.2.3",
"version": "2.3.0",
"depth": 1,
"source": "registry",
"dependencies": {
Expand Down Expand Up @@ -357,7 +357,7 @@
"url": "https://packages.unity.com"
},
"com.unity.xr.openxr": {
"version": "1.9.1",
"version": "1.10.0",
"depth": 0,
"source": "registry",
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions ProjectSettings/ProjectVersion.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
m_EditorVersion: 2022.3.18f1
m_EditorVersionWithRevision: 2022.3.18f1 (d29bea25151d)
m_EditorVersion: 2022.3.20f1
m_EditorVersionWithRevision: 2022.3.20f1 (61c2feb0970d)
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Tests](https://github.com/uni-bremen-agst/SEE/actions/workflows/main.yml/badge.svg)](https://github.com/uni-bremen-agst/SEE/actions/workflows/main.yml)

SEE visualizes hierarchical dependency graphs of software in 3D/VR based on the city metaphor.
The underlying game engine is Unity 3D (version 2022.3.19f1).
The underlying game engine is Unity 3D (version 2022.3.20f1).

![Screenshot of SEE](Screenshot.png)

Expand Down

0 comments on commit 5546ef6

Please sign in to comment.