Skip to content

Commit

Permalink
Merge pull request #42 from MaxWasUnavailable/improved-modlist-ui
Browse files Browse the repository at this point in the history
Improved modlist UI & various UI fixes
  • Loading branch information
legoandmars authored Feb 15, 2024
2 parents 2cc79e4 + 9fd01c8 commit ed1902e
Show file tree
Hide file tree
Showing 14 changed files with 1,099 additions and 187 deletions.
317 changes: 248 additions & 69 deletions LobbyCompatibility/Behaviours/ModListPanel.cs

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions LobbyCompatibility/Behaviours/ModListTab.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using LobbyCompatibility.Enums;
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace LobbyCompatibility.Behaviours;

/// <summary>
/// Mod list tab used to change the filtering of a <see cref="ModListPanel"/>.
/// </summary>
public class ModListTab : MonoBehaviour
{
public ModListFilter ModListFilter;
private Image? _tabBackground;
private Image? _tabOutline;
private Button? _button;
private TextMeshProUGUI? _buttonText;
private Color _selectedColor;
private Color _unselectedColor;

/// <summary>
/// Set up our tab based on precreated UI elements.
/// </summary>
/// <param name="tabBackground"> The image to use as the tab background. </param>
/// <param name="tabOutline"> The image to use as the tab background's outline. </param>
/// <param name="button"> The button to use on the tab. </param>
/// <param name="buttonText"> The button's text. </param>
/// <param name="modListFilter"> The <see cref="ModListFilter"/> to apply when clicking the tab. </param>
/// <param name="selectedColor"> Color to use when the tab is selected. </param>
/// <param name="unselectedColor"> Color to use when the tab is not selected. </param>
public void Setup(Image tabBackground, Image tabOutline, Button button, TextMeshProUGUI buttonText, ModListFilter modListFilter, Color selectedColor, Color unselectedColor)
{
ModListFilter = modListFilter;
_tabBackground = tabBackground;
_tabOutline = tabOutline;
_button = button;
_buttonText = buttonText;
_selectedColor = selectedColor;
_unselectedColor = unselectedColor;

// Need to use TMP rich text to set size, because buttons have a custom style that cannot be overridden with normal text properties
_buttonText.text = "<size=13px><cspace=-0.04em>" + modListFilter.ToString();
}

/// <summary>
/// Set up events that will be called when the tab is clicked.
/// </summary>
/// <param name="action"> The action that will be called when the tab is clicked. </param>
public void SetupEvents(Action<ModListFilter> action)
{
if (_button == null)
return;

// Clear original onClick events
_button.onClick.m_PersistentCalls.Clear();
_button.onClick.RemoveAllListeners();

_button.onClick.AddListener(() => action(ModListFilter));
}

/// <summary>
/// Set if the tab visuals should appear selected.
/// </summary>
/// <param name="active"> Whether or not the tab should appear selected. </param>
public void SetSelectionStatus(bool selected)
{
if (_tabBackground == null || _tabOutline == null)
return;

_tabBackground.color = selected ? _selectedColor : _unselectedColor;
_tabOutline.color = new Color(_tabOutline.color.r, _tabOutline.color.g, _tabOutline.color.b, selected ? 1f : 0.3f);
}
}
142 changes: 95 additions & 47 deletions LobbyCompatibility/Behaviours/ModListTooltipPanel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using LobbyCompatibility.Enums;
using LobbyCompatibility.Enums;
using LobbyCompatibility.Features;
using LobbyCompatibility.Models;
using LobbyCompatibility.Pooling;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using Image = UnityEngine.UI.Image;
Expand All @@ -21,16 +22,17 @@ public class ModListTooltipPanel : MonoBehaviour
private static readonly Vector2 NotificationWidth = new(0.75f, 1.1f);
private static readonly float HeaderSpacing = 12f;
private static readonly float TextSpacing = 11f;
private static readonly int MaxLines = 10;
private readonly List<TextMeshProUGUI> _existingText = new();
private static readonly int MaxLines = 12;

// Needed for mod diff text generation
private TextMeshProUGUI? _headerTextTemplate;
private List<PluginDiffSlot?> _spawnedPluginDiffSlots = new();
private List<PluginCategorySlot?> _spawnedPluginCategorySlots = new();

private RectTransform? _panelTransform;
private TextMeshProUGUI? _textTemplate;
private TextMeshProUGUI? _titleText;

private PluginDiffSlotPool? _pluginDiffSlotPool;
private PluginCategorySlotPool? _pluginCategorySlotPool;

private void Awake()
{
Instance = this;
Expand All @@ -48,7 +50,8 @@ private void Awake()
// Find actual alert panel so we can modify it
var panelImage = transform.Find("Panel")?.GetComponent<Image>();
_panelTransform = panelImage?.rectTransform;
if (panelImage == null || _panelTransform == null)
var panelOutlineImage = _panelTransform?.Find("Image")?.GetComponent<Image>();
if (panelImage == null || _panelTransform == null || panelOutlineImage == null)
return;

// Increase panel opacity to 100% since we disabled the background image
Expand All @@ -58,7 +61,7 @@ private void Awake()

// Multiply panel element sizes to make the hover notification skinnier
UIHelper.TryMultiplySizeDelta(_panelTransform, NotificationWidth);
UIHelper.TryMultiplySizeDelta(_panelTransform.Find("Image"), NotificationWidth);
UIHelper.TryMultiplySizeDelta(panelOutlineImage.transform, NotificationWidth);
UIHelper.TryMultiplySizeDelta(_panelTransform.Find("NotificationText"), NotificationWidth);

// Remove "dismiss" button
Expand All @@ -69,30 +72,90 @@ private void Awake()
_panelTransform.anchoredPosition = new Vector2(sizeDelta.x / 2, -sizeDelta.y / 2);
_panelTransform.gameObject.SetActive(false);

SetupText(_panelTransform);
SetupText(_panelTransform, panelOutlineImage);
}

// This could/should eventually be moved to a UIHelper method if we want this to look identical to the full mod list panel
private void SetupText(RectTransform panelTransform)
private void SetupText(RectTransform panelTransform, Image panelOutlineImage)
{
_titleText = panelTransform.Find("NotificationText")?.GetComponent<TextMeshProUGUI>();
if (_titleText == null)
return;

// Create new image to put text inside of
var textContainerImage = Instantiate(panelOutlineImage, panelTransform);
textContainerImage.sprite = null;
textContainerImage.color = Color.clear;
textContainerImage.rectTransform.sizeDelta = panelTransform.sizeDelta;

// Setup ContentSizeFilter and VerticalLayoutGroup so diff elements are automagically spaced
UIHelper.AddVerticalLayoutGroup(textContainerImage.gameObject, false);

// Setup text
_titleText.transform.SetParent(textContainerImage.transform);
_titleText.fontSizeMax = 13f;
_titleText.fontSizeMin = 12f;
_titleText.rectTransform.anchoredPosition = new Vector2(0, 95f);
_titleText.gameObject.SetActive(false);

// Setup PluginDiffSlot template panel
var pluginDiffSlot = new GameObject("PluginDiffSlot");
var pluginDiffSlotImage = pluginDiffSlot.AddComponent<Image>();
var pluginDiffSlotTransform = UIHelper.ApplyParentSize(pluginDiffSlot, textContainerImage.transform);
pluginDiffSlotTransform.anchoredPosition = Vector2.zero;
pluginDiffSlotTransform.sizeDelta = new Vector2(1f, TextSpacing);
pluginDiffSlotImage.color = Color.clear;
pluginDiffSlot.SetActive(false);

// Setup text as template
_headerTextTemplate =
UIHelper.SetupTextAsTemplate(_titleText, _titleText.color, new Vector2(165f, 75f), 13f, 2f);
_textTemplate = UIHelper.SetupTextAsTemplate(_titleText, _titleText.color, new Vector2(165f, 75f), 13f, 2f,
HorizontalAlignmentOptions.Left);

// Make the title wrap with compact line spacing
_titleText.lineSpacing = -20f;
_titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
_titleText.rectTransform.sizeDelta = new Vector2(170f, 75f);
var pluginNameText =
UIHelper.SetupTextAsTemplate(_titleText, pluginDiffSlotTransform, _titleText.color, new Vector2(160f, 75f), 13f, 2f,
HorizontalAlignmentOptions.Left, new Vector2(0f, 7f));

// Make the text wrap with compact line spacing
pluginNameText.lineSpacing = -20f;

// Finish PluginDiffSlot setup
var diffSlot = pluginDiffSlot.AddComponent<PluginDiffSlot>();
diffSlot.SetupText(pluginNameText);

// Setup PluginCategorySlot template panel, identical except for the height
var diffSlotClone = Instantiate(diffSlot, pluginDiffSlotTransform.parent);
diffSlotClone.GetComponent<RectTransform>().sizeDelta = new Vector2(1, HeaderSpacing);
var categorySlot = diffSlotClone.gameObject.AddComponent<PluginCategorySlot>();

// Setup all text for PluginCategorySlot
if (diffSlotClone.PluginNameText != null)
{
diffSlotClone.PluginNameText.horizontalAlignment = HorizontalAlignmentOptions.Center;
categorySlot.SetupText(diffSlotClone.PluginNameText);
}

// Remove duplicate PluginDiffSlot on PluginCategorySlot and finish setup
Destroy(diffSlotClone);
categorySlot.gameObject.SetActive(false);

// Duplicate category slot to use as main "title" text
var titleTextContainer = Instantiate(categorySlot, categorySlot.transform.parent);
var titleTextContainerTransform = titleTextContainer.GetComponent<RectTransform>();
titleTextContainer.gameObject.SetActive(true);
_titleText = titleTextContainer.CategoryNameText;

if (titleTextContainerTransform == null || titleTextContainer.CategoryNameText == null)
return;

// Setup new positioning for the larger title text
titleTextContainerTransform.sizeDelta = new Vector2(1, HeaderSpacing * 4);
titleTextContainer.CategoryNameText.rectTransform.anchoredPosition = new Vector2(0, 20f);
titleTextContainer.CategoryNameText.horizontalAlignment = HorizontalAlignmentOptions.Left;

// Initialize pools
var pluginPoolsObject = new GameObject("PluginSlotPools");
pluginPoolsObject.transform.SetParent(panelTransform);
_pluginDiffSlotPool = pluginPoolsObject.AddComponent<PluginDiffSlotPool>();
_pluginDiffSlotPool.InitializeUsingTemplate(diffSlot, diffSlot.transform.parent);
_pluginCategorySlotPool = pluginPoolsObject.AddComponent<PluginCategorySlotPool>();
_pluginCategorySlotPool.InitializeUsingTemplate(categorySlot, categorySlot.transform.parent);
}

public void DisplayNotification(LobbyDiff lobbyDiff, RectTransform elementTransform,
Expand Down Expand Up @@ -123,14 +186,21 @@ public void DisplayNotification(LobbyDiff lobbyDiff, RectTransform elementTransf
_panelTransform.anchoredPosition = hoverPanelPosition;
_panelTransform.gameObject.SetActive(true);

if (_panelTransform == null || _titleText == null || _pluginCategorySlotPool == null || _pluginDiffSlotPool == null)
return;


DisplayModList(lobbyDiff);
}

private void DisplayModList(LobbyDiff lobbyDiff)
{
if (_panelTransform == null || _titleText == null || _headerTextTemplate == null || _textTemplate == null)
if (_panelTransform == null || _titleText == null || _pluginCategorySlotPool == null || _pluginDiffSlotPool == null)
return;

// Despawn old diffs
UIHelper.ClearSpawnedDiffSlots(_pluginDiffSlotPool, _pluginCategorySlotPool, ref _spawnedPluginDiffSlots, ref _spawnedPluginCategorySlots);

var incompatibleModsCount = lobbyDiff.PluginDiffs
.Where(pluginDiff => pluginDiff.PluginDiffResult != PluginDiffResult.Compatible &&
pluginDiff.PluginDiffResult != PluginDiffResult.Unknown).ToList().Count;
Expand All @@ -143,31 +213,9 @@ private void DisplayModList(LobbyDiff lobbyDiff)
_titleText.text =
$"{lobbyDiff.GetDisplayText()}\nTotal Mods: ({lobbyDiff.PluginDiffs.Count})\nIncompatible Mods: {incompatibleMods}\n========================";

// clear old text
foreach (var text in _existingText)
{
if (text == null)
continue;

Destroy(text.gameObject);
}

_existingText.Clear();

// Generate text based on LobbyDiff
var (newText, padding, pluginsShown) = UIHelper.GenerateTextFromDiff(lobbyDiff, _textTemplate,
_headerTextTemplate, TextSpacing, HeaderSpacing, -51.5f, true, MaxLines);

// Add cutoff text if necessary
var remainingPlugins = lobbyDiff.PluginDiffs.Count - pluginsShown;
if (newText.Count >= MaxLines && remainingPlugins > 0)
{
var cutoffText = UIHelper.CreateTextFromTemplate(_textTemplate,
$"{lobbyDiff.PluginDiffs.Count - pluginsShown} more mods...", -padding, Color.gray);
newText.Add(cutoffText);
}

_existingText.AddRange(newText); // probably doesn't need to be an AddRange since we just deleted stuff
// Spawn new diffslots
(_spawnedPluginDiffSlots, _spawnedPluginCategorySlots) = UIHelper.GenerateDiffSlotsFromLobbyDiff(
lobbyDiff, _pluginDiffSlotPool, _pluginCategorySlotPool, null, MaxLines);
}

public void HideNotification()
Expand Down
69 changes: 69 additions & 0 deletions LobbyCompatibility/Behaviours/PluginCategorySlot.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using LobbyCompatibility.Enums;
using LobbyCompatibility.Features;
using TMPro;
using UnityEngine;

namespace LobbyCompatibility.Behaviours;

/// <summary>
/// Slot used to show a <see cref="PluginDiffResult"/> header in a <see cref="ModListPanel"/>.
/// </summary>
internal class PluginCategorySlot : MonoBehaviour
{
[field: SerializeField]
public TextMeshProUGUI? CategoryNameText { get; private set; }
[field: SerializeField]
public TextMeshProUGUI? ClientVersionCategoryNameText { get; private set; }
[field: SerializeField]
public TextMeshProUGUI? ServerVersionCategoryNameText { get; private set; }

/// <summary>
/// Set up the slot using existing text objects.
/// </summary>
/// <param name="categoryNameText"> Text to use to display the category's name. </param>
/// <param name="clientVersionCategoryNameText"> Text to use to display the category's client plugin version header. </param>
/// <param name="serverVersionCategoryNameText"> Text to use to display the category's server plugin version header. </param>
public void SetupText(TextMeshProUGUI categoryNameText, TextMeshProUGUI? clientVersionCategoryNameText = null, TextMeshProUGUI? serverVersionCategoryNameText = null)
{
CategoryNameText = categoryNameText;
ClientVersionCategoryNameText = clientVersionCategoryNameText;
ServerVersionCategoryNameText = serverVersionCategoryNameText;

// Make sure text is enabled and setup properly
categoryNameText.gameObject.SetActive(true);
}

/// <summary>
/// Set the slot's text based on a <see cref="PluginDiffResult"/>.
/// </summary>
/// <param name="pluginDiffResult"> PluginDiffResult to display. </param>
public void SetPluginDiffResult(PluginDiffResult pluginDiffResult)
{
if (CategoryNameText == null)
return;

CategoryNameText.text = LobbyHelper.GetCompatibilityHeader(pluginDiffResult);

SetText(CategoryNameText.text);
}

/// <summary>
/// Manually set the slot's text.
/// </summary>
/// <param name="text"> The category header to display. </param>
private void SetText(string text)
{
if (CategoryNameText == null)
return;

CategoryNameText.text = text + ":";

if (ClientVersionCategoryNameText == null || ServerVersionCategoryNameText == null)
return;

ClientVersionCategoryNameText.text = "You";
ServerVersionCategoryNameText.text = "Lobby";

// TODO: Use color for a little colored icon or something?
}
}
Loading

0 comments on commit ed1902e

Please sign in to comment.