OldBlueWater/BlueWater/Assets/Doozy/Editor/EditorUI/Components/FluidAnimatedContainer.cs
2023-08-02 15:08:03 +09:00

489 lines
16 KiB
C#

// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System;
using Doozy.Editor.Reactor.Internal;
using Doozy.Editor.UIElements;
using Doozy.Runtime.Reactor;
using Doozy.Runtime.Reactor.Easings;
using Doozy.Runtime.Reactor.Internal;
using Doozy.Runtime.Reactor.Reactions;
using Doozy.Runtime.UIElements.Extensions;
using UnityEngine.Events;
using UnityEngine.UIElements;
// ReSharper disable MemberCanBePrivate.Global
namespace Doozy.Editor.EditorUI.Components
{
public class FluidAnimatedContainer : VisualElement, IDisposable
{
public void Dispose()
{
ClearContent();
reaction?.Recycle();
}
//layoutContainer
//--fluidContainer
#region State
public enum State
{
Idle,
IsShowing,
Visible,
IsHiding,
Hidden
}
private State m_State;
public State state
{
get => m_State;
set
{
// Debug.Log($"{name}.{nameof(state)}: {state} > {value}");
reaction?.ClearOnFinishCallback();
switch (value)
{
case State.Idle:
//do nothing
break;
case State.Visible:
fluidContainer.SetStyleDisplay(DisplayStyle.Flex);
break;
case State.Hidden:
fluidContainer.SetStyleDisplay(DisplayStyle.None);
if (clearOnHide) ClearContent();
break;
case State.IsShowing:
fluidContainer.SetStyleDisplay(DisplayStyle.Flex);
reaction?.SetOnFinishCallback(() => state = State.Visible);
break;
case State.IsHiding:
fluidContainer.SetStyleDisplay(DisplayStyle.Flex);
reaction?.SetOnFinishCallback(() => state = State.Hidden);
break;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
m_State = value;
onStateChanged?.Invoke(state);
}
}
public bool isVisible => state == State.Visible || state == State.IsShowing;
public bool isHidden => state == State.Hidden || state == State.IsHiding;
#endregion
#region Direction
public enum Direction
{
Top,
Bottom,
Left,
Right
}
public Direction direction { get; private set; }
#endregion
//SETTINGS
public const Ease k_ReactionEase = Ease.InOutCubic;
public const float k_ReactionDuration = 0.2f;
private float fluidContainerWidth { get; set; }
private float fluidContainerHeight { get; set; }
public TemplateContainer templateContainer { get; }
public VisualElement layoutContainer { get; }
public VisualElement fluidContainer { get; }
public FloatReaction reaction { get; private set; }
public bool clearOnHide { get; set; }
public UnityAction OnShowCallback;
public UnityAction OnHideCallback;
public UnityAction<State> onStateChanged { get; private set; }
public FluidAnimatedContainer(bool clearOnHide) : this(string.Empty, clearOnHide) {}
public FluidAnimatedContainer(string name, bool clearOnHide) : this()
{
this
.SetName(name)
.SetClearOnHide(clearOnHide);
}
public FluidAnimatedContainer()
{
Add(templateContainer = EditorLayouts.EditorUI.FluidAnimatedContainer.CloneTree());
templateContainer
.SetStyleFlexGrow(1)
.AddStyle(EditorStyles.EditorUI.FluidAnimatedContainer);
layoutContainer = templateContainer.Q<VisualElement>(nameof(layoutContainer));
fluidContainer = layoutContainer.Q<VisualElement>(nameof(fluidContainer));
reaction = Reaction.Get<FloatReaction>().SetEditorHeartbeat().SetEase(k_ReactionEase).SetDuration(k_ReactionDuration)
.SetSetter(value =>
{
RecalculateValues();
fluidContainer.SetStyleDisplay(value < 0.99f ? DisplayStyle.Flex : DisplayStyle.None);
switch (direction)
{
case Direction.Top:
case Direction.Bottom:
// Debug.Log($"{reaction} > -{fluidContainerHeight} * {value.Round(2)} = {-fluidContainerHeight * value}");
if (fluidContainerHeight == 0) return; //this fixes the 'jumpy' effect when the contents of the container changes during animation
Update(-fluidContainerHeight * value);
break;
case Direction.Left:
case Direction.Right:
// Debug.Log($"{reaction} > -{fluidContainerHeight} * {value.Round(2)} = {-fluidContainerHeight * value}");
if (fluidContainerWidth == 0) return; //this fixes the 'jumpy' effect when the contents of the container changes during animation
Update(-fluidContainerWidth * value);
break;
default:
throw new ArgumentOutOfRangeException();
}
});
// Debug.Log($"{nameof(FluidAnimatedContainer)} - reaction type: {reaction.GetType().Name}");
direction = Direction.Top;
state = State.Idle;
fluidContainer.SetStyleDisplay(DisplayStyle.None);
fluidContainer.visible = false;
fluidContainer.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
fluidContainer.RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
fluidContainer.RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
}
private IVisualElementScheduledItem autoUpdate { get; set; }
private void OnAttachToPanel(AttachToPanelEvent evt)
{
schedule.Execute(() => OnGeometryChanged(null));
autoUpdate ??= schedule.Execute(UpdateContainer).Every(1000);
autoUpdate?.Resume();
}
private void OnDetachFromPanel(DetachFromPanelEvent evt)
{
autoUpdate?.Pause();
}
private void UpdateContainer()
{
RecalculateValues();
if (reaction.isActive)
return;
switch (state)
{
case State.Idle:
case State.IsShowing:
case State.IsHiding:
return;
case State.Visible:
ResetToVisiblePosition();
break;
case State.Hidden:
ResetToHiddenPosition();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool m_ShowContainer;
private void OnGeometryChanged(GeometryChangedEvent evt)
{
// Debug.Log($"{name}.{nameof(OnGeometryChanged)}: {evt}");
UpdateContainer();
if (!m_ShowContainer) return;
if (reaction == null) return;
if (reaction.isActive) return;
ResetToHiddenPosition();
schedule.Execute(() =>
{
m_ShowContainer = false;
reaction.Play(PlayDirection.Reverse);
fluidContainer.visible = true;
});
}
public void ResetToHiddenPosition()
{
switch (direction)
{
case Direction.Top:
case Direction.Bottom:
Update(-fluidContainerHeight); //reset to hidden
break;
case Direction.Left:
case Direction.Right:
Update(-fluidContainerWidth); //reset to hidden
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public void ResetToVisiblePosition()
{
switch (direction)
{
case Direction.Top:
case Direction.Bottom:
Update(0); //reset to visible
break;
case Direction.Left:
case Direction.Right:
Update(0); //reset to visible
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public void RecalculateValues()
{
// Debug.Log($"{nameof(RecalculateValues)}");
fluidContainerWidth = fluidContainer.resolvedStyle.width;
fluidContainerHeight = fluidContainer.resolvedStyle.height;
}
public FluidAnimatedContainer SetDirection(Direction targetDirection)
{
if (targetDirection == direction) return this;
if (reaction.isActive) reaction.SetProgressAtZero();
direction = targetDirection;
return this;
}
private void Update(float toValue)
{
switch (direction)
{
case Direction.Top:
fluidContainer.SetStyleMarginTop(toValue);
break;
case Direction.Bottom:
fluidContainer.SetStyleBottom(toValue);
fluidContainer.SetStyleMarginTop(toValue);
break;
case Direction.Left:
fluidContainer.SetStyleMarginLeft(toValue);
break;
case Direction.Right:
fluidContainer.SetStyleMarginRight(toValue);
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
}
public FluidAnimatedContainer Toggle(bool show, bool animateChange = true) =>
show ? Show(animateChange) : Hide(animateChange);
public FluidAnimatedContainer Show(bool animateChange = true)
{
schedule.Execute(() => OnGeometryChanged(null));
fluidContainer.visible = true;
if (!animateChange)
{
switch (state)
{
case State.Idle:
case State.Hidden:
OnShowCallback?.Invoke();
break;
case State.Visible:
//do nothing
break;
case State.IsShowing:
case State.IsHiding:
reaction?.Stop();
break;
default:
throw new ArgumentOutOfRangeException();
}
Update(0); //reset
state = State.Visible;
}
else //animation
{
switch (state)
{
case State.Idle:
case State.Hidden:
m_ShowContainer = true;
OnShowCallback?.Invoke();
fluidContainer.visible = false;
state = State.IsShowing;
break;
case State.IsHiding:
reaction.Reverse();
state = State.IsShowing;
break;
case State.IsShowing:
case State.Visible:
//do nothing
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return this;
}
public FluidAnimatedContainer Hide(bool animateChange = true)
{
schedule.Execute(() => OnGeometryChanged(null));
if (!animateChange)
{
switch (state)
{
case State.Idle:
case State.Hidden:
//do nothing
break;
case State.Visible:
OnHideCallback?.Invoke();
RecalculateValues();
break;
case State.IsShowing:
case State.IsHiding:
reaction.Stop();
break;
default:
throw new ArgumentOutOfRangeException();
}
switch (direction)
{
case Direction.Top:
case Direction.Bottom:
Update(-fluidContainerHeight); //reset
break;
case Direction.Left:
case Direction.Right:
Update(-fluidContainerWidth); //reset
break;
default:
throw new ArgumentOutOfRangeException();
}
fluidContainer.SetStyleDisplay(DisplayStyle.None);
state = State.Hidden;
}
else //animation
{
switch (state)
{
case State.Idle:
reaction.Play(PlayDirection.Forward);
state = State.IsHiding;
break;
case State.Visible:
RecalculateValues();
OnHideCallback?.Invoke();
reaction.Play(PlayDirection.Forward);
state = State.IsHiding;
break;
case State.IsShowing:
reaction.Reverse();
state = State.IsHiding;
break;
case State.IsHiding:
case State.Hidden:
//do nothing
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return this;
}
public FluidAnimatedContainer AddContent(VisualElement content)
{
fluidContainer.AddChild(content);
return this;
}
/// <summary> Recycle and clear the animated container and then reset its layout </summary>
public FluidAnimatedContainer ClearContent(bool resetLayout = false)
{
fluidContainer.RecycleAndClear();
if (resetLayout) fluidContainer.ResetLayout();
return this;
}
public FluidAnimatedContainer SetClearOnHide(bool clear)
{
clearOnHide = clear;
return this;
}
#region OnShowCallback
public FluidAnimatedContainer AddOnShowCallback(UnityAction callback)
{
OnShowCallback += callback;
return this;
}
public FluidAnimatedContainer SetOnShowCallback(UnityAction callback)
{
OnShowCallback = callback;
return this;
}
public FluidAnimatedContainer ClearOnShowCallback()
{
OnShowCallback = null;
return this;
}
#endregion
#region OnHideCallback
public FluidAnimatedContainer AddOnHideCallback(UnityAction callback)
{
OnHideCallback += callback;
return this;
}
public FluidAnimatedContainer SetOnHideCallback(UnityAction callback)
{
OnHideCallback = callback;
return this;
}
public FluidAnimatedContainer ClearOnHideCallback()
{
OnHideCallback = null;
return this;
}
#endregion
}
}