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"); } } } }