using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using System.Collections; #if UNITY_EDITOR using UnityEditor; #endif namespace DuloGames.UI { [AddComponentMenu("UI/Icon Slots/Base Slot"), ExecuteInEditMode, DisallowMultipleComponent] public class UISlotBase : UIBehaviour, IEventSystemHandler, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler { public enum Transition { None, ColorTint, SpriteSwap, Animation } public enum DragKeyModifier { None, Control, Alt, Shift } /// /// The current dragged object. /// protected GameObject m_CurrentDraggedObject; /// /// The current dragging plane. /// protected RectTransform m_CurrentDraggingPlane; /// /// The target icon graphic. /// public Graphic iconGraphic; [SerializeField, Tooltip("The game object that should be cloned on drag.")] private GameObject m_CloneTarget; [SerializeField, Tooltip("Should the drag and drop functionallty be enabled.")] private bool m_DragAndDropEnabled = true; [SerializeField, Tooltip("If set to static the slot won't be unassigned when drag and drop is preformed.")] private bool m_IsStatic = false; [SerializeField, Tooltip("Should the icon assigned to the slot be throwable.")] private bool m_AllowThrowAway = true; [SerializeField, Tooltip("The key which should be held down in order to begin the drag.")] private DragKeyModifier m_DragKeyModifier = DragKeyModifier.None; [SerializeField, Tooltip("Should the tooltip functionallty be enabled.")] private bool m_TooltipEnabled = true; [SerializeField, Tooltip("How long of a delay to expect before showing the tooltip.")] private float m_TooltipDelay = 1f; public Transition hoverTransition = Transition.None; public Graphic hoverTargetGraphic; public Color hoverNormalColor = Color.white; public Color hoverHighlightColor = Color.white; public float hoverTransitionDuration = 0.15f; public Sprite hoverOverrideSprite; public string hoverNormalTrigger = "Normal"; public string hoverHighlightTrigger = "Highlighted"; public Transition pressTransition = Transition.None; public Graphic pressTargetGraphic; public Color pressNormalColor = Color.white; public Color pressPressColor = new Color(0.6117f, 0.6117f, 0.6117f, 1f); public float pressTransitionDuration = 0.15f; public Sprite pressOverrideSprite; public string pressNormalTrigger = "Normal"; public string pressPressTrigger = "Pressed"; [SerializeField, Tooltip("Should the pressed state transition to normal state instantly.")] private bool m_PressTransitionInstaOut = true; [SerializeField, Tooltip("Should the pressed state force normal state transition on the hover target.")] private bool m_PressForceHoverNormal = true; private bool isPointerDown = false; private bool isPointerInside = false; private bool m_DragHasBegan = false; private bool m_DropPreformed = false; private bool m_IsTooltipShown = false; /// /// Gets or sets a value indicating whether this drag and drop is enabled. /// /// true if drag and drop enabled; otherwise, false. public bool dragAndDropEnabled { get { return this.m_DragAndDropEnabled; } set { this.m_DragAndDropEnabled = value; } } /// /// Gets or sets a value indicating whether this is static. /// /// true if is static; otherwise, false. public bool isStatic { get { return this.m_IsStatic; } set { this.m_IsStatic = value; } } /// /// Gets or sets a value indicating whether this can be throw away. /// /// true if allow throw away; otherwise, false. public bool allowThrowAway { get { return this.m_AllowThrowAway; } set { this.m_AllowThrowAway = value; } } /// /// Gets or sets the drag key modifier. /// /// The drag key modifier. public DragKeyModifier dragKeyModifier { get { return this.m_DragKeyModifier; } set { this.m_DragKeyModifier = value; } } /// /// Gets or sets a value indicating whether this tooltip should be enabled. /// /// true if tooltip enabled; otherwise, false. public bool tooltipEnabled { get { return this.m_TooltipEnabled; } set { this.m_TooltipEnabled = value; } } /// /// Gets or sets the tooltip delay. /// /// The tooltip delay. public float tooltipDelay { get { return this.m_TooltipDelay; } set { this.m_TooltipDelay = value; } } /// /// Gets or sets a value indicating whether this pressed state should transition out instantly. /// /// true if press transition insta out; otherwise, false. public bool pressTransitionInstaOut { get { return this.m_PressTransitionInstaOut; } set { this.m_PressTransitionInstaOut = value; } } /// /// Gets or sets a value indicating whether this pressed state should force normal state transition on the hover target. /// /// true if press force hover normal; otherwise, false. public bool pressForceHoverNormal { get { return this.m_PressForceHoverNormal; } set { this.m_PressForceHoverNormal = value; } } /// /// Gets or sets a value indicating whether this drop was preformed. /// /// true if drop preformed; otherwise, false. public bool dropPreformed { get { return this.m_DropPreformed; } set { this.m_DropPreformed = value; } } protected override void Start() { // Check if the slot is not assigned but the icon graphic is active if (!this.IsAssigned() && this.iconGraphic != null && this.iconGraphic.gameObject.activeSelf) { // Disable the icon graphic object this.iconGraphic.gameObject.SetActive(false); } } protected override void OnEnable() { base.OnEnable(); // Instant transition this.EvaluateAndTransitionHoveredState(true); this.EvaluateAndTransitionPressedState(true); } protected override void OnDisable() { base.OnDisable(); this.isPointerInside = false; this.isPointerDown = false; // Instant transition this.EvaluateAndTransitionHoveredState(true); this.EvaluateAndTransitionPressedState(true); } #if UNITY_EDITOR protected override void OnValidate() { this.hoverTransitionDuration = Mathf.Max(this.hoverTransitionDuration, 0f); this.pressTransitionDuration = Mathf.Max(this.pressTransitionDuration, 0f); if (this.isActiveAndEnabled) { this.DoSpriteSwap(this.hoverTargetGraphic, null); this.DoSpriteSwap(this.pressTargetGraphic, null); if (!EditorApplication.isPlayingOrWillChangePlaymode) { // Instant transition this.EvaluateAndTransitionHoveredState(true); this.EvaluateAndTransitionPressedState(true); } else { // Regular transition this.EvaluateAndTransitionHoveredState(false); this.EvaluateAndTransitionPressedState(false); } } } #endif /// /// Raises the pointer enter event. /// /// Event data. public virtual void OnPointerEnter(PointerEventData eventData) { this.isPointerInside = true; this.EvaluateAndTransitionHoveredState(false); // Check if tooltip is enabled if (this.enabled && this.IsActive() && this.m_TooltipEnabled) { // Start the tooltip delayed show coroutine // If delay is set at all if (this.m_TooltipDelay > 0f) { this.StartCoroutine("TooltipDelayedShow"); } else { this.InternalShowTooltip(); } } } /// /// Raises the pointer exit event. /// /// Event data. public virtual void OnPointerExit(PointerEventData eventData) { this.isPointerInside = false; this.EvaluateAndTransitionHoveredState(false); this.InternalHideTooltip(); } /// /// Raises the tooltip event. /// /// If set to true show. public virtual void OnTooltip(bool show) { } /// /// Raises the pointer down event. /// /// Event data. public virtual void OnPointerDown(PointerEventData eventData) { this.isPointerDown = true; this.EvaluateAndTransitionPressedState(false); // Hide the tooltip this.InternalHideTooltip(); } /// /// Raises the pointer up event. /// /// Event data. public virtual void OnPointerUp(PointerEventData eventData) { this.isPointerDown = false; this.EvaluateAndTransitionPressedState(this.m_PressTransitionInstaOut); } /// /// Raises the pointer click event. /// /// Event data. public virtual void OnPointerClick(PointerEventData eventData) { } /// /// Determines whether this slot is highlighted based on the specified eventData. /// /// true if this instance is highlighted the specified eventData; otherwise, false. /// Event data. protected bool IsHighlighted(BaseEventData eventData) { if (!this.IsActive()) return false; if (eventData is PointerEventData) { PointerEventData pointerEventData = eventData as PointerEventData; return ((this.isPointerDown && !this.isPointerInside && pointerEventData.pointerPress == base.gameObject) || (!this.isPointerDown && this.isPointerInside && pointerEventData.pointerPress == base.gameObject) || (!this.isPointerDown && this.isPointerInside && pointerEventData.pointerPress == null)); } return false; } /// /// Determines whether this slot is pressed based on the specified eventData. /// /// true if this instance is pressed the specified eventData; otherwise, false. /// Event data. protected bool IsPressed(BaseEventData eventData) { return this.IsActive() && this.isPointerInside && this.isPointerDown; } /// /// Evaluates and transitions the hovered state. /// /// If set to true instant. protected virtual void EvaluateAndTransitionHoveredState(bool instant) { if (!this.IsActive() || this.hoverTargetGraphic == null || !this.hoverTargetGraphic.gameObject.activeInHierarchy) return; // Determine what should the state of the hover target be bool highlighted = (this.m_PressForceHoverNormal ? (this.isPointerInside && !this.isPointerDown) : this.isPointerInside); // Do the transition switch (this.hoverTransition) { case Transition.ColorTint: { this.StartColorTween(this.hoverTargetGraphic, (highlighted ? this.hoverHighlightColor : this.hoverNormalColor), (instant ? 0f : this.hoverTransitionDuration)); break; } case Transition.SpriteSwap: { this.DoSpriteSwap(this.hoverTargetGraphic, (highlighted ? this.hoverOverrideSprite : null)); break; } case Transition.Animation: { this.TriggerHoverStateAnimation(highlighted ? this.hoverHighlightTrigger : this.hoverNormalTrigger); break; } } } /// /// Evaluates and transitions the pressed state. /// /// If set to true instant. protected virtual void EvaluateAndTransitionPressedState(bool instant) { if (!this.IsActive() || this.pressTargetGraphic == null || !this.pressTargetGraphic.gameObject.activeInHierarchy) return; // Do the transition switch (this.pressTransition) { case Transition.ColorTint: { this.StartColorTween(this.pressTargetGraphic, (this.isPointerDown ? this.pressPressColor : this.pressNormalColor), (instant ? 0f : this.pressTransitionDuration)); break; } case Transition.SpriteSwap: { this.DoSpriteSwap(this.pressTargetGraphic, (this.isPointerDown ? this.pressOverrideSprite : null)); break; } case Transition.Animation: { this.TriggerPressStateAnimation(this.isPointerDown ? this.pressPressTrigger : this.pressNormalTrigger); break; } } // If we should force normal state transition on the hover target if (this.m_PressForceHoverNormal) this.EvaluateAndTransitionHoveredState(false); } /// /// Starts a color tween. /// /// Target. /// Target color. /// Duration. protected virtual void StartColorTween(Graphic target, Color targetColor, float duration) { if (target == null) return; target.CrossFadeColor(targetColor, duration, true, true); } /// /// Does a sprite swap. /// /// Target. /// New sprite. protected virtual void DoSpriteSwap(Graphic target, Sprite newSprite) { if (target == null) return; Image image = target as Image; if (image == null) return; image.overrideSprite = newSprite; } /// /// Triggers the hover state animation. /// /// Triggername. private void TriggerHoverStateAnimation(string triggername) { if (this.hoverTargetGraphic == null) return; // Get the animator on the target game object Animator animator = this.hoverTargetGraphic.gameObject.GetComponent(); if (animator == null || !animator.enabled || !animator.isActiveAndEnabled || animator.runtimeAnimatorController == null || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername)) return; animator.ResetTrigger(this.hoverNormalTrigger); animator.ResetTrigger(this.hoverHighlightTrigger); animator.SetTrigger(triggername); } /// /// Triggers the pressed state animation. /// /// Triggername. private void TriggerPressStateAnimation(string triggername) { if (this.pressTargetGraphic == null) return; // Get the animator on the target game object Animator animator = this.pressTargetGraphic.gameObject.GetComponent(); if (animator == null || !animator.enabled || !animator.isActiveAndEnabled || animator.runtimeAnimatorController == null || !animator.hasBoundPlayables || string.IsNullOrEmpty(triggername)) return; animator.ResetTrigger(this.pressNormalTrigger); animator.ResetTrigger(this.pressPressTrigger); animator.SetTrigger(triggername); } /// /// Determines whether this slot is assigned. /// /// true if this instance is assigned; otherwise, false. public virtual bool IsAssigned() { return (this.GetIconSprite() != null || this.GetIconTexture() != null); } /// /// Assign the specified slot by icon sprite. /// /// Icon. public bool Assign(Sprite icon) { if (icon == null) return false; // Set the icon this.SetIcon(icon); return true; } /// /// Assign the specified slot by icon texture. /// /// Icon. public bool Assign(Texture icon) { if (icon == null) return false; // Set the icon this.SetIcon(icon); return true; } /// /// Assign the specified slot by object. /// /// Source. public virtual bool Assign(Object source) { if (source is UISlotBase) { UISlotBase sourceSlot = source as UISlotBase; if (sourceSlot != null) { // Assign by sprite or texture if (sourceSlot.GetIconSprite() != null) { return this.Assign(sourceSlot.GetIconSprite()); } else if (sourceSlot.GetIconTexture() != null) { return this.Assign(sourceSlot.GetIconTexture()); } } } return false; } /// /// Unassign this slot. /// public virtual void Unassign() { // Remove the icon this.ClearIcon(); } /// /// Gets the icon sprite of this slot if it's set and the icon graphic is . /// /// The icon. public Sprite GetIconSprite() { // Check if the icon graphic valid image if (this.iconGraphic == null || !(this.iconGraphic is Image)) return null; return (this.iconGraphic as Image).sprite; } /// /// Gets the icon texture of this slot if it's set and the icon graphic is . /// /// The icon. public Texture GetIconTexture() { // Check if the icon graphic valid image if (this.iconGraphic == null || !(this.iconGraphic is RawImage)) return null; return (this.iconGraphic as RawImage).texture; } /// /// Gets the icon as object. /// /// The icon as object. public Object GetIconAsObject() { if (this.iconGraphic == null) return null; if (this.iconGraphic is Image) { return this.GetIconSprite(); } else if (this.iconGraphic is RawImage) { return this.GetIconTexture(); } // Default return null; } /// /// Sets the icon of this slot. /// /// The icon sprite. public void SetIcon(Sprite iconSprite) { // Check if the icon graphic valid image if (this.iconGraphic == null || !(this.iconGraphic is Image)) return; // Set the sprite (this.iconGraphic as Image).sprite = iconSprite; // Enable or disabled the icon graphic game object if (iconSprite != null && !this.iconGraphic.gameObject.activeSelf) this.iconGraphic.gameObject.SetActive(true); if (iconSprite == null && this.iconGraphic.gameObject.activeSelf) this.iconGraphic.gameObject.SetActive(false); } /// /// Sets the icon of this slot. /// /// The icon texture. public void SetIcon(Texture iconTex) { // Check if the icon graphic valid raw image if (this.iconGraphic == null || !(this.iconGraphic is RawImage)) return; // Set the sprite (this.iconGraphic as RawImage).texture = iconTex; // Enable or disabled the icon graphic game object if (iconTex != null && !this.iconGraphic.gameObject.activeSelf) this.iconGraphic.gameObject.SetActive(true); if (iconTex == null && this.iconGraphic.gameObject.activeSelf) this.iconGraphic.gameObject.SetActive(false); } /// /// Clears the icon of this slot. /// public void ClearIcon() { // Check if the icon graphic valid if (this.iconGraphic == null) return; // In case of image if (this.iconGraphic is Image) (this.iconGraphic as Image).sprite = null; // In case of raw image if (this.iconGraphic is RawImage) (this.iconGraphic as RawImage).texture = null; // Disable the game object this.iconGraphic.gameObject.SetActive(false); } /// /// Raises the begin drag event. /// /// Event data. public virtual void OnBeginDrag(PointerEventData eventData) { if (!this.enabled || !this.IsAssigned() || !this.m_DragAndDropEnabled) { eventData.Reset(); return; } // Check if we have a key modifier and if it's held down if (!this.DragKeyModifierIsDown()) { eventData.Reset(); return; } // Start the drag this.m_DragHasBegan = true; // Create the temporary icon for dragging this.CreateTemporaryIcon(eventData); // Prevent event propagation eventData.Use(); } /// /// Is the drag key modifier down. /// /// true, if key modifier is down, false otherwise. public virtual bool DragKeyModifierIsDown() { switch (this.m_DragKeyModifier) { case DragKeyModifier.Control: return (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)); case DragKeyModifier.Alt: return (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)); case DragKeyModifier.Shift: return (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)); } // Default should be true return true; } /// /// Raises the drag event. /// /// Event data. public virtual void OnDrag(PointerEventData eventData) { // Check if the dragging has been started if (this.m_DragHasBegan) { // Update the dragged object's position if (this.m_CurrentDraggedObject != null) this.UpdateDraggedPosition(eventData); } } /// /// Raises the drop event. /// /// Event data. public virtual void OnDrop(PointerEventData eventData) { // Get the source slot UISlotBase source = (eventData.pointerPress != null) ? eventData.pointerPress.GetComponent() : null; // Make sure we have the source slot if (source == null || !source.IsAssigned() || !source.dragAndDropEnabled) return; // Notify the source that a drop was performed so it does not unassign source.dropPreformed = true; // Check if this slot is enabled and it's drag and drop feature is enabled if (!this.enabled || !this.m_DragAndDropEnabled) return; // Prepare a variable indicating whether the assign process was successful bool assignSuccess = false; // Normal empty slot assignment if (!this.IsAssigned()) { // Assign the target slot with the info from the source assignSuccess = this.Assign(source); // Unassign the source on successful assignment and the source is not static if (assignSuccess && !source.isStatic) source.Unassign(); } // The target slot is assigned else { // If the target slot is not static // and we have a source slot that is not static if (!this.isStatic && !source.isStatic) { // Check if we can swap if (this.CanSwapWith(source) && source.CanSwapWith(this)) { // Swap the slots assignSuccess = source.PerformSlotSwap(this); } } // If the target slot is not static // and the source slot is a static one else if (!this.isStatic && source.isStatic) { assignSuccess = this.Assign(source); } } // If this slot failed to be assigned if (!assignSuccess) { this.OnAssignBySlotFailed(source); } } /// /// Raises the end drag event. /// /// Event data. public virtual void OnEndDrag(PointerEventData eventData) { // Check if a drag was initialized at all if (!this.m_DragHasBegan) return; // Reset the drag begin bool this.m_DragHasBegan = false; // Destroy the dragged icon object if (this.m_CurrentDraggedObject != null) { Destroy(this.m_CurrentDraggedObject); } // Reset the variables this.m_CurrentDraggedObject = null; this.m_CurrentDraggingPlane = null; // Check if we are returning the icon to the same slot // By checking if the slot is highlighted if (this.IsHighlighted(eventData)) return; // Check if no drop was preformed if (!this.m_DropPreformed) { // Try to throw away the assigned content this.OnThrowAway(); } else { // Reset the drop preformed variable this.m_DropPreformed = false; } } /// /// Determines whether this slot can swap with the specified target slot. /// /// true if this instance can swap with the specified target; otherwise, false. /// Target. public virtual bool CanSwapWith(Object target) { return (target is UISlotBase); } /// /// Performs a slot swap. /// /// Target slot. public virtual bool PerformSlotSwap(Object targetObject) { // Get the source slot UISlotBase targetSlot = (targetObject as UISlotBase); // Get the target slot icon Object targetIcon = targetSlot.GetIconAsObject(); // Assign the target slot with this one bool assign1 = targetSlot.Assign(this); // Assign this slot by the target slot icon bool assign2 = this.Assign(targetIcon); // Return the status return (assign1 && assign2); } /// /// Called when the slot fails to assign by another slot. /// protected virtual void OnAssignBySlotFailed(Object source) { Debug.Log("UISlotBase (" + this.gameObject.name + ") failed to get assigned by (" + (source as UISlotBase).gameObject.name + ")."); } /// /// This method is raised to confirm throwing away the slot. /// protected virtual void OnThrowAway() { // Check if throwing away is allowed if (this.m_AllowThrowAway) { // Throw away successful, unassign the slot this.Unassign(); } else { // Throw away was denied this.OnThrowAwayDenied(); } } /// /// This method is raised when the slot is denied to be thrown away and returned to it's source. /// protected virtual void OnThrowAwayDenied() { } /// /// Creates the temporary icon. /// /// The temporary icon. protected virtual void CreateTemporaryIcon(PointerEventData eventData) { Canvas canvas = UIUtility.FindInParents(this.gameObject); if (canvas == null || this.iconGraphic == null) return; // Create temporary panel GameObject iconObj = (GameObject)Instantiate((this.m_CloneTarget == null) ? this.iconGraphic.gameObject : this.m_CloneTarget); iconObj.transform.localScale = new Vector3(1f, 1f, 1f); iconObj.transform.SetParent(canvas.transform, false); iconObj.transform.SetAsLastSibling(); (iconObj.transform as RectTransform).pivot = new Vector2(0.5f, 0.5f); // The icon will be under the cursor. // We want it to be ignored by the event system. iconObj.AddComponent(); // Save the dragging plane this.m_CurrentDraggingPlane = canvas.transform as RectTransform; // Set as the current dragging object this.m_CurrentDraggedObject = iconObj; // Update the icon position this.UpdateDraggedPosition(eventData); } /// /// Updates the dragged icon position. /// /// Data. private void UpdateDraggedPosition(PointerEventData data) { var rt = this.m_CurrentDraggedObject.GetComponent(); Vector3 globalMousePos; if (RectTransformUtility.ScreenPointToWorldPointInRectangle(this.m_CurrentDraggingPlane, data.position, data.pressEventCamera, out globalMousePos)) { rt.position = globalMousePos; rt.rotation = this.m_CurrentDraggingPlane.rotation; } } /// /// Internal call for show tooltip. /// protected void InternalShowTooltip() { // Call the on tooltip only if it's currently not shown if (!this.m_IsTooltipShown) { this.m_IsTooltipShown = true; this.OnTooltip(true); } } /// /// Internal call for hide tooltip. /// protected void InternalHideTooltip() { // Cancel the delayed show coroutine this.StopCoroutine("TooltipDelayedShow"); // Call the on tooltip only if it's currently shown if (this.m_IsTooltipShown) { this.m_IsTooltipShown = false; this.OnTooltip(false); } } protected IEnumerator TooltipDelayedShow() { yield return new WaitForSeconds(this.m_TooltipDelay); this.InternalShowTooltip(); } } }