390 lines
12 KiB
C#
390 lines
12 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.InputSystem;
|
|
|
|
namespace DDD
|
|
{
|
|
public enum ButtonState
|
|
{
|
|
Normal,
|
|
Highlighted,
|
|
Pressed,
|
|
Selected,
|
|
Disabled
|
|
}
|
|
|
|
public class UiButton : MonoBehaviour, IInteractableUi, IPointerEnterHandler, IPointerExitHandler,
|
|
IPointerDownHandler, IPointerUpHandler, ISelectHandler, IDeselectHandler, ISubmitHandler
|
|
{
|
|
[Header("Button Components")]
|
|
[SerializeField] private Button _button;
|
|
[SerializeField] private Selectable _selectable;
|
|
|
|
[Header("State Synchronization")]
|
|
[SerializeField] private bool _synchronizeStates = true;
|
|
[SerializeField] private bool _handleKeyboardInput = true;
|
|
[SerializeField] private bool _handleGamepadInput = true;
|
|
|
|
[Header("Visual Feedback")]
|
|
[SerializeField] private Animator _animator;
|
|
[SerializeField] private Image _targetGraphic;
|
|
|
|
[Header("Toggle Functionality")]
|
|
[SerializeField] private bool _useToggle = false;
|
|
|
|
// State tracking
|
|
private bool _isPressed;
|
|
private bool _isHighlighted;
|
|
private bool _isSelected;
|
|
private bool _wasSelectedByKeyboard;
|
|
private bool _isToggled = false;
|
|
|
|
// Events
|
|
public event Action OnClicked;
|
|
public event Action OnStateChanged;
|
|
|
|
// Animation parameter hashes (if using Animator)
|
|
private readonly int _normalHash = Animator.StringToHash("Normal");
|
|
private readonly int _highlightedHash = Animator.StringToHash("Highlighted");
|
|
private readonly int _pressedHash = Animator.StringToHash("Pressed");
|
|
private readonly int _selectedHash = Animator.StringToHash("Selected");
|
|
private readonly int _disabledHash = Animator.StringToHash("Disabled");
|
|
|
|
private void Awake()
|
|
{
|
|
InitializeComponents();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_button != null)
|
|
{
|
|
_button.onClick.AddListener(HandleButtonClick);
|
|
}
|
|
|
|
UpdateVisualState();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (_button != null)
|
|
{
|
|
_button.onClick.RemoveListener(HandleButtonClick);
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
HandleInputUpdate();
|
|
}
|
|
|
|
private void InitializeComponents()
|
|
{
|
|
// Get Button component if not assigned
|
|
if (_button == null)
|
|
{
|
|
_button = GetComponent<Button>();
|
|
}
|
|
|
|
// Get Selectable component (Button inherits from Selectable)
|
|
if (_selectable == null)
|
|
{
|
|
_selectable = _button;
|
|
}
|
|
|
|
// Get target graphic from button if not assigned
|
|
if (_targetGraphic == null && _button != null)
|
|
{
|
|
_targetGraphic = _button.targetGraphic as Image;
|
|
}
|
|
|
|
// Get Animator if not assigned
|
|
if (_animator == null)
|
|
{
|
|
_animator = GetComponent<Animator>();
|
|
}
|
|
}
|
|
|
|
private void HandleInputUpdate()
|
|
{
|
|
if (!_handleKeyboardInput && !_handleGamepadInput) return;
|
|
if (_selectable == null || !_selectable.interactable) return;
|
|
|
|
// Handle keyboard/gamepad input when this button is selected
|
|
if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject)
|
|
{
|
|
//HandleSelectedInput();
|
|
}
|
|
}
|
|
|
|
private void HandleSelectedInput()
|
|
{
|
|
var keyboard = Keyboard.current;
|
|
var gamepad = Gamepad.current;
|
|
|
|
// Handle keyboard input
|
|
if (_handleKeyboardInput && keyboard != null)
|
|
{
|
|
if (keyboard.enterKey.wasPressedThisFrame || keyboard.spaceKey.wasPressedThisFrame)
|
|
{
|
|
HandleButtonClick();
|
|
}
|
|
}
|
|
|
|
// Handle gamepad input
|
|
if (_handleGamepadInput && gamepad != null)
|
|
{
|
|
if (gamepad.buttonSouth.wasPressedThisFrame) // A button on Xbox controller
|
|
{
|
|
HandleButtonClick();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleButtonClick()
|
|
{
|
|
if (_selectable != null && !_selectable.interactable) return;
|
|
|
|
if (_useToggle)
|
|
{
|
|
_isToggled = !_isToggled;
|
|
|
|
if (_isToggled)
|
|
{
|
|
// When toggled on, maintain pressed state and make non-interactable
|
|
_isPressed = true;
|
|
_selectable.interactable = false;
|
|
}
|
|
else
|
|
{
|
|
// When toggled off, restore normal behavior
|
|
_isPressed = false;
|
|
_selectable.interactable = true;
|
|
}
|
|
|
|
UpdateVisualState();
|
|
}
|
|
|
|
OnClicked?.Invoke();
|
|
}
|
|
|
|
private void UpdateVisualState()
|
|
{
|
|
if (!_synchronizeStates) return;
|
|
|
|
var currentState = GetCurrentState();
|
|
print(currentState);
|
|
ApplyVisualState(currentState);
|
|
OnStateChanged?.Invoke();
|
|
}
|
|
|
|
private ButtonState GetCurrentState()
|
|
{
|
|
if (_selectable == null || (!_selectable.interactable && !_isToggled))
|
|
return ButtonState.Disabled;
|
|
|
|
// Toggle mode: when toggled on, maintain pressed state even if not interactable
|
|
if (_useToggle && _isToggled)
|
|
return ButtonState.Pressed;
|
|
|
|
if (_isPressed)
|
|
return ButtonState.Pressed;
|
|
|
|
if (_isSelected)
|
|
return ButtonState.Selected;
|
|
|
|
if (_isHighlighted)
|
|
return ButtonState.Highlighted;
|
|
|
|
return ButtonState.Normal;
|
|
}
|
|
|
|
private void ApplyVisualState(ButtonState state)
|
|
{
|
|
// Apply animator state if available
|
|
if (_animator != null && _animator.runtimeAnimatorController != null)
|
|
{
|
|
ApplyAnimatorState(state);
|
|
}
|
|
|
|
// Apply color tint if using Button's color block
|
|
if (_button != null && _targetGraphic != null)
|
|
{
|
|
ApplyColorState(state);
|
|
}
|
|
}
|
|
|
|
private void ApplyAnimatorState(ButtonState state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case ButtonState.Normal:
|
|
_animator.SetTrigger(_normalHash);
|
|
break;
|
|
case ButtonState.Highlighted:
|
|
_animator.SetTrigger(_highlightedHash);
|
|
break;
|
|
case ButtonState.Pressed:
|
|
_animator.SetTrigger(_pressedHash);
|
|
break;
|
|
case ButtonState.Selected:
|
|
_animator.SetTrigger(_selectedHash);
|
|
break;
|
|
case ButtonState.Disabled:
|
|
_animator.SetTrigger(_disabledHash);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void ApplyColorState(ButtonState state)
|
|
{
|
|
var colors = _button.colors;
|
|
Color targetColor;
|
|
|
|
switch (state)
|
|
{
|
|
case ButtonState.Normal:
|
|
targetColor = colors.normalColor;
|
|
break;
|
|
case ButtonState.Highlighted:
|
|
targetColor = colors.highlightedColor;
|
|
break;
|
|
case ButtonState.Pressed:
|
|
targetColor = colors.pressedColor;
|
|
break;
|
|
case ButtonState.Selected:
|
|
targetColor = colors.selectedColor;
|
|
break;
|
|
case ButtonState.Disabled:
|
|
targetColor = colors.disabledColor;
|
|
break;
|
|
default:
|
|
targetColor = colors.normalColor;
|
|
break;
|
|
}
|
|
|
|
_targetGraphic.color = targetColor;
|
|
}
|
|
|
|
// IInteractableUi implementation
|
|
public void OnInteract()
|
|
{
|
|
if (_selectable != null && _selectable.interactable)
|
|
{
|
|
// This method is called for programmatic interaction
|
|
HandleButtonClick();
|
|
}
|
|
}
|
|
|
|
// Pointer event handlers
|
|
public void OnPointerEnter(PointerEventData eventData)
|
|
{
|
|
_isHighlighted = true;
|
|
UpdateVisualState();
|
|
}
|
|
|
|
public void OnPointerExit(PointerEventData eventData)
|
|
{
|
|
_isHighlighted = false;
|
|
UpdateVisualState();
|
|
}
|
|
|
|
public void OnPointerDown(PointerEventData eventData)
|
|
{
|
|
if (eventData.button == PointerEventData.InputButton.Left)
|
|
{
|
|
if (_isSelected)
|
|
{
|
|
_isPressed = true;
|
|
UpdateVisualState();
|
|
return;
|
|
}
|
|
_isSelected = true;
|
|
UpdateVisualState();
|
|
}
|
|
}
|
|
|
|
public void OnPointerUp(PointerEventData eventData)
|
|
{
|
|
if (eventData.button == PointerEventData.InputButton.Left)
|
|
{
|
|
// Don't reset pressed state in toggle mode when toggled on
|
|
if (_isPressed && !(_useToggle && _isToggled))
|
|
{
|
|
_isPressed = false;
|
|
}
|
|
_isSelected = false;
|
|
UpdateVisualState();
|
|
}
|
|
}
|
|
|
|
// Selection event handlers (for keyboard/gamepad navigation)
|
|
public void OnSelect(BaseEventData eventData)
|
|
{
|
|
_isSelected = true;
|
|
UpdateVisualState();
|
|
}
|
|
|
|
public void OnDeselect(BaseEventData eventData)
|
|
{
|
|
_isSelected = false;
|
|
UpdateVisualState();
|
|
}
|
|
|
|
// Submit handler (for keyboard/gamepad activation)
|
|
public void OnSubmit(BaseEventData eventData)
|
|
{
|
|
HandleButtonClick();
|
|
}
|
|
|
|
// Public API
|
|
public bool IsInteractable => _selectable != null && _selectable.interactable;
|
|
|
|
public void SetInteractable(bool interactable)
|
|
{
|
|
if (_selectable != null)
|
|
{
|
|
_selectable.interactable = interactable;
|
|
UpdateVisualState();
|
|
}
|
|
}
|
|
|
|
public void ForceUpdateState()
|
|
{
|
|
UpdateVisualState();
|
|
}
|
|
|
|
// Toggle functionality API
|
|
public bool UseToggle
|
|
{
|
|
get => _useToggle;
|
|
set => _useToggle = value;
|
|
}
|
|
|
|
public bool IsToggled => _isToggled;
|
|
|
|
public void SetToggleState(bool toggled)
|
|
{
|
|
if (!_useToggle) return;
|
|
|
|
_isToggled = toggled;
|
|
|
|
if (_isToggled)
|
|
{
|
|
_isPressed = true;
|
|
if (_selectable != null)
|
|
_selectable.interactable = false;
|
|
}
|
|
else
|
|
{
|
|
_isPressed = false;
|
|
if (_selectable != null)
|
|
_selectable.interactable = true;
|
|
}
|
|
|
|
UpdateVisualState();
|
|
}
|
|
}
|
|
} |