using System;
using System.Collections.ObjectModel;
namespace MonoGame.Extended.NuclexGui.Controls
{
/// Collection of GUI controls
///
/// This class is for internal use only. Do not expose it to the user. If it was
/// exposed, the user might decide to use it for storing his own controls, causing
/// exceptions because the collection tries to parent the controls which are already
/// belonging to another collection.
///
internal class ParentingControlCollection : Collection
{
/// Parent control to assign to all controls in this collection.
private readonly GuiControl _parent;
/// GUI this control is currently assigned to. Can be null.
private GuiScreen _screen;
/// Initializes a new parenting control collection.
/// Parent control to assign to all children.
public ParentingControlCollection(GuiControl parent)
{
_parent = parent;
}
/// Clears all elements from the collection
protected override void ClearItems()
{
for (var index = 0; index < Count; ++index)
UnassignParent(base[index]);
base.ClearItems();
}
/// Inserts a new element into the collection
/// Index at which to insert the element
/// Item to be inserted
protected override void InsertItem(int index, GuiControl item)
{
EnsureIntegrity(item);
base.InsertItem(index, item);
AssignParent(item);
}
/// Removes an element from the collection
/// Index of the element to remove
protected override void RemoveItem(int index)
{
UnassignParent(base[index]);
base.RemoveItem(index);
}
/// Takes over a new element that is directly assigned
/// Index of the element that was assigned
/// New item
protected override void SetItem(int index, GuiControl item)
{
EnsureIntegrity(item);
UnassignParent(base[index]);
AssignParent(item);
}
/// Switches the control to a specific GUI
/// Screen that owns the control from now on
internal void SetScreen(GuiScreen screen)
{
_screen = screen;
for (var index = 0; index < Count; ++index)
base[index].SetScreen(screen);
}
/// Moves the specified control to the start of the list.
/// Index of the control that will be moved to the start of the list.
internal void MoveToStart(int controlIndex)
{
var control = base[controlIndex];
// We explicitely circumvent the additional logic for adding and removing items
// in this collection since we're only relocating an item. Removal and readdition
// have no risk of causing an exception in a normal collection, otherwise the
// rollback attempt would be futile anyway since it would mean to repeat exactly
// what has caused failed: adding an item ;)
RemoveAt(controlIndex);
Insert(0, control);
}
/// Checks whether the provided name is already taken by a control.
/// Id that will be checked.
/// True if the id is already taken; otherwise false.
internal bool IsNameTaken(string name)
{
// Empty names are an exception and will not be checked for duplicates.
if (name == null)
return false;
// Look for any controls with the provided name. This is a stupid sequential
// search, but given the typical number of controls in a Gui and the fact
// that this operation usually only happens once, there's no point in adding
// the overhead of managing a synchronized look-up dictionary here.
for (var index = 0; index < Count; ++index)
{
if (base[index].Name == name)
return true;
}
// If we reach this point, no control is using the specified name.
return false;
}
/// Gives up the parentage on the item provided.
/// Item to be unparented.
private void AssignParent(GuiControl item)
{
item.SetParent(_parent);
}
/// Sets up the parentage on the specified item.
/// Item to be parented.
private void UnassignParent(GuiControl item)
{
item.SetParent(null);
}
/// Determines whether the provided control is a parent of this control.
/// Control to check for parentage.
/// True if the control is one of our parents; otherwise false.
/// This method takes into account all ancestors up to the tree's root.
private bool IsParent(GuiControl control)
{
var parent = _parent;
while (parent != null)
{
// Check if one of parents is control we are looking for
if (ReferenceEquals(parent, control))
return true;
// Walk upwards in the tree
parent = parent.Parent;
}
// Control is not in the tree
return false;
}
/// Ensures the integrity of the parent/child relationships.
/// Control that is to become one of our childs.
private void EnsureIntegrity(GuiControl proposedChild)
{
// The item must not have a parent (otherwise, by being added to this collection,
// it would either be contained twice in the same collection or have two parents).
if (!ReferenceEquals(proposedChild.Parent, null))
throw new InvalidOperationException("Control already is the child of another control");
// The item must not become its own parent. I cannot imagine this ever happenning
// unless someone deliberately tried to crash the GUI library :)
if (ReferenceEquals(_parent, proposedChild))
throw new InvalidOperationException("Attempt to instate control as its own parent");
// The item also must not be any of our parent's parents (and so on). Otherwise,
// a stack overflow is likely to occur.
if (IsParent(proposedChild))
throw new InvalidOperationException("Attempt to instate one of the control's parents as its child");
// We also do not allow a child control to have the same id as an existing
// control (with the exception of an empty name)
if (IsNameTaken(proposedChild.Name))
{
throw new InvalidOperationException(
"The name of the added control has already been taken by another child");
}
}
}
}