ProjectDDD/Packages/SLUnity/SLUI/SLUIDragButton.cs

550 lines
17 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace Superlazy.UI
{
[RequireComponent(typeof(Button))]
public class SLUIDragButton : SLUIComponent, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler, IPointerUpHandler, ISelectHandler, IDeselectHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
{
public static bool globalDragging;
public static GameObject currentDragObject;
public static string currentDragCancelCommand;
private static PointerEventData currentPoint;
public string command;
public bool useGrayScale;
public bool focus;
public string focusBind;
public bool fastClick;
public SLValueComparison comparison;
public string clickSound = "SoundEffect/SE_UI_click";
public string cancelCommand;
[NonSerialized]
public Button button;
private Graphic[] childImages;
private Color[] childImageColors; // TODO: SLUIColorSelect 대비해야함
private TextMeshProUGUI[] childTexts;
private Color[] childTextColors;
private DateTime clickedTime = DateTime.MinValue;
// drag
private bool dragging = false;
private enum ButtonState
{
Normal,
Highlighted,
Pressed,
Disabled
}
private ButtonState currentButtonState;
private bool isPointerInside;
private bool isPointerDown;
protected override void Validate()
{
button = GetComponent<Button>();
childImages = GetComponentsInChildren<Image>(true).Where(i => i.transform != transform).ToArray();
childImageColors = new Color[childImages.Length];
var i = 0;
foreach (var image in childImages)
{
childImageColors[i] = image.color;
i += 1;
}
childTexts = GetComponentsInChildren<TextMeshProUGUI>(true).Where(i => i.transform != transform).ToArray();
childTextColors = new Color[childTexts.Length];
i = 0;
foreach (var text in childTexts)
{
childTextColors[i] = text.color;
i += 1;
}
}
protected override void Init()
{
button.onClick.AddListener(ButtonAction);
}
public void ButtonAction()
{
if (bindParent.Active == false) return;
if (globalDragging)
{
EndDrag();
return;
}
if (fastClick == false)
{
var now = SLSystem.Now;
if (SLUIButton.globalClickedTime.AddSeconds(0.1f) > now) return;
if (clickedTime.AddSeconds(0.15f) > now) return;
SLUIButton.globalClickedTime = now;
clickedTime = now;
}
SLSound.PlaySound(clickSound, "UI", false, false, true, 0.5f);
if (string.IsNullOrEmpty(command)) return;
SLGame.Command(command, SLGame.SessionGet(bindParent.BindPath));
currentDragCancelCommand = cancelCommand;
currentDragObject = gameObject;
globalDragging = true;
{
var canvas = GetComponentInParent<Canvas>();
Camera uiCamera;
if (canvas.renderMode == RenderMode.ScreenSpaceCamera || canvas.renderMode == RenderMode.WorldSpace)
{
uiCamera = canvas.worldCamera; // Camera가 필요함
}
else
{
uiCamera = null; // ScreenSpace-Overlay는 Camera가 필요 없음
}
var scrrenPosition = RectTransformUtility.WorldToScreenPoint(uiCamera, transform.position);
SLGame.Session["DragBeginPosition"]["X"] = scrrenPosition.x;
SLGame.Session["DragBeginPosition"]["Y"] = scrrenPosition.y;
}
}
protected override void Enable()
{
if (comparison.useCheckValue)
{
comparison.OnEnable(bindParent.BindPath, OnChange);
}
else
{
if (focus && button.interactable)
{
button.Select();
}
}
if (string.IsNullOrEmpty(focusBind) == false)
{
SLGame.AddNotify(bindParent.BindPath.CombinePath(focusBind), OnFocusChange);
}
}
protected override void Disable()
{
if (comparison.useCheckValue)
{
comparison.OnDisable();
}
if (dragging) // CCC
{
EndDrag(); // Dragged == false 체크 이미 되어있음
}
if (string.IsNullOrEmpty(focusBind) == false)
{
SLGame.RemoveNotify(bindParent.BindPath.CombinePath(focusBind), OnFocusChange);
}
}
private void OnChange()
{
if (bindParent.Active == false) return;
var sessionRoot = SLGame.SessionGet(bindParent.BindPath);
if (sessionRoot == false) return; // TEMP: 세션루트가 삭제되었지만, 삭제되되기전 코루틴 처리가 있을 수 있음
var result = true;
if (comparison.useCheckValue)
{
result = comparison.Result;
}
button.interactable = result;
if (result == false)
{
if (dragging)
{
EndDrag(); // Dragged == false 체크 이미 되어있음
dragging = false;
}
}
if (focus && button.interactable)
{
button.Select();
}
if (string.IsNullOrEmpty(focusBind) == false)
{
OnFocusChange();
}
UpdateGrayScale();
}
private void UpdateGrayScale()
{
if (useGrayScale == false) return;
if (button.targetGraphic == null) return;
if (button.interactable && (button.targetGraphic.material == SLUIButton.grayMaterial || button.targetGraphic.material == SLUIButton.grayTextMaterial))
{
button.targetGraphic.material = null;
foreach (var child in childImages)
{
child.material = null;
}
foreach (var child in childTexts)
{
child.material = null;
}
}
else if (button.interactable == false && button.targetGraphic.material != SLUIButton.grayMaterial && button.targetGraphic.mainTexture != SLUIButton.grayTextMaterial)
{
button.targetGraphic.material = SLUIButton.grayMaterial;
foreach (var child in childImages)
{
child.material = SLUIButton.grayMaterial;
}
foreach (var child in childTexts)
{
child.material = SLUIButton.grayTextMaterial;
}
}
}
public void OnPointerEnter(PointerEventData eventData)
{
currentPoint = eventData;
if (bindParent.Active == false) return;
if (button.interactable == false) return;
button.Select();
isPointerInside = true;
}
public void OnPointerExit(PointerEventData eventData)
{
currentPoint = eventData;
isPointerInside = false;
}
public void OnPointerDown(PointerEventData eventData)
{
currentPoint = eventData;
isPointerDown = true;
}
public void OnPointerUp(PointerEventData eventData)
{
currentPoint = eventData;
isPointerDown = false;
}
private void OnFocusChange()
{
if (bindParent.Active == false) return;
if (button.interactable == false) return;
bool focusResult = SLGame.SessionGet(bindParent.BindPath.CombinePath(focusBind));
if (focusResult)
{
button.Select();
}
else
{
if (EventSystem.current.currentSelectedGameObject == button.gameObject)
{
EventSystem.current.SetSelectedGameObject(null);
}
}
}
public void OnSelect(BaseEventData eventData)
{
if (bindParent.Active == false) return;
if (button.interactable == false) return;
if (string.IsNullOrEmpty(focusBind)) return;
SLGame.SessionGet(bindParent.BindPath).Set(focusBind, true);
}
public void OnDeselect(BaseEventData eventData)
{
if (bindParent.Active == false) return;
if (button.interactable == false) return;
if (string.IsNullOrEmpty(focusBind)) return;
SLGame.SessionGet(bindParent.BindPath).Set(focusBind, false);
}
private void Update()
{
if (button.transition != Selectable.Transition.ColorTint) return;
var newState = GetButtonState();
if (newState != currentButtonState)
{
currentButtonState = newState;
var color = button.colors.normalColor;
switch (currentButtonState)
{
case ButtonState.Normal:
color = button.colors.normalColor;
break;
case ButtonState.Highlighted:
color = button.colors.highlightedColor;
break;
case ButtonState.Pressed:
color = button.colors.pressedColor;
break;
case ButtonState.Disabled:
color = button.colors.disabledColor;
break;
}
var i = 0;
foreach (var image in childImages)
{
image.color = childImageColors[i] * color * button.colors.colorMultiplier;
i += 1;
}
i = 0;
foreach (var text in childTexts)
{
text.color = childTextColors[i] * color * button.colors.colorMultiplier;
i += 1;
}
}
}
private ButtonState GetButtonState()
{
if (!button.interactable)
{
return ButtonState.Disabled;
}
if (isPointerDown)
{
return ButtonState.Pressed;
}
if (isPointerInside)
{
return ButtonState.Highlighted;
}
return ButtonState.Normal;
}
// Drag
private void BeginDrag()
{
if (comparison.useCheckValue && comparison.Result == false) return;
ButtonAction();
}
private void Drag()
{
if (comparison.useCheckValue && comparison.Result == false) return;
if (dragging == false) return;
}
private void EndDrag()
{
SLGame.Session["DragBeginPosition"] = false;
if (currentPoint == null)
{
UpdateExternalDrag(null, null);
return;
}
var pointerData = new PointerEventData(EventSystem.current)
{
position = currentPoint.position,
pointerId = currentPoint.pointerId,
pointerEnter = currentPoint.pointerEnter,
eligibleForClick = true
};
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
var canceled = true;
foreach (var result in results)
{
var targetObject = result.gameObject;
if (targetObject == currentDragObject) // 드래그를 넘 멀리 안했다면 짧은 클릭으로 인식
{
dragging = false;
return;
}
else if (targetObject.TryGetComponent<SLUIDragClickTarget>(out var target))
{
if (target.CanClick())
{
// target.Click 에서 EndDrag가 또 불리지 않게끔 미리 드래그 해제
currentPoint = null;
currentDragCancelCommand = null;
currentDragObject = null;
globalDragging = false;
target.Click();
canceled = false;
return;
}
break;
}
}
if (canceled)
{
SLGame.Command(currentDragCancelCommand, bindParent.BindPath);
}
currentPoint = null;
currentDragCancelCommand = null;
currentDragObject = null;
globalDragging = false;
}
public void OnBeginDrag(PointerEventData eventData)
{
currentPoint = eventData;
BeginDrag();
dragging = true;
}
public void OnDrag(PointerEventData eventData)
{
currentPoint = eventData;
Drag();
}
public void OnEndDrag(PointerEventData eventData)
{
currentPoint = eventData;
if (dragging == false) return;
EndDrag();
dragging = false;
}
// 중복제거할수도 있지만, 세션이슈나 커맨드 실행등의 이슈
public static bool UpdateExternalDrag(GameObject obj, string cancelCommand)
{
if (obj == currentDragObject)
{
return false;
}
else if (globalDragging)
{
SLGame.Command(currentDragCancelCommand, SLEntity.Empty);
currentPoint = null;
currentDragObject = null;
currentDragCancelCommand = null;
globalDragging = false;
return false;
}
else
{
if (cancelCommand != null)
{
currentDragCancelCommand = cancelCommand;
currentDragObject = obj;
globalDragging = true;
return true;
}
return false;
}
}
public static void EndExternalDrag(PointerEventData point)
{
currentPoint = point;
var pointerData = new PointerEventData(EventSystem.current)
{
position = currentPoint.position,
pointerId = currentPoint.pointerId,
pointerEnter = currentPoint.pointerEnter,
eligibleForClick = true
};
var results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
var canceled = true;
foreach (var result in results)
{
var targetObject = result.gameObject;
if (targetObject == currentDragObject) // 드래그를 넘 멀리 안했다면 짧은 클릭으로 인식
{
//dragging = false;
return;
}
else
if (targetObject.TryGetComponent<SLUIDragClickTarget>(out var target))
{
if (target.CanClick())
{
// target.Click 에서 EndDrag가 또 불리지 않게끔 미리 드래그 해제
currentPoint = null;
currentDragCancelCommand = null;
currentDragObject = null;
globalDragging = false;
target.Click();
canceled = false;
return;
}
break;
}
}
if (canceled)
{
SLGame.Command(currentDragCancelCommand, SLEntity.Empty); // TODO: 캔슬커맨드 컨텍스트 없음
}
currentPoint = null;
currentDragCancelCommand = null;
currentDragObject = null;
globalDragging = false;
}
}
}