2025-06-07 17:43:34 +08:00

1140 lines
34 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace FairyGUI
{
/// <summary>
///
/// </summary>
public class Container : DisplayObject
{
/// <summary>
///
/// </summary>
public RenderMode renderMode;
/// <summary>
///
/// </summary>
public Camera renderCamera;
/// <summary>
///
/// </summary>
public bool opaque;
/// <summary>
///
/// </summary>
public Vector4? clipSoftness;
/// <summary>
///
/// </summary>
public IHitTest hitArea;
/// <summary>
///
/// </summary>
public bool touchChildren;
/// <summary>
///
/// </summary>
public event Action onUpdate;
/// <summary>
///
/// </summary>
public bool reversedMask;
List<DisplayObject> _children;
DisplayObject _mask;
Rect? _clipRect;
List<BatchElement> _batchElements;
internal int _panelOrder;
internal DisplayObject _lastFocus;
/// <summary>
///
/// </summary>
public Container()
: base()
{
CreateGameObject("Container");
Init();
}
/// <summary>
///
/// </summary>
/// <param name="gameObjectName"></param>
public Container(string gameObjectName)
: base()
{
CreateGameObject(gameObjectName);
Init();
}
/// <summary>
///
/// </summary>
/// <param name="attachTarget"></param>
public Container(GameObject attachTarget)
: base()
{
SetGameObject(attachTarget);
Init();
}
void Init()
{
_children = new List<DisplayObject>();
touchChildren = true;
}
/// <summary>
///
/// </summary>
public int numChildren
{
get { return _children.Count; }
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
public DisplayObject AddChild(DisplayObject child)
{
AddChildAt(child, _children.Count);
return child;
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <param name="index"></param>
/// <returns></returns>
public DisplayObject AddChildAt(DisplayObject child, int index)
{
int count = _children.Count;
if (index >= 0 && index <= count)
{
if (child.parent == this)
{
SetChildIndex(child, index);
}
else
{
child.RemoveFromParent();
if (index == count)
_children.Add(child);
else
_children.Insert(index, child);
child.InternalSetParent(this);
if (stage != null)
{
if (child is Container)
child.BroadcastEvent("onAddedToStage", null);
else
child.DispatchEvent("onAddedToStage", null);
}
InvalidateBatchingState(true);
}
return child;
}
else
{
throw new Exception("Invalid child index");
}
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
public bool Contains(DisplayObject child)
{
return _children.Contains(child);
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public DisplayObject GetChildAt(int index)
{
return _children[index];
}
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public DisplayObject GetChild(string name)
{
int cnt = _children.Count;
for (int i = 0; i < cnt; ++i)
{
if (_children[i].name == name)
return _children[i];
}
return null;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public DisplayObject[] GetChildren()
{
return _children.ToArray();
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
public int GetChildIndex(DisplayObject child)
{
return _children.IndexOf(child);
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <returns></returns>
public DisplayObject RemoveChild(DisplayObject child)
{
return RemoveChild(child, false);
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <param name="dispose"></param>
/// <returns></returns>
public DisplayObject RemoveChild(DisplayObject child, bool dispose)
{
if (child.parent != this)
throw new Exception("obj is not a child");
int i = _children.IndexOf(child);
if (i >= 0)
return RemoveChildAt(i, dispose);
else
return null;
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public DisplayObject RemoveChildAt(int index)
{
return RemoveChildAt(index, false);
}
/// <summary>
///
/// </summary>
/// <param name="index"></param>
/// <param name="dispose"></param>
/// <returns></returns>
public DisplayObject RemoveChildAt(int index, bool dispose)
{
if (index >= 0 && index < _children.Count)
{
DisplayObject child = _children[index];
if (stage != null && (child._flags & Flags.Disposed) == 0)
{
if (child is Container)
{
child.BroadcastEvent("onRemovedFromStage", null);
if (child == Stage.inst.focus || ((Container)child).IsAncestorOf(Stage.inst.focus))
Stage.inst._OnFocusRemoving(this);
}
else
{
child.DispatchEvent("onRemovedFromStage", null);
if (child == Stage.inst.focus)
Stage.inst._OnFocusRemoving(this);
}
}
_children.Remove(child);
InvalidateBatchingState(true);
if (!dispose)
child.InternalSetParent(null);
else
child.Dispose();
return child;
}
else
throw new Exception("Invalid child index");
}
/// <summary>
///
/// </summary>
public void RemoveChildren()
{
RemoveChildren(0, int.MaxValue, false);
}
/// <summary>
///
/// </summary>
/// <param name="beginIndex"></param>
/// <param name="endIndex"></param>
/// <param name="dispose"></param>
public void RemoveChildren(int beginIndex, int endIndex, bool dispose)
{
if (endIndex < 0 || endIndex >= numChildren)
endIndex = numChildren - 1;
for (int i = beginIndex; i <= endIndex; ++i)
RemoveChildAt(beginIndex, dispose);
}
/// <summary>
///
/// </summary>
/// <param name="child"></param>
/// <param name="index"></param>
public void SetChildIndex(DisplayObject child, int index)
{
int oldIndex = _children.IndexOf(child);
if (oldIndex == index) return;
if (oldIndex == -1) throw new ArgumentException("Not a child of this container");
_children.RemoveAt(oldIndex);
if (index >= _children.Count)
_children.Add(child);
else
_children.Insert(index, child);
InvalidateBatchingState(true);
}
/// <summary>
///
/// </summary>
/// <param name="child1"></param>
/// <param name="child2"></param>
public void SwapChildren(DisplayObject child1, DisplayObject child2)
{
int index1 = _children.IndexOf(child1);
int index2 = _children.IndexOf(child2);
if (index1 == -1 || index2 == -1)
throw new Exception("Not a child of this container");
SwapChildrenAt(index1, index2);
}
/// <summary>
///
/// </summary>
/// <param name="index1"></param>
/// <param name="index2"></param>
public void SwapChildrenAt(int index1, int index2)
{
DisplayObject obj1 = _children[index1];
DisplayObject obj2 = _children[index2];
_children[index1] = obj2;
_children[index2] = obj1;
InvalidateBatchingState(true);
}
/// <summary>
///
/// </summary>
/// <param name="indice"></param>
/// <param name="objs"></param>
public void ChangeChildrenOrder(IList<int> indice, IList<DisplayObject> objs)
{
int cnt = objs.Count;
for (int i = 0; i < cnt; i++)
{
DisplayObject obj = objs[i];
if (obj.parent != this)
throw new Exception("Not a child of this container");
_children[indice[i]] = obj;
}
InvalidateBatchingState(true);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public IEnumerator<DisplayObject> GetDescendants(bool backward)
{
return new DescendantsEnumerator(this, backward);
}
/// <summary>
///
/// </summary>
public Rect? clipRect
{
get { return _clipRect; }
set
{
if (_clipRect != value)
{
_clipRect = value;
UpdateBatchingFlags();
}
}
}
/// <summary>
///
/// </summary>
public DisplayObject mask
{
get { return _mask; }
set
{
if (_mask != value)
{
_mask = value;
_flags |= Flags.BatchingRequested;
UpdateBatchingFlags();
}
}
}
/// <summary>
///
/// </summary>
public void CreateGraphics()
{
if (graphics == null)
{
graphics = new NGraphics(this.gameObject);
graphics.texture = NTexture.Empty;
}
}
public override Rect GetBounds(DisplayObject targetSpace)
{
if (_clipRect != null)
return TransformRect((Rect)_clipRect, targetSpace);
int count = _children.Count;
Rect rect;
if (count == 0)
{
Vector2 v = TransformPoint(Vector2.zero, targetSpace);
rect = Rect.MinMaxRect(v.x, v.y, 0, 0);
}
else if (count == 1)
{
rect = _children[0].GetBounds(targetSpace);
}
else
{
float minX = float.MaxValue, maxX = float.MinValue;
float minY = float.MaxValue, maxY = float.MinValue;
for (int i = 0; i < count; ++i)
{
rect = _children[i].GetBounds(targetSpace);
minX = minX < rect.xMin ? minX : rect.xMin;
maxX = maxX > rect.xMax ? maxX : rect.xMax;
minY = minY < rect.yMin ? minY : rect.yMin;
maxY = maxY > rect.yMax ? maxY : rect.yMax;
}
rect = Rect.MinMaxRect(minX, minY, maxX, maxY);
}
return rect;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public Camera GetRenderCamera()
{
if (renderMode == RenderMode.ScreenSpaceOverlay)
return StageCamera.main;
else
{
Camera cam = this.renderCamera;
if (cam == null)
{
if (HitTestContext.cachedMainCamera != null)
cam = HitTestContext.cachedMainCamera;
else
{
cam = Camera.main;
if (cam == null)
cam = StageCamera.main;
}
}
return cam;
}
}
/// <summary>
///
/// </summary>
/// <param name="stagePoint"></param>
/// <param name="forTouch"></param>
/// <param name="displayIndex"></param>
/// <returns></returns>
public DisplayObject HitTest(Vector2 stagePoint, bool forTouch)
{
if (StageCamera.main == null)
{
if (this is Stage)
return this;
else
return null;
}
HitTestContext.screenPoint = new Vector3(stagePoint.x, Screen.height - stagePoint.y, 0);
if (Display.displays.Length > 1)
{
Vector3 p = Display.RelativeMouseAt(HitTestContext.screenPoint);
if (p.x != 0 || p.y != 0) //(p != Vector3.zero) we got (0,0,1) in some unity version, especially on recovering from sleep
HitTestContext.screenPoint = p;
}
HitTestContext.worldPoint = StageCamera.main.ScreenToWorldPoint(HitTestContext.screenPoint);
HitTestContext.direction = Vector3.back;
HitTestContext.forTouch = forTouch;
HitTestContext.camera = StageCamera.main;
DisplayObject ret = HitTest();
if (ret != null)
return ret;
else if (this is Stage)
return this;
else
return null;
}
override protected DisplayObject HitTest()
{
if ((_flags & Flags.UserGameObject) != 0 && !gameObject.activeInHierarchy)
return null;
if (this.cachedTransform.localScale.x == 0 || this.cachedTransform.localScale.y == 0)
return null;
Camera savedCamera = HitTestContext.camera;
Vector3 savedWorldPoint = HitTestContext.worldPoint;
Vector3 savedDirection = HitTestContext.direction;
DisplayObject target;
if (renderMode != RenderMode.ScreenSpaceOverlay || (_flags & Flags.UserGameObject) != 0)
{
Camera cam = GetRenderCamera();
if (cam.targetDisplay != HitTestContext.screenPoint.z)
return null;
HitTestContext.camera = cam;
if (renderMode == RenderMode.WorldSpace)
{
Vector3 screenPoint = HitTestContext.camera.WorldToScreenPoint(this.cachedTransform.position); //only for query z value
screenPoint.x = HitTestContext.screenPoint.x;
screenPoint.y = HitTestContext.screenPoint.y;
//获得本地z轴在世界坐标的方向
HitTestContext.worldPoint = HitTestContext.camera.ScreenToWorldPoint(screenPoint);
Ray ray = HitTestContext.camera.ScreenPointToRay(screenPoint);
HitTestContext.direction = Vector3.zero - ray.direction;
}
else if (renderMode == RenderMode.ScreenSpaceCamera)
{
HitTestContext.worldPoint = HitTestContext.camera.ScreenToWorldPoint(HitTestContext.screenPoint);
}
}
else
{
if (HitTestContext.camera.targetDisplay != HitTestContext.screenPoint.z && !(this is Stage))
return null;
}
target = HitTest_Container();
HitTestContext.camera = savedCamera;
HitTestContext.worldPoint = savedWorldPoint;
HitTestContext.direction = savedDirection;
return target;
}
DisplayObject HitTest_Container()
{
Vector2 localPoint = WorldToLocal(HitTestContext.worldPoint, HitTestContext.direction);
if (_vertexMatrix != null)
HitTestContext.worldPoint = this.cachedTransform.TransformPoint(new Vector2(localPoint.x, -localPoint.y));
if (hitArea != null)
{
if (!hitArea.HitTest(_contentRect, localPoint))
return null;
if (hitArea is MeshColliderHitTest)
localPoint = ((MeshColliderHitTest)hitArea).lastHit;
}
else
{
if (_clipRect != null && !((Rect)_clipRect).Contains(localPoint))
return null;
}
if (_mask != null)
{
DisplayObject tmp = _mask.InternalHitTestMask();
if (!reversedMask && tmp == null || reversedMask && tmp != null)
return null;
}
DisplayObject target = null;
if (touchChildren)
{
int count = _children.Count;
for (int i = count - 1; i >= 0; --i) // front to back!
{
DisplayObject child = _children[i];
if ((child._flags & Flags.GameObjectDisposed) != 0)
{
child.DisplayDisposedWarning();
continue;
}
if (child == _mask || (child._flags & Flags.TouchDisabled) != 0)
continue;
target = child.InternalHitTest();
if (target != null)
break;
}
}
if (target == null && opaque && (hitArea != null || _contentRect.Contains(localPoint)))
target = this;
return target;
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool IsAncestorOf(DisplayObject obj)
{
if (obj == null)
return false;
Container p = obj.parent;
while (p != null)
{
if (p == this)
return true;
p = p.parent;
}
return false;
}
/// <summary>
///
/// </summary>
public bool fairyBatching
{
get { return (_flags & Flags.FairyBatching) != 0; }
set
{
bool oldValue = (_flags & Flags.FairyBatching) != 0;
if (oldValue != value)
{
if (value)
_flags |= Flags.FairyBatching;
else
_flags &= ~Flags.FairyBatching;
UpdateBatchingFlags();
}
}
}
internal void UpdateBatchingFlags()
{
bool oldValue = (_flags & Flags.BatchingRoot) != 0;
bool newValue = (_flags & Flags.FairyBatching) != 0 || _clipRect != null || _mask != null || _paintingMode > 0;
if (newValue)
_flags |= Flags.BatchingRoot;
else
_flags &= ~Flags.BatchingRoot;
if (oldValue != newValue)
{
if (newValue)
_flags |= Flags.BatchingRequested;
else if (_batchElements != null)
_batchElements.Clear();
InvalidateBatchingState();
}
}
/// <summary>
///
/// </summary>
/// <param name="childrenChanged"></param>
public void InvalidateBatchingState(bool childrenChanged)
{
if (childrenChanged && (_flags & Flags.BatchingRoot) != 0)
_flags |= Flags.BatchingRequested;
else
{
Container p = this.parent;
while (p != null)
{
if ((p._flags & Flags.BatchingRoot) != 0)
{
p._flags |= Flags.BatchingRequested;
break;
}
p = p.parent;
}
}
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
public void SetChildrenLayer(int value)
{
int cnt = _children.Count;
for (int i = 0; i < cnt; i++)
{
DisplayObject child = _children[i];
if (child._paintingMode > 0)
child.paintingGraphics.gameObject.layer = value;
else
child._SetLayerDirect(value);
if ((child is Container) && child._paintingMode == 0)
((Container)child).SetChildrenLayer(value);
}
}
override public void Update(UpdateContext context)
{
if ((_flags & Flags.UserGameObject) != 0 && !gameObject.activeInHierarchy)
return;
base.Update(context);
if (_paintingMode != 0)
{
if ((_flags & Flags.CacheAsBitmap) != 0 && _paintingInfo.flag == 2)
{
if (onUpdate != null)
onUpdate();
return;
}
context.EnterPaintingMode();
}
if (_mask != null)
{
context.EnterClipping(this.id, reversedMask);
if (_mask.graphics != null)
_mask.graphics._PreUpdateMask(context, _mask.id);
}
else if (_clipRect != null)
context.EnterClipping(this.id, this.TransformRect((Rect)_clipRect, null), clipSoftness);
float savedAlpha = context.alpha;
context.alpha *= this.alpha;
bool savedGrayed = context.grayed;
context.grayed = context.grayed || this.grayed;
if ((_flags & Flags.FairyBatching) != 0)
context.batchingDepth++;
if (context.batchingDepth > 0)
{
int cnt = _children.Count;
for (int i = 0; i < cnt; i++)
{
DisplayObject child = _children[i];
if ((child._flags & Flags.GameObjectDisposed) != 0)
{
child.DisplayDisposedWarning();
continue;
}
if (child.visible)
child.Update(context);
}
}
else
{
if (_mask != null)
_mask.SetRenderingOrder(context, false);
int cnt = _children.Count;
for (int i = 0; i < cnt; i++)
{
DisplayObject child = _children[i];
if ((child._flags & Flags.GameObjectDisposed) != 0)
{
child.DisplayDisposedWarning();
continue;
}
if (child.visible)
{
if (!(child.graphics != null && child.graphics._maskFlag == 1)) //if not a mask
child.SetRenderingOrder(context, false);
child.Update(context);
}
}
if (_mask != null)
{
if (_mask.graphics != null)
_mask.graphics._SetStencilEraserOrder(context.renderingOrder++);
}
}
if ((_flags & Flags.FairyBatching) != 0)
{
if (context.batchingDepth == 1)
SetRenderingOrderAll(context);
context.batchingDepth--;
}
context.alpha = savedAlpha;
context.grayed = savedGrayed;
if (_clipRect != null || _mask != null)
context.LeaveClipping();
if (_paintingMode != 0)
{
context.LeavePaintingMode();
UpdateContext.OnEnd += _paintingInfo.captureDelegate;
}
if (onUpdate != null)
onUpdate();
}
private void SetRenderingOrderAll(UpdateContext context)
{
if ((_flags & Flags.BatchingRequested) != 0)
DoFairyBatching();
if (_mask != null)
_mask.SetRenderingOrder(context, false);
int cnt = _batchElements.Count;
for (int i = 0; i < cnt; i++)
{
BatchElement batchElement = _batchElements[i];
batchElement.owner.SetRenderingOrder(context, true);
if (batchElement.isRoot)
((Container)batchElement.owner).SetRenderingOrderAll(context);
}
if (_mask != null)
{
if (_mask.graphics != null)
_mask.graphics._SetStencilEraserOrder(context.renderingOrder++);
}
}
private void DoFairyBatching()
{
_flags &= ~Flags.BatchingRequested;
if (_batchElements == null)
_batchElements = new List<BatchElement>();
else
_batchElements.Clear();
CollectChildren(this, false);
int cnt = _batchElements.Count;
int i, j, k, m;
object curMat, testMat, lastMat;
BatchElement current, test;
float[] bounds;
for (i = 0; i < cnt; i++)
{
current = _batchElements[i];
bounds = current.bounds;
curMat = current.material;
if (curMat == null || current.breakBatch)
continue;
k = -1;
lastMat = null;
m = i;
for (j = i - 1; j >= 0; j--)
{
test = _batchElements[j];
if (test.breakBatch)
break;
testMat = test.material;
if (testMat != null)
{
if (lastMat != testMat)
{
lastMat = testMat;
m = j + 1;
}
if (curMat == testMat)
k = m;
}
if ((bounds[0] > test.bounds[0] ? bounds[0] : test.bounds[0])
<= (bounds[2] < test.bounds[2] ? bounds[2] : test.bounds[2])
&& (bounds[1] > test.bounds[1] ? bounds[1] : test.bounds[1])
<= (bounds[3] < test.bounds[3] ? bounds[3] : test.bounds[3]))
{
if (k == -1)
k = m;
break;
}
}
if (k != -1 && i != k)
{
_batchElements.RemoveAt(i);
_batchElements.Insert(k, current);
}
}
//Debug.Log("DoFairyBatching " + cnt + "," + this.cachedTransform.GetInstanceID());
}
private void CollectChildren(Container initiator, bool outlineChanged)
{
EnsureSizeCorrect();
int count = _children.Count;
for (int i = 0; i < count; i++)
{
DisplayObject child = _children[i];
if (!child.visible || child == initiator._mask)
continue;
bool childOutlineChanged = outlineChanged || (child._flags & Flags.OutlineChanged) != 0;
bool isRoot = (child._flags & Flags.BatchingRoot) != 0;
BatchElement batchElement = child.AddToBatch(initiator._batchElements, isRoot);
if (batchElement != null)
{
batchElement.isRoot = isRoot;
if (childOutlineChanged)
{
Rect rect = child.GetBounds(initiator);
batchElement.bounds[0] = rect.xMin;
batchElement.bounds[1] = rect.yMin;
batchElement.bounds[2] = rect.xMax;
batchElement.bounds[3] = rect.yMax;
child._flags &= ~Flags.OutlineChanged;
}
}
if (isRoot)
{
if ((child._flags & Flags.BatchingRequested) != 0)
((Container)child).DoFairyBatching();
}
else if (child is Container)
((Container)child).CollectChildren(initiator, childOutlineChanged);
}
}
public override void Dispose()
{
if ((_flags & Flags.Disposed) != 0)
return;
base.Dispose(); //Destroy GameObject tree first, avoid destroying each seperately;
int numChildren = _children.Count;
for (int i = numChildren - 1; i >= 0; --i)
{
DisplayObject obj = _children[i];
obj.InternalSetParent(null); //Avoid RemoveParent call
obj.Dispose();
}
}
/// <summary>
/// If true, when the container is focused, tab navigation is lock inside it.
/// </summary>
public bool tabStopChildren
{
get { return (_flags & Flags.TabStopChildren) != 0; }
set
{
if (value)
_flags |= Flags.TabStopChildren;
else
_flags &= ~Flags.TabStopChildren;
}
}
struct DescendantsEnumerator : IEnumerator<DisplayObject>
{
Container _root;
Container _com;
DisplayObject _current;
int _index;
bool _forward;
public DescendantsEnumerator(Container root, bool backward)
{
_root = root;
_com = _root;
_current = null;
_forward = !backward;
if (_forward)
_index = 0;
else
_index = _com._children.Count - 1;
}
public DisplayObject Current
{
get { return _current; }
}
object IEnumerator.Current
{
get { return _current; }
}
public bool MoveNext()
{
if (_forward)
{
if (_index >= _com._children.Count)
{
if (_com == _root)
{
_current = null;
return false;
}
_current = _com;
_com = _com.parent;
_index = _com.GetChildIndex(_current) + 1;
return true;
}
else
{
DisplayObject obj = _com._children[_index];
if (obj is Container)
{
_com = (Container)obj;
_index = 0;
return MoveNext();
}
_index++;
_current = obj;
return true;
}
}
else
{
if (_index < 0)
{
if (_com == _root)
{
_current = null;
return false;
}
_current = _com;
_com = _com.parent;
_index = _com.GetChildIndex(_current) - 1;
return true;
}
else
{
DisplayObject obj = _com._children[_index];
if (obj is Container)
{
_com = (Container)obj;
_index = _com._children.Count - 1;
return MoveNext();
}
_index--;
_current = obj;
return true;
}
}
}
public void Reset()
{
_com = _root;
_current = null;
_index = 0;
}
public void Dispose()
{
}
}
}
public class BatchElement
{
public Material material;
public float[] bounds;
public IBatchable owner;
public bool isRoot;
public bool breakBatch;
public BatchElement(IBatchable owner, float[] bounds)
{
this.owner = owner;
this.bounds = bounds ?? new float[4];
}
}
/// <summary>
///
/// </summary>
public interface IBatchable
{
void SetRenderingOrder(UpdateContext context, bool inBatch);
}
}