2025-07-08 10:46:31 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|