Compare commits

..

4 commits

Author SHA1 Message Date
Mark Joshwel 685fbe961d game: back 2 square 1 (of them) 2024-11-17 22:05:04 +08:00
Mark Joshwel 5a0159c929 game: make compilable 2024-11-17 21:36:00 +08:00
Mark Joshwel 27f42ffc55 Revert "game: overcomplicated interim 2"
This reverts commit 4e6c7db3f1.
2024-11-17 21:32:26 +08:00
Mark Joshwel 4e6c7db3f1 game: overcomplicated interim 2 2024-11-17 21:32:14 +08:00
10 changed files with 278 additions and 229 deletions

View file

@ -132,6 +132,7 @@ GameObject:
m_Component:
- component: {fileID: 133964672}
- component: {fileID: 133964671}
- component: {fileID: 133964676}
- component: {fileID: 133964673}
- component: {fileID: 133964674}
- component: {fileID: 133964675}
@ -213,6 +214,19 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6351b7620d84e2d43bc4f59c5f3f8b5c, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &133964676
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 133964670}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ac64716d16f44bd596798277ddc5eaaf, type: 3}
m_Name:
m_EditorClassIdentifier:
state: 0
--- !u!1 &447905425
GameObject:
m_ObjectHideFlags: 0
@ -459,6 +473,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 0cfc0b50e9003a0468ebdc186439c53b, type: 3}
m_Name:
m_EditorClassIdentifier:
ui: {fileID: 0}
--- !u!4 &1204483826
Transform:
m_ObjectHideFlags: 0

View file

@ -116,6 +116,10 @@ public void OnEnable()
_secondaryActionButton.clicked += OnSecondaryActionButtonClick;
TransitionStateTo(State.NotSignedIn);
}
private void Awake()
{
GameManager.Instance.Backend.RegisterOnSignInCallback(OnSignInCallback);
}
@ -216,12 +220,12 @@ private void TransitionStateTo(State newState, bool keepAccompanyingText = false
state = newState;
}
private void ValidateUsername()
private void ValidateUsername(TextField usernameField)
{
// just has to be min. 5 characters
_usernameField.style.color = _defaultInputFieldValueTextColour;
if (_usernameField.value.Length >= 5) return;
_usernameField.style.color = _errorInputFieldValueTextColour;
usernameField.style.color = _defaultInputFieldValueTextColour;
if (usernameField.value.Length >= 5) return;
usernameField.style.color = _errorInputFieldValueTextColour;
throw new Exception("Username must be at least 5 characters long.");
}
@ -279,18 +283,18 @@ private void ValidateFields(TextField emailField, TextField passwordField)
try
{
ValidateEmailField(_emailField);
ValidateEmailField(emailField);
}
catch (Exception _)
catch (Exception)
{
invalidEmail = true;
}
try
{
ValidatePasswordField(_passwordField);
ValidatePasswordField(passwordField);
}
catch (Exception _)
catch (Exception)
{
invalidPassword = true;
}
@ -321,27 +325,27 @@ private void ValidateFields(TextField emailField, TextField passwordField, TextF
try
{
ValidateEmailField(_emailField);
ValidateEmailField(emailField);
}
catch (Exception _)
catch (Exception)
{
invalidEmail = true;
}
try
{
ValidatePasswordField(_passwordField);
ValidatePasswordField(passwordField);
}
catch (Exception _)
catch (Exception)
{
invalidPassword = true;
}
try
{
ValidateUsername();
ValidateUsername(usernameField);
}
catch (Exception _)
catch (Exception)
{
invalidUsername = true;
}
@ -369,7 +373,7 @@ private void OnUsernameUpdateButtonClick()
{
try
{
ValidateUsername();
ValidateUsername(_usernameField);
}
catch (Exception e)
{

View file

@ -62,6 +62,7 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb)
/// a and b must be normalized so a^2 + b^2 == 1
/// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static float find_gamut_intersection(
float a,
float b,
@ -164,6 +165,7 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb)
/// a and b must be normalized so a^2 + b^2 == 1
/// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static LC find_cusp(float a, float b)
{
// First, find the maximum saturation (saturation S = C/L)
@ -178,7 +180,7 @@ public static LC find_cusp(float a, float b)
}
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
// ReSharper disable once MemberCanBePrivate.Global
public static Lab linear_srgb_to_oklab(RGB c)
{
var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
@ -220,6 +222,7 @@ public static RGB oklab_to_linear_srgb(Lab c)
/// a and b must be normalized so a^2 + b^2 == 1
/// </summary>
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
// ReSharper disable once MemberCanBePrivate.Global
public static float compute_max_saturation(float a, float b)
{
// Max saturation will be when one of r, g or b goes below zero.

View file

@ -1,47 +1,29 @@
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UIElements;
/// <summary>
/// singleton for a single source of truth game state and flow management
/// </summary>
public class GameManager : MonoBehaviour
{
/// <summary>
/// enum for available menus in the game, for use with <c>ShowMenu()</c>
/// </summary>
public enum DisplayState
{
Nothing,
PlayView,
LeaderboardView,
AccountView,
}
/// <summary>
/// singleton pattern: define instance field for accessing the singleton elsewhere
/// </summary>
public static GameManager Instance;
/// <summary>
/// the current display state of the game
/// </summary>
[SerializeField] private DisplayState state = DisplayState.Nothing;
/// <summary>
/// the visual element object for game ui (hud/prompts/tooltips)
/// the local player data object for storing player data
/// </summary>
private VisualElement _ui;
private LocalPlayerData _localPlayerData;
/// <summary>
/// backend object for handling communication with the firebase backend
/// </summary>
public Backend Backend;
/// <summary>
/// the local player data object for storing player data
/// ui manager object for handling ui state and flow
/// </summary>
private LocalPlayerData _localPlayerData;
public UIManager ui;
/// <summary>
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
@ -61,30 +43,27 @@ private void Awake()
Debug.Log("awake as non-singleton instance, destroying self");
Destroy(gameObject);
}
}
private void Start()
{
SetDisplayState(DisplayState.PlayView);
// load the local player data and refresh the ui
_localPlayerData = new LocalPlayerData();
_localPlayerData.LoadFromTheWorld();
// register a callback to refresh the ui when the player signs in
Backend.RegisterOnSignInCallback(_ =>
{
_localPlayerData.LoadFromTheWorld();
});
}
/// <summary>
/// called when the game object is enabled
/// called when the game object is enabled, initialises variables
/// </summary>
private void OnEnable()
{
Backend = new Backend();
Backend.Initialise();
ui = UIManager.Instance;
}
private void Start()
{
// load the local player data and refresh the ui
_localPlayerData = new LocalPlayerData();
_localPlayerData.LoadFromTheWorld();
// register a callback to refresh the ui when the player signs in
Backend.RegisterOnSignInCallback(_ => { _localPlayerData.LoadFromTheWorld(); });
}
/// <summary>
@ -94,15 +73,4 @@ private void OnDestroy()
{
Backend.Cleanup();
}
/// <summary>
/// function to show a menu based on the enum passed,
/// and any other necessary actions
/// </summary>
/// <param name="newDisplayState">the game menu to show</param>
public void SetDisplayState(DisplayState newDisplayState)
{
var currentDisplayState = St;
switch (newDisplayState)
}
}

View file

@ -4,7 +4,7 @@ MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
executionOrder: -10
icon: {instanceID: 0}
userData:
assetBundleName:

View file

@ -5,32 +5,32 @@
public class LocalPlayerData
{
/// <summary>
/// queue of the best online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> BestOnlineScores = new(30);
/// <summary>
/// last known email used
/// </summary>
public string LastKnownEmail = "";
/// <summary>
/// last known username used
/// </summary>
public string LastKnownUsername = "Guest";
/// <summary>
/// queue of the 10 most recent local scores
/// </summary>
public Queue<Score> RecentLocalScores = new(10);
/// <summary>
/// queue of the 10 most recent online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> RecentOnlineScores = new(10);
/// <summary>
/// queue of the best online scores,
/// used in user rating calculation and accuracy display stats
/// </summary>
public Queue<Score> BestOnlineScores = new(30);
/// <summary>
/// loads player data from player prefs and database
@ -46,6 +46,7 @@ public void LoadFromTheWorld()
currentKnownEmail = possibleUser.Email;
currentKnownUsername = GameManager.Instance.Backend.GetUsername();
}
var lastStoredEmail = PlayerPrefs.GetString("LastKnownEmail", "");
var lastStoredUsername = PlayerPrefs.GetString("LastKnownUsername", "Guest");
LastKnownEmail = string.IsNullOrEmpty(currentKnownEmail) ? lastStoredEmail : currentKnownEmail;
@ -78,27 +79,19 @@ public void LoadFromTheWorld()
RegisterLocalScore(new Score(timestamp, noOfRounds, l, c, h));
}
// load online scores
RecentOnlineScores.Clear();
GameManager.Instance.Backend.GetRecentScores((dtr, recentOnlineScores) =>
GameManager.Instance.Backend.GetRecentScores((_, recentOnlineScores) =>
{
foreach (var onlineScore in recentOnlineScores)
{
if (RecentOnlineScores.Count > 10) RecentOnlineScores.Dequeue();
RecentOnlineScores.Enqueue(onlineScore);
}
});
}
/// <summary>
/// registers a score to the player's local data
/// </summary>
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
Debug.Log("loaded online scores from backend");
});
}
/// <summary>
@ -121,9 +114,21 @@ public void SaveToTheWorld()
idx++;
}
Debug.Log("saved lpdata to playerprefs");
// online scores are already saved in the backend
}
/// <summary>
/// registers a score to the player's local data
/// </summary>
/// <param name="score">the score to register</param>
public void RegisterLocalScore(Score score)
{
if (RecentLocalScores.Count > 10) RecentLocalScores.Dequeue();
RecentLocalScores.Enqueue(score);
}
public struct Score
{
/// <summary>

View file

@ -74,7 +74,7 @@ private void OnEnable()
private void RenderFromPlayerData(LocalPlayerData localPlayerData)
{
// calculate averages from both recent local scores and online scores
var totalLightessAcc = 0f;
var totalLightnessAcc = 0f;
var totalChromaAcc = 0f;
var totalHueAcc = 0f;
var totalRounds = 0;
@ -83,7 +83,7 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData)
foreach (var localScore in localPlayerData.RecentLocalScores)
{
totalLightessAcc += localScore.AvgLightnessAccuracy;
totalLightnessAcc += localScore.AvgLightnessAccuracy;
totalChromaAcc += localScore.AvgChromaAccuracy;
totalHueAcc += localScore.AvgHueAccuracy;
totalRounds += localScore.NoOfRounds;
@ -91,26 +91,26 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData)
foreach (var onlineScore in localPlayerData.RecentOnlineScores)
{
totalLightessAcc += onlineScore.AvgLightnessAccuracy;
totalLightnessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds;
}
foreach (var onlineScore in localPlayerData.BestOnlineScores)
{
totalLightessAcc += onlineScore.AvgLightnessAccuracy;
totalLightnessAcc += onlineScore.AvgLightnessAccuracy;
totalChromaAcc += onlineScore.AvgChromaAccuracy;
totalHueAcc += onlineScore.AvgHueAccuracy;
totalRounds += onlineScore.NoOfRounds;
}
// finally, set the labels
_playerNameText.text = GameManager.Instance.Backend.GetUsername();
_lightnessAccuracyText.text = $"{(totalLightessAcc / totalRounds):F}";
_hueAccuracyText.text = $"{(totalHueAcc / totalRounds):F}";
_chromaAccuracyText.text = $"{(totalChromaAcc / totalRounds):F}";
_lightnessAccuracyText.text = $"{totalLightnessAcc / totalRounds:F}";
_hueAccuracyText.text = $"{totalHueAcc / totalRounds:F}";
_chromaAccuracyText.text = $"{totalChromaAcc / totalRounds:F}";
// and set the player rating, but after we get it from the backend
// (god I LOVE async (I am LYING out of my teeth))
GameManager.Instance.Backend.CalculateUserRating((dtr, rating) =>
@ -125,7 +125,7 @@ private void RenderFromPlayerData(LocalPlayerData localPlayerData)
/// </summary>
private static void OnPlayButtonClicked()
{
GameManager.Instance.SetDisplayState(GameManager.DisplayState.PlayView);
GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.PlayView);
}
/// <summary>
@ -133,7 +133,7 @@ private static void OnPlayButtonClicked()
/// </summary>
private static void OnLeaderboardButtonClicked()
{
GameManager.Instance.SetDisplayState(GameManager.DisplayState.LeaderboardView);
GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.LeaderboardView);
}
/// <summary>
@ -141,6 +141,6 @@ private static void OnLeaderboardButtonClicked()
/// </summary>
private static void OnAccountButtonClicked()
{
GameManager.Instance.SetDisplayState(GameManager.DisplayState.AccountView);
GameManager.Instance.ui.SetDisplayState(UIManager.DisplayState.AccountView);
}
}

View file

@ -0,0 +1,113 @@
using System;
using UnityEngine;
using UnityEngine.UIElements;
public class UIManager : MonoBehaviour
{
/// <summary>
/// singleton pattern: define instance field for accessing the singleton elsewhere
/// </summary>
public static UIManager Instance;
/// <summary>
/// enum for available menus in the game, for use with <c>ShowMenu()</c>
/// </summary>
public enum DisplayState
{
UnassociatedState,
Nothing,
PlayView,
LeaderboardView,
AccountView
}
/// <summary>
/// the current display state of the game
/// </summary>
[SerializeField] private DisplayState state = DisplayState.UnassociatedState;
/// <summary>
/// the visual element object for game ui
/// </summary>
public VisualElement UI;
/// <summary>
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
/// </summary>
private void Awake()
{
// check if instance hasn't been set yet
if (Instance == null)
{
Debug.Log("awake as singleton instance, setting self as the forever-alive instance");
Instance = this;
DontDestroyOnLoad(gameObject);
}
// check if instance is already set and it's not this instance
else if (Instance != null && Instance != this)
{
Debug.Log("awake as non-singleton instance, destroying self");
Destroy(gameObject);
}
}
private void OnEnable()
{
UI = GetComponent<UIDocument>().rootVisualElement;
SetDisplayState(DisplayState.Nothing);
}
/// <summary>
/// function to show a menu based on the enum passed,
/// and any other necessary actions
/// </summary>
/// <param name="newDisplayState">the game menu to show</param>
public void SetDisplayState(DisplayState newDisplayState)
{
var currentDisplayState = state;
// if the new state is the same as the current state, do nothing
if (currentDisplayState == newDisplayState)
{
Debug.Log($"staying at {currentDisplayState} (illogical transition)");
return;
}
Debug.Log($"switching from {currentDisplayState} to {newDisplayState}");
var gameView = UI.Q<VisualElement>("GameView");
var leaderboardView = UI.Q<VisualElement>("LeaderboardView");
var accountView = UI.Q<VisualElement>("AccountView");
switch (newDisplayState)
{
case DisplayState.Nothing:
gameView.style.display = DisplayStyle.None;
leaderboardView.style.display = DisplayStyle.None;
accountView.style.display = DisplayStyle.None;
break;
case DisplayState.PlayView:
gameView.style.display = DisplayStyle.Flex;
leaderboardView.style.display = DisplayStyle.None;
accountView.style.display = DisplayStyle.None;
break;
case DisplayState.LeaderboardView:
gameView.style.display = DisplayStyle.None;
leaderboardView.style.display = DisplayStyle.Flex;
accountView.style.display = DisplayStyle.None;
break;
case DisplayState.AccountView:
gameView.style.display = DisplayStyle.None;
leaderboardView.style.display = DisplayStyle.None;
accountView.style.display = DisplayStyle.Flex;
break;
case DisplayState.UnassociatedState:
default:
throw new ArgumentOutOfRangeException(nameof(newDisplayState), newDisplayState, null);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac64716d16f44bd596798277ddc5eaaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: -15
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,162 +1,92 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance"
engine="UnityEngine.UIElements" editor="UnityEditor.UIElements"
noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&amp;guid=2c7ff79f21a3e8e408e76d75944d575b&amp;type=3#GameUI"/>
<ui:VisualElement name="Root"
style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
<ui:VisualElement name="SideView"
style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
<ui:VisualElement name="Header"
style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="Name"
style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;"/>
<ui:Label tabindex="-1"
text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab perceptually uniform colour space."
parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description"
style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);"/>
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&amp;guid=2c7ff79f21a3e8e408e76d75944d575b&amp;type=3#GameUI" />
<ui:VisualElement name="Root" style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
<ui:VisualElement name="SideView" style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
<ui:VisualElement name="Header" style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Name" style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;" />
<ui:Label tabindex="-1" text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab perceptually uniform colour space." parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description" style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);" />
</ui:VisualElement>
<ui:VisualElement name="Content"
style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="PlayButton"/>
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="LeaderboardButton"/>
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="AccountButton"/>
<ui:VisualElement name="AccountSection"
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
<ui:VisualElement name="PlayerDetails"
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
<ui:VisualElement name="Content" style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayButton" />
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LeaderboardButton" />
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccountButton" />
<ui:VisualElement name="AccountSection" style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); padding-bottom: 12px;">
<ui:VisualElement name="PlayerDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
<ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;">
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PlayerHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;"/>
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PlayerText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;"/>
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;" />
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;" />
</ui:VisualElement>
<ui:VisualElement name="PlayerRatingDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="RatingHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;"/>
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="RatingText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;"/>
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;" />
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="AccuracyDetails"
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
<ui:VisualElement name="AccuracyDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
<ui:VisualElement name="LightnessAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Lightness&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="LightnessAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="LightnessAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;"/>
<ui:Label tabindex="-1" text="Lightness&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;" />
</ui:VisualElement>
<ui:VisualElement name="ChromaAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Chroma&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ChromaAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ChromaAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;"/>
<ui:Label tabindex="-1" text="Chroma&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;" />
</ui:VisualElement>
<ui:VisualElement name="HueAccuracyDetail" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Hue&#10;Accuracy" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="HueAccuracyHeader"
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;"/>
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="HueAccuracyText"
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;"/>
<ui:Label tabindex="-1" text="Hue&#10;Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;" />
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ConnectionStatusText" style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
<ui:Label tabindex="-1" text="Status: Unknown" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ConnectionStatusText" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="MainView"
style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
<ui:VisualElement name="GameView"
style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="GameHeader"
style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="RoundText" style="font-size: 58px; -unity-font-style: normal;"/>
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="TimeText"
style="-unity-text-align: lower-right; font-size: 58px;"/>
<ui:VisualElement name="MainView" style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
<ui:VisualElement name="GameView" style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch; display: none;">
<ui:VisualElement name="GameHeader" style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RoundText" style="font-size: 58px; -unity-font-style: normal;" />
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TimeText" style="-unity-text-align: lower-right; font-size: 58px;" />
</ui:VisualElement>
<ui:VisualElement name="ColourPreview"
style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
<ui:VisualElement name="TemplatePreview"
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="TemplateText"
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;"/>
<ui:VisualElement name="TemplateColour"
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
<ui:VisualElement name="ColourPreview" style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
<ui:VisualElement name="TemplatePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TemplateText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;" />
<ui:VisualElement name="TemplateColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
</ui:VisualElement>
<ui:VisualElement name="ResponsePreview"
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="ResponseText"
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;"/>
<ui:VisualElement name="ResponseColour"
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
<ui:VisualElement name="ResponsePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ResponseText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;" />
<ui:VisualElement name="ResponseColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="ResponseSliders" style="flex-grow: 0; display: flex;">
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider"/>
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider"/>
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider"/>
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider" />
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider" />
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="LeaderboardView"
style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:VisualElement name="LeaderboardHeader" style="flex-grow: 0;">
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="RoundText"
style="font-size: 58px; -unity-font-style: bold;"/>
</ui:VisualElement>
<ui:ListView name="LeaderboardListView"/>
<ui:VisualElement name="LeaderboardView" style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RoundText" style="font-size: 58px; -unity-font-style: normal;" />
<ui:ListView name="LeaderboardListView" />
</ui:VisualElement>
<ui:VisualElement name="AccountView"
style="flex-grow: 1; display: flex; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true"
display-tooltip-when-elided="true" name="AccountHeader"
style="font-size: 58px; -unity-font-style: normal;"/>
<ui:VisualElement name="AccountView" style="flex-grow: 1; display: flex; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccountHeader" style="font-size: 58px; -unity-font-style: normal;" />
<ui:VisualElement name="AccountFields" style="flex-grow: 0;">
<ui:VisualElement name="UsernameContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Username" name="UsernameField"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="UsernameUpdateButton"/>
<ui:VisualElement name="UsernameContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Username" name="UsernameField" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="UsernameUpdateButton" />
</ui:VisualElement>
<ui:VisualElement name="EmailContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Email" name="EmailField"
keyboard-type="EmailAddress"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="EmailUpdateButton"/>
<ui:VisualElement name="EmailContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Email" name="EmailField" keyboard-type="EmailAddress" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="EmailUpdateButton" />
</ui:VisualElement>
<ui:VisualElement name="PasswordContainer"
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Password" name="PasswordField" password="true"/>
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
name="PasswordUpdateButton"/>
<ui:VisualElement name="PasswordContainer" style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
<ui:TextField picking-mode="Ignore" label="Password" name="PasswordField" password="true" />
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PasswordUpdateButton" />
</ui:VisualElement>
<ui:Label tabindex="-1" text="A verification email has been sent. Check your inbox."
parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccompanyingText"
style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 1.5%; margin-right: 0; margin-bottom: 0.75%; margin-left: 0; -unity-text-align: upper-left; display: none;"/>
<ui:Label tabindex="-1" text="A verification email has been sent. Check your inbox." parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccompanyingText" style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 1.5%; margin-right: 0; margin-bottom: 0.75%; margin-left: 0; -unity-text-align: upper-left; display: none;" />
</ui:VisualElement>
<ui:VisualElement name="AccountButtons" style="flex-grow: 0; align-items: flex-start;">
<ui:Button text="Primary Action Button →" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="PrimaryActionButton"
style="-unity-text-align: middle-center; margin-bottom: 1%; margin-right: 1%; margin-top: 1%; -unity-font-style: bold;"/>
<ui:Button text="Secondary Action Button →" parse-escape-sequences="true"
display-tooltip-when-elided="true" name="SecondaryActionButton"
style="margin-top: 1%; margin-right: 1%; -unity-font-style: bold;"/>
<ui:Button text="Primary Action Button →" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PrimaryActionButton" style="-unity-text-align: middle-center; margin-bottom: 1%; margin-right: 1%; margin-top: 1%; -unity-font-style: bold;" />
<ui:Button text="Secondary Action Button →" parse-escape-sequences="true" display-tooltip-when-elided="true" name="SecondaryActionButton" style="margin-top: 1%; margin-right: 1%; -unity-font-style: bold;" />
</ui:VisualElement>
</ui:VisualElement>
</ui:VisualElement>