// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.ObjectModel;
using UnityEngine;
using UnityEngine.Assertions;
namespace Microsoft.MixedReality.Toolkit.Experimental.UI
{
///
/// This control enables moving objects in and out of predetermined positions,
/// to create palettes, shelves and navigation bars.
///
///
///
[AddComponentMenu("Scripts/MRTK/Experimental/Dock/Dock")]
public class Dock : MonoBehaviour
{
///
/// A read-only list of possible positions in this dock.
///
[Experimental]
[SerializeField]
[Tooltip("A read-only list of possible positions in this dock.")]
private ReadOnlyCollection dockPositions;
///
/// A read-only list of possible positions in this dock.
///
public ReadOnlyCollection DockPositions => dockPositions;
///
/// Initializes the list of positions in this dock.
///
private void OnEnable()
{
UpdatePositions();
}
///
/// Updates the list of positions in this dock when its children change.
///
private void OnTransformChildrenChanged()
{
UpdatePositions();
}
///
/// Updates the list of positions in this dock.
///
private void UpdatePositions()
{
dockPositions = gameObject.GetComponentsInChildren().ToReadOnlyCollection();
}
///
/// Moves elements near the desired position to make space for a new element,
/// if possible.
///
/// The desired position where an object wants to be docked.
/// Returns true if the desired position is now available, false otherwise.
public bool TryMoveToFreeSpace(DockPosition position)
{
if (dockPositions == null)
{
UpdatePositions();
}
if (!dockPositions.Contains(position))
{
Debug.LogError("Looking for a DockPosition in the wrong Dock.");
return false;
}
var index = dockPositions.IndexOf(position);
if (!dockPositions[index].IsOccupied)
{
// Already free
return true;
}
// Where is the closest free space? (on a tie, favor left)
int? closestFreeSpace = null;
int distanceToClosestFreeSpace = int.MaxValue;
for (int i = 0; i < dockPositions.Count; i++)
{
var distance = Math.Abs(index - i);
if (!dockPositions[i].IsOccupied && distance < distanceToClosestFreeSpace)
{
closestFreeSpace = i;
distanceToClosestFreeSpace = distance;
}
}
if (closestFreeSpace == null)
{
// No free space
return false;
}
if (closestFreeSpace < index)
{
// Move left
// Check if we can undock all of them
for (int i = closestFreeSpace.Value + 1; i <= index; i++)
{
if (!dockPositions[i].DockedObject.CanUndock)
{
return false;
}
}
for (int i = closestFreeSpace.Value + 1; i <= index; i++)
{
MoveDockedObject(i, i - 1);
}
}
else
{
// Move right
// Check if we can undock all of them
for (int i = closestFreeSpace.Value - 1; i >= index; i--)
{
if (!dockPositions[i].DockedObject.CanUndock)
{
return false;
}
}
for (int i = closestFreeSpace.Value - 1; i >= index; i--)
{
MoveDockedObject(i, i + 1);
}
}
return true;
}
///
/// Moves a docked object from a position to another, by undocking it
/// and docking it in the new position.
///
/// The position we're moving the object from.
/// The position we're moving the object to.
private void MoveDockedObject(int from, int to)
{
var objectToMove = dockPositions[from].DockedObject;
objectToMove.Undock();
objectToMove.Dock(dockPositions[to]);
Assert.AreEqual(dockPositions[to].DockedObject, objectToMove, "The object we just moved needs to match the object docked in the new position.");
}
}
}