public void RegisterInteractiveItem(IInteractiveItem interactiveItem, GameObject interactiveObject) { if (!m_interactiveItems.ContainsKey(interactiveObject)) { m_interactiveItems[interactiveObject] = interactiveItem; } }
/// <summary> /// Vrátí true pro testovaný prvek Item, který vyhovuje filtraci: /// a) Pokud Item je null, vrátí false (null nebrat); /// b) Pokud includeItem není null, a Item je identický s includeItem, vrátí true (includeItem bereme vždy); /// c) Pokud chceme pouze viditelné, a prvek Item není viditelný, vrátí false; /// d) Pokud chceme pouze TabStop, a prvek Item není TabStop, vrátí false; /// Jinak vrátí true = prvek Item vyhovuje. /// </summary> /// <param name="item">Testovaný prvek</param> /// <param name="requirements">Požadavky na prohledávané prvky</param> /// <param name="includeItem">Prvek, který chceme do seznamu přidat bezpodmínečně (pokud v <see cref="IInteractiveParent.Childs"/> bude přítomen)</param> /// <returns></returns> private static bool IsItemValid(IInteractiveItem item, InteractiveFocusStateFlag requirements = InteractiveFocusStateFlag.Default, IInteractiveItem includeItem = null) { if (item == null) { return(false); } if (includeItem != null && Object.ReferenceEquals(item, includeItem)) { return(true); } if (requirements.HasFlag(InteractiveFocusStateFlag.Visible) && !item.Is.Visible) { return(false); // Pokud prvek musí být Visible, a tento není, pak vrátím false } if (requirements.HasFlag(InteractiveFocusStateFlag.Enabled) && !item.Is.Enabled) { return(false); // Pokud prvek musí být Enabled, a tento není, pak vrátím false } if (requirements.HasFlag(InteractiveFocusStateFlag.TabStop) && !item.Is.TabStop) { return(false); // Pokud prvek musí být TabStop, a tento není, pak vrátím false } if (!Settings.TabStopOnReadOnlyItems && item.Is.ReadOnly) { return(false); // Pokud Setting říká "TabStopOnReadOnlyItems = false", a tento prvek je ReadOnly, pak vrátím false } return(true); }
/// <summary> /// Vrátí absolutní souřadnice (AbsoluteBounds) viditelné části pro daný Child prvek. /// Souřadný systém prvku volí podle toho, zda prvek je umístěn ve fyzických nebo ve virtuálních souřadnicích. /// </summary> /// <param name="item">Prvek</param> /// <param name="layer">Vrstva, ovlivní výběr typu souřadnic</param> /// <returns></returns> protected Rectangle GetAbsoluteVisibleBounds(IInteractiveItem item, GInteractiveDrawLayer layer) { Coordinates parentCoordinates = (item.Is.OnPhysicalBounds ? this._PhysicalCoordinates : this._VirtualCoordinates); Rectangle bounds = GetItemBounds(item, layer); return(parentCoordinates.GetVisibleBounds(parentCoordinates.GetAbsoluteBounds(bounds))); }
void Update () { RaycastHit hit; //Mouse input //Ray camRay = Camera.main.ScreenPointToRay(Input.mousePosition); //VR Input Ray ray = new Ray(camTransform.position, camTransform.forward); // Perform the raycast and if it hits something on the floor layer... if (Physics.Raycast(ray, out hit, shootDistance, objectMask)) { if (hit.transform.GetComponent(typeof(IInteractiveItem)) as IInteractiveItem != null) { if ((hit.transform.GetComponent(typeof(IInteractiveItem)) as IInteractiveItem).Gazable) { if ((interactiveItem = hit.transform.GetComponent<IInteractiveItem>() as IInteractiveItem) != interactiveItem) slider.value = 0f; if (interactiveItem != null && !interactiveItem.Gazed) slider.value += Time.deltaTime / gazeDuration; if (slider.value >= 1f) { interactiveItem.OnGazed(); slider.value = 0f; } } } else { if (interactiveItem != null) { slider.value = 0f; interactiveItem.OnEndGazed(); interactiveItem = null; } } Debug.Log(hit.transform.gameObject.name); //slider.transform.position = new Vector3(0f, 0f, hit.distance); } else { slider.value = 0f; if (interactiveItem != null) { interactiveItem.OnEndGazed(); interactiveItem = null; } //slider.transform.position = new Vector3(0f, 0f, shootDistance); //Debug.Log("Gazing nothing."); } }
/// <summary> /// Metoda vrátí true, pokud daný prvek je aktuálně Selected; výhradně stabilní Select, nezohledňuje Framed. /// </summary> /// <param name="item"></param> /// <returns></returns> public bool IsSelected(IInteractiveItem item) { if (item == null) { return(false); } return(this._Selected.ContainsKey(item.Id)); }
/// <summary> /// Vrací souřadnice daného prvku <paramref name="item"/> pro danou vrstvu <paramref name="layer"/>. /// Pro vrstvu <see cref="GInteractiveDrawLayer.Interactive"/> vrací interaktivní souřadnice <see cref="IInteractiveItem.BoundsInteractive"/>, pokud jsou zadány. /// Standardně vrací <see cref="IInteractiveItem.Bounds"/>. /// </summary> /// <param name="item"></param> /// <param name="layer"></param> /// <returns></returns> protected Rectangle GetItemBounds(IInteractiveItem item, GInteractiveDrawLayer layer) { if (item == null) { return(Rectangle.Empty); } if (layer == GInteractiveDrawLayer.Interactive && item.BoundsInteractive.HasValue) { return(item.BoundsInteractive.Value); } return(item.Bounds); }
/// <summary> /// Metoda nastaví IsActivated pro daný prvek na danou hodnotu. /// Tato metoda nemění stav <see cref="IInteractiveItem.IsActivated"/> pro ostatní aktuálně aktivní prvky. /// </summary> /// <param name="item"></param> /// <param name="isActivated"></param> public void SetActivated(IInteractiveItem item, bool isActivated) { if (item == null) { return; } uint id = item.Id; bool oldActivated = this._Activated.ContainsKey(id); if (isActivated != oldActivated) { item.IsActivated = isActivated; // Položka sama ve své property IsActivated by měla zavolat: ((ISelectorInternal)host.Selector).SetActivatedValue(this, value); } }
/// <summary> /// Metoda zajistí, že daný prvek změní svůj stav Selected (výhradně stabilní Select): pokud nyní je vybraný, pak nebude; a naopak. /// Druhý parametr říká, zda ostatní dosud selectované prvky mají být zapomenuty (leaveOther je false) anebo ponechány (leaveOther je true). /// Standardně se tato metoda volá po kliknutí levou myší na prvek, /// a parametr leaveOther se odvozuje od stisknuté klávesy Ctrl: stisknutá klávesa =» leaveOther = true =» ponechat ostatní prvky beze změn. /// Tato metoda nijak nepracuje s příznaky Framed. /// </summary> /// <param name="item"></param> /// <param name="leaveOther"></param> public void ChangeSelection(IInteractiveItem item, bool leaveOther) { if (item == null) { return; } uint id = item.Id; bool isSelected = this._Selected.ContainsKey(id); // Aktuální hodnota daného prvku; na konci metody do něj vložíme hodnotu opačnou (Changed) // Pokud nemáme nechat ostatní prvky selectované, a nějaké existují, pak musíme všechny prvky odselectovat: if (!leaveOther && this._Selected.Count > 0) { this.ClearSelected(); } item.IsSelected = !isSelected; // Položka sama ve své property IsSelected by měla zavolat: ((ISelectorInternal)host.Selector).SetSelectedValue(this, value); }
/// <summary> /// Vrací <see cref="BoundsInfo"/> pro daného parenta a danou vrstvu souřadnic /// </summary> /// <param name="forItem"></param> /// <param name="asContainer"></param> /// <param name="layer">Vrstva, jejíž souřadnice řešíme. Každý prvek může mít souřadnice různé podle toho, o kterou vrstvu se jedná. /// To je důsledek procesu Drag and Drop, kdy ve standardní vrstvě se prvek nachází na výchozích souřadnicích Bounds, /// ale ve vrstvě <see cref="GInteractiveDrawLayer.Interactive"/> je na souřadnicích Drag = <see cref="IInteractiveItem.BoundsInteractive"/>.</param> /// <returns></returns> private static BoundsInfo _CreateForItem(IInteractiveParent forItem, bool asContainer, GInteractiveDrawLayer layer) { // Nejprve si nastřádám řadu prvků počínaje daným prvkem/nebo jeho parentem, přes jeho Parenty, až k nejspodnějšímu prvku (který nemá parenta): List <IInteractiveParent> parents = new List <IInteractiveParent>(); Dictionary <uint, object> scanned = new Dictionary <uint, object>(); IInteractiveParent parent = (asContainer ? forItem : forItem.Parent); while (parent != null) { if (scanned.ContainsKey(parent.Id)) { break; // Zacyklení! } parents.Add(parent); parent = parent.Parent; // Krok na dalšího parenta } // Pak vytvořím postupně řadu BoundsInfo, počínaje od posledního v poli parents (=fyzický Control): int last = parents.Count - 1; // Pokud na vstupu byl předán container = fyzický Control, pak pole má 0 prvků a výstupní BoundsInfo je vytvořeno pro daný Control! if (last < 0) { return(BoundsInfo.CreateForParent(forItem)); } // Poslední prvek pole = parents[last] = fyzický Control: BoundsInfo boundsInfo = BoundsInfo.CreateForParent(parents[last], layer); for (int i = last - 1; i >= 0; i--) { IInteractiveItem it = parents[i] as IInteractiveItem; if (it == null) { continue; } boundsInfo = boundsInfo.GetChildsBoundsInfo(it, layer); } // Na závěr do výsledného boundsInfo vepíšu dodaný Child prvek (forItem) jako Current prvek: if (!asContainer && forItem is IInteractiveItem) { boundsInfo.CurrentItem = forItem as IInteractiveItem; } return(boundsInfo); }
/// <summary> /// Metoda zkusí najít prvek následující za daným prvkem, do kterého má z něj přejít focus v daném směru /// </summary> /// <param name="currentItem">Výchozí prvek pro hledání. Tento prvek nedostane focus, dostane jej některý z jeho sousedů (=sousední prvky Childs od Parenta tohoto daného prvku).</param> /// <param name="direction">Směr kroku: <see cref="Direction.Positive"/> = "doprava" = na následující prvek (klávesou Tab); <see cref="Direction.Negative"/> = "doleva" = na předešlý prvek (klávesou Ctrl+Tab) </param> /// <param name="nextItem">Out nalezený sousední prvek</param> /// <param name="requirements">Požadavky na prohledávané prvky</param> /// <returns>true = nalezeno / false = nenalezeno</returns> internal static bool TryGetNextFocusItem(IInteractiveItem currentItem, Direction direction, out IInteractiveItem nextItem, InteractiveFocusStateFlag requirements = InteractiveFocusStateFlag.Default) { nextItem = null; if (currentItem == null) { return(false); // Nezadán prvek } if (!(direction == Direction.Positive || direction == Direction.Negative)) { return(false); // Nezadán platný směr } List <IInteractiveItem> childList = GetChildsSorted(currentItem.Parent, direction, requirements, currentItem); if (childList == null) { return(false); // Jeho Parent neobsahuje žádné prvky } int index = childList.FindIndex(i => Object.ReferenceEquals(currentItem, i)); if (index < 0) { return(false); // Jeho Parent neobsahuje zadaný prvek (???) } // Nyní projdu sousední prvky vstupního prvku, za ním/před ním ve správném pořadí (seznam je setříděn Positive = ASC / Negative = DESC): int count = childList.Count; for (int i = (index + 1); i < count; i++) { // Pokud daný sousední prvek je sám vhodný, anebo ve svých Childs obsahuje vhodný prvek, pak jej dáme do out nextItem a vrátíme true: if (TryGetOuterFocusItem(childList[i], direction, out nextItem, requirements)) { return(true); } } // V mé úrovni ani v mých Child prvcích jsme nenašli vhodný prvek. Zpracujeme obdobně vyšší úroveň (=sousedy mého parenta): if (!(currentItem.Parent is IInteractiveItem)) { return(false); // Prvek nemá interaktivního Parenta = náš parent je fyzický Control. } return(TryGetNextFocusItem((currentItem.Parent as IInteractiveItem), direction, out nextItem, requirements)); }
/// <summary> /// Vlastní výkonná metoda, volá se výhradně z <see cref="IInteractiveItem.IsActivated"/>.set{} /// </summary> /// <param name="item"></param> /// <param name="isActivated"></param> void ISelectorInternal.SetActivatedValue(IInteractiveItem item, bool isActivated) { if (item == null) { return; } uint id = item.Id; bool oldActivated = this._Activated.ContainsKey(id); if (isActivated != oldActivated) { if (isActivated && !oldActivated) { this._Activated.Add(id, item); } else if (!isActivated && oldActivated) { this._Activated.Remove(id); } } }
/// <summary> /// Vrátí novou instanci <see cref="BoundsInfo"/> pro daný Child objekt, který od té chvíle bude Parent objektem pro další, v něm vnořené prvky. /// Respektuje přitom, že daný Child objekt se může pohybovat ve virtuálních anebo ve fyzických souřadnicích aktuálního systému. /// </summary> /// <param name="item">Prvek</param> /// <param name="layer">Vrstva, ovlivní výběr typu souřadnic</param> /// <returns></returns> protected BoundsInfo GetChildsBoundsInfo(IInteractiveItem item, GInteractiveDrawLayer layer) { // Child prvek je umístěn ve svých souřadnicích Bounds, a to buď ve fyzickém nebo ve virtuálním prostoru (koordinátech): Coordinates parentCoordinates = (item.Is.OnPhysicalBounds ? this._PhysicalCoordinates : this._VirtualCoordinates); // Určíme jeho absolutní souřadnice = jeho Bounds posunuté do odpovídajících (fyzické/virtuální) koordinátů: Rectangle bounds = GetItemBounds(item, layer); Rectangle absoluteBounds = parentCoordinates.GetAbsoluteBounds(bounds); // Prvek může zmenšit tento prostor o své vnitřní okraje. // Prostor okrajů patří do prvku (=prvek sám si je dokáže vykreslit), ale nepatří do prostoru, který prvek poskytuje svým child prvkům: // Toto jsou tedy absolutní souřadnice prostoru, ve kterém budou zobrazovány Child prvky: Rectangle clientBounds = absoluteBounds.Sub(item.ClientBorder); // Určíme viditelnou oblast (průsečík z prostoru pro childs s prostorem dosud viditelné oblasti): Rectangle visibleBounds = parentCoordinates.GetVisibleBounds(clientBounds); // V rámci těchto souřadnic prvek item může poskytovat svůj souřadný systém standardní (fyzický) anebo i virtuální: Coordinates physicalCoordinates = Coordinates.FromOrigin(clientBounds.Location, visibleBounds); Coordinates virtualCoordinates = physicalCoordinates; if (item is IAutoScrollContainer) { // Child prvek (item) je AutoScrollContainer: IAutoScrollContainer autoScrollContainer = item as IAutoScrollContainer; if (autoScrollContainer.AutoScrollActive) { Point virtualOrigin = parentCoordinates.GetAbsolutePoint(autoScrollContainer.VirtualOrigin); Rectangle virtualBounds = parentCoordinates.GetVisibleBounds(parentCoordinates.GetAbsoluteBounds(autoScrollContainer.VirtualBounds)); virtualCoordinates = Coordinates.FromOrigin(virtualOrigin, virtualBounds); } } bool isVisible = this.IsVisible && item.Is.Visible; bool isEnabled = this.IsEnabled && item.Is.Enabled; return(new BoundsInfo(physicalCoordinates, virtualCoordinates, isVisible, isEnabled, layer)); }
/// <summary> /// Metoda zkusí najít první konkrétní prvek v rámci daného interaktivního parenta, do kterého lze umístit klávesový Focus. /// Pokud dodaný parent sám je interaktivní prvek <see cref="IInteractiveItem"/>, a pokud vyhovuje podmínkám Visible a TabStop, a podporuje KeyboardInput, pak je vrácen přímo tento prvek. /// Pokud není, nebo pokud nevyhovuje, pak se prověří jeho vlastní Childs prvky, a rekurzivně i jejich vnořené Childs prvky, a vyhledá se první vyhovující prvek. /// Pokud takový neexistuje, je vráceno false. /// </summary> /// <param name="parent">Vstupující prvek. On sám může být tím, kdo dostane focus, anebo některý z jeho Childs prvků (rekurzivně). /// Vstupující prvek může být celý Control (=Host), nebo jakýkoli container nebo konkrétní prvek.</param> /// <param name="direction">Směr hledání: <see cref="Direction.Positive"/>: hledá první vhodný prvek (od začátku); <see cref="Direction.Negative"/>: hledá poslední vhodný prvek (od konce)</param> /// <param name="foundItem">Out: nalezený prvek (this nebo některý z this.Childs[.Childs[...]])</param> /// <param name="requirements">Požadavky na prohledávané prvky</param> /// <returns>true = nalezeno / false = nenalezeno</returns> internal static bool TryGetOuterFocusItem(IInteractiveParent parent, Direction direction, out IInteractiveItem foundItem, InteractiveFocusStateFlag requirements = InteractiveFocusStateFlag.Default) { foundItem = null; if (parent == null) { return(false); // Null nebrat } // Pokud sám daný prvek je interaktivní, a pokud je vyhovující, pak jej akceptujeme: if (parent is IInteractiveItem) { IInteractiveItem item = parent as IInteractiveItem; if (IsItemValid(item, requirements) && item.Is.KeyboardInput) { foundItem = item; return(true); } } // Získáme seznam Childs prvky daného prvku, ve správném pořadí, a najdeme první vhodný prvek = cyklem (skrz Childs) a rekurzí (do téže metody): List <IInteractiveItem> childList = GetChildsSorted(parent, direction, requirements, null); if (childList == null) { return(false); } foreach (IInteractiveItem childItem in childList) { if (TryGetOuterFocusItem(childItem, direction, out foundItem, requirements)) { return(true); // childItem je IInteractiveItem, a protože IInteractiveItem je potomkem IInteractiveParent, vyvoláme přímou rekurzi... } } return(false); }
/// <summary> /// Add one interactive item. /// Does not trigger Draw(). /// </summary> /// <param name="item"></param> public virtual void AddItem(IInteractiveItem item) { this.ChildList.Add(item); }
/// <summary> /// Odebere daný Child prvek /// </summary> /// <param name="item"></param> public virtual void RemoveItem(IInteractiveItem item) { this.ChildList.Remove(item); }
/// <summary> /// Pro daný prvek (který je na pozici Child) určí a vrátí souřadný systém, v kterém se pohybuje. /// Jde tedy o systém, v němž se daný prvek pohybuje, nikoli o systém který poskytuje svým Childs prvkům. /// Daný prvek bude umístěn do property <see cref="BoundsInfo.CurrentItem"/>, a v ostatních properties vráceného systému budou k dispozici jeho jednotlivé koordináty /// (např. <see cref="BoundsInfo.CurrentItemAbsoluteBounds"/> bude obsahovat absolutní souřadnice daného prvku). /// </summary> /// <param name="currentItem"></param> /// <returns></returns> public static BoundsInfo CreateForChild(IInteractiveItem currentItem) { CheckItem(currentItem, "CreateForChild"); return(_CreateForItem(currentItem, false, GInteractiveDrawLayer.Standard)); }
/// <summary> /// Metoda získá a vrátí seznam Child prvků daného prvku <paramref name="parentItem"/>. /// Seznam setřídí podle <see cref="IInteractiveItem.TabOrder"/> vzestupně (pro <paramref name="direction"/> == <see cref="Direction.Positive"/>) /// nebo sestupně (pro <paramref name="direction"/> == <see cref="Direction.Negative"/>). /// <para/> /// Seznam obsahuje pouze ty prvky, které jsou viditelné, které mají Enabled = true a které mají TabStop = true, vše podle požadavků v parametru <paramref name="requirements"/>. /// Seznam může volitelně obsahovat i explicitně zadaný Child prvek <paramref name="includeItem"/> bez ohledu na jeho Visible a TabStop (tedy pokud tento prvek je obsažen v seznamu Childs). /// Tato vlastnost slouží k zařazení určitého prvku pro hledání jeho Next prvků. /// <para/> /// Seznam obsahuje prvky bez ohledu na jejich vlastnost KeyboardInput, protože tuto vlastnost mohou mít až jejich vnořené Child prvky, a my chceme najít i nadřízené Containery. /// <para/> /// Pokud je na vstupu objekt <paramref name="parentItem"/>= null, nebo jeho <see cref="IInteractiveParent.Childs"/> je null, pak výstupem metody je null (nebudeme generovat new empty List). /// Dále pak proto, že vstupní objekt i jeho Childs připouštíme, že smí být null, ale nechceme nutit volajícího aby si to sám testoval, /// protože získání Childs může být výkonnostně náročnější = získáme to jen jednou a vyhodnotíme zde, volající ať si otestuje vrácený objekt, to je nenáročné. /// </summary> /// <param name="parentItem">Parent, jehož <see cref="IInteractiveParent.Childs"/> prvky budeme zpracovávat</param> /// <param name="direction">Směr třídění: <see cref="Direction.Positive"/> = podle TabOrder vzestupně; <see cref="Direction.Negative"/> = podle TabOrder sestupně</param> /// <param name="requirements">Požadavky na prohledávané prvky</param> /// <param name="includeItem">Prvek, který chceme do seznamu přidat bezpodmínečně (pokud v <see cref="IInteractiveParent.Childs"/> bude přítomen)</param> /// <returns></returns> private static List <IInteractiveItem> GetChildsSorted(IInteractiveParent parentItem, Direction direction, InteractiveFocusStateFlag requirements = InteractiveFocusStateFlag.Default, IInteractiveItem includeItem = null) { if (parentItem == null) { return(null); } var childs = parentItem.Childs; if (childs == null) { return(null); } List <IInteractiveItem> childList = childs.Where(i => IsItemValid(i, requirements, includeItem)).ToList(); if (childList.Count > 1) { switch (direction) { case Direction.Positive: childList.Sort(InteractiveObject.CompareByTabOrderAsc); break; case Direction.Negative: childList.Sort(InteractiveObject.CompareByTabOrderDesc); break; } } return(childList); }
/// <summary> /// Metoda vrátí absolutní souřadnice daného objektu. /// </summary> /// <param name="item"></param> /// <returns></returns> public static Rectangle GetAbsoluteBounds(IInteractiveItem item) { BoundsInfo boundsInfo = BoundsInfo.CreateForChild(item); return(boundsInfo.CurrentItemAbsoluteBounds); }