Skip to content

Commit 7adb510

Browse files
committed
added prefab component
1 parent 839fb70 commit 7adb510

22 files changed

+425
-22
lines changed

Diff for: Runtime/Core/BaseReactComponent.cs

+1
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ public void ApplyLayoutStyles()
291291
ApplyLayoutStylesSelf();
292292
}
293293

294+
public abstract void Relayout();
294295

295296
private void OnStylesUpdated(NodeStyle obj, bool hasLayout)
296297
{

Diff for: Runtime/Core/ComponentInterfaces.cs

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public interface IReactComponent
3333
void ApplyLayoutStyles();
3434
void ScheduleLayout();
3535
void ResolveStyle(bool recursive = false);
36+
void Relayout();
3637

3738
void Update();
3839
void Accept(ReactComponentVisitor visitor);

Diff for: Runtime/Core/ReactUnityBridge.cs

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using Jint.Native;
2-
using Jint.Native.Function;
31
using ReactUnity.Helpers.TypescriptUtils;
42
using ReactUnity.Helpers;
53
using System;
@@ -83,19 +81,10 @@ public void setData(object element, string property, object value)
8381
c.SetData(property, value);
8482
}
8583

86-
public void setEventListener(IReactComponent element, string eventType, JsValue value)
87-
{
88-
var hasValue = value != null && !value.IsNull() && !value.IsUndefined() && !value.IsBoolean();
89-
var callback = value.As<FunctionInstance>();
90-
if (hasValue && callback == null) throw new Exception("The callback for an event must be a function.");
91-
92-
element.SetEventListener(eventType, new Callback(callback));
93-
}
94-
9584
public void setEventListener(object element, string eventType, object value)
9685
{
97-
if (element is IReactComponent c && value != null)
98-
c.SetEventListener(eventType, new Callback(value));
86+
if (element is IReactComponent c)
87+
c.SetEventListener(eventType, Callback.From(value));
9988
}
10089

10190
#endregion
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using Facebook.Yoga;
2+
using ReactUnity.Helpers;
3+
using System.Collections.Generic;
4+
using UnityEngine;
5+
6+
namespace ReactUnity.UGUI
7+
{
8+
public class PrefabComponent : UGUIComponent
9+
{
10+
GameObject currentTarget;
11+
public GameObject Instance { get; private set; }
12+
private RectTransform InstanceTransform;
13+
private Transform InstanceParent;
14+
private bool InstanceWasPrefab;
15+
IPrefabTarget TargetHandler;
16+
17+
Callback onMount;
18+
Callback onUnmount;
19+
20+
public PrefabComponent(UGUIContext context, string tag = "prefab") : base(context, tag)
21+
{
22+
Layout.SetMeasureFunction(Measure);
23+
}
24+
25+
private YogaSize Measure(YogaNode node, float width, YogaMeasureMode widthMode, float height, YogaMeasureMode heightMode)
26+
{
27+
if (!InstanceTransform) return new YogaSize { width = 0, height = 0 };
28+
29+
return new YogaSize
30+
{
31+
width = InstanceTransform.rect.width,
32+
height = InstanceTransform.rect.height,
33+
};
34+
}
35+
36+
void SetTarget(GameObject target)
37+
{
38+
if (currentTarget == target) return;
39+
40+
if (currentTarget)
41+
{
42+
TargetHandler?.Unmount(this);
43+
onUnmount?.Call(currentTarget, this);
44+
currentTarget = null;
45+
TargetHandler = null;
46+
47+
if (Instance)
48+
{
49+
if (InstanceWasPrefab) GameObject.Destroy(Instance);
50+
else Instance.transform.SetParent(InstanceParent, false);
51+
}
52+
Instance = null;
53+
InstanceTransform = null;
54+
InstanceParent = null;
55+
}
56+
57+
currentTarget = target;
58+
59+
if (currentTarget)
60+
{
61+
ResolveInstance();
62+
63+
Instance.transform.SetParent(Container, false);
64+
TargetHandler?.Mount(this);
65+
onMount?.Call(currentTarget, this);
66+
}
67+
68+
Relayout();
69+
}
70+
71+
void ResolveInstance()
72+
{
73+
if (!currentTarget) return;
74+
75+
var isPrefab = currentTarget.scene.rootCount == 0;
76+
77+
if (isPrefab)
78+
{
79+
Instance = null;
80+
#if UNITY_EDITOR
81+
Instance = UnityEditor.PrefabUtility.InstantiatePrefab(currentTarget, Container) as GameObject;
82+
#endif
83+
if (!Instance) Instance = GameObject.Instantiate(currentTarget);
84+
InstanceParent = null;
85+
InstanceWasPrefab = true;
86+
}
87+
else
88+
{
89+
Instance = currentTarget;
90+
InstanceParent = currentTarget.transform.parent;
91+
InstanceWasPrefab = false;
92+
}
93+
InstanceTransform = Instance.transform as RectTransform;
94+
95+
TargetHandler = currentTarget.GetComponent<IPrefabTarget>();
96+
}
97+
98+
GameObject FindTarget(object value)
99+
{
100+
if (value == null) return null;
101+
if (value is GameObject g && g) return g;
102+
if (value is Component c && c) return c.gameObject;
103+
return null;
104+
}
105+
106+
public override void SetProperty(string propertyName, object value)
107+
{
108+
switch (propertyName)
109+
{
110+
case "target":
111+
SetTarget(FindTarget(value));
112+
break;
113+
default:
114+
var handled = TargetHandler != null ? TargetHandler.SetProperty(propertyName, value) : false;
115+
if (!handled) base.SetProperty(propertyName, value);
116+
break;
117+
}
118+
}
119+
120+
public override void SetEventListener(string eventName, Callback callback)
121+
{
122+
switch (eventName)
123+
{
124+
case "onMount":
125+
onMount = callback;
126+
return;
127+
case "onUnmount":
128+
onUnmount = callback;
129+
return;
130+
default:
131+
var handled = TargetHandler != null ? TargetHandler.SetEventListener(eventName, callback) : false;
132+
if (!handled) base.SetEventListener(eventName, callback);
133+
return;
134+
}
135+
}
136+
}
137+
}

Diff for: Runtime/Frameworks/UGUI/Components/PrefabComponent.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Runtime/Frameworks/UGUI/Components/UGUIComponent.cs

+6
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@ protected override void ApplyStylesSelf()
129129
UpdateBackgroundGraphic(false, true);
130130
}
131131

132+
public override void Relayout()
133+
{
134+
Layout.MarkDirty();
135+
Context.ScheduleLayout();
136+
}
137+
132138
#endregion
133139

134140

Diff for: Runtime/Frameworks/UGUI/General.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Runtime/Frameworks/UGUI/General/PrefabTarget.cs

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using ReactUnity.Helpers;
2+
using System;
3+
using UnityEngine.Events;
4+
using UnityEngine.EventSystems;
5+
6+
namespace ReactUnity.UGUI
7+
{
8+
public class PrefabTarget : UIBehaviour, IPrefabTarget
9+
{
10+
public PrefabComponent MountedTo;
11+
public PrefabEvent OnMount;
12+
public PrefabEvent OnUnmount;
13+
public SetPropertyEvent OnSetProperty;
14+
public SetEventListenerEvent OnSetEventListener;
15+
16+
protected override void OnRectTransformDimensionsChange()
17+
{
18+
MountedTo?.Relayout();
19+
}
20+
21+
public virtual void Mount(PrefabComponent cmp)
22+
{
23+
MountedTo = cmp;
24+
OnMount?.Invoke(cmp, this);
25+
}
26+
27+
public virtual void Unmount(PrefabComponent cmp)
28+
{
29+
OnUnmount?.Invoke(cmp, this);
30+
MountedTo = null;
31+
}
32+
33+
public virtual bool SetEventListener(string eventName, Callback callback)
34+
{
35+
OnSetEventListener?.Invoke(eventName, callback);
36+
return OnSetEventListener != null;
37+
}
38+
39+
public virtual bool SetProperty(string propertyName, object value)
40+
{
41+
OnSetProperty?.Invoke(propertyName, value);
42+
return OnSetProperty != null;
43+
}
44+
45+
[Serializable]
46+
public class PrefabEvent : UnityEvent<PrefabComponent, PrefabTarget> { }
47+
}
48+
49+
public interface IPrefabTarget
50+
{
51+
/* Return true to notify that this property is handled */
52+
bool SetProperty(string propertyName, object value);
53+
54+
/* Return true to notify that this event is handled */
55+
bool SetEventListener(string eventName, Callback callback);
56+
57+
/* Callback called when the target is mounted on the component */
58+
void Mount(PrefabComponent cmp);
59+
60+
/* Callback called when the target is unmounted off the component */
61+
void Unmount(PrefabComponent cmp);
62+
}
63+
}

Diff for: Runtime/Frameworks/UGUI/General/PrefabTarget.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Runtime/Frameworks/UGUI/Layout/TextMeasurer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class TextMeasurer : MonoBehaviour, ILayoutSelfController
1414
public YogaNode Layout;
1515
public UGUIContext Context;
1616

17-
private void Start()
17+
void Start()
1818
{
1919
if (Layout == null) DestroyImmediate(this);
2020
}

Diff for: Runtime/Frameworks/UGUI/ReactUnity.UGUI.asmdef

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ReactUnity.UGUI",
3-
"rootNamespace": "",
3+
"rootNamespace": "ReactUnity.UGUI",
44
"references": [
55
"GUID:ed4a920eac98dc342ba18a6baa202849",
66
"GUID:6055be8ebefd69e48b49212b09b47b2f"

Diff for: Runtime/Frameworks/UGUI/UGUIContext.cs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public class UGUIContext : ReactContext
2727
{ "render", (tag, text, context) => new RenderComponent(context) },
2828
{ "object", (tag, text, context) => new ObjectComponent(context) },
2929
{ "video", (tag, text, context) => new VideoComponent(context) },
30+
{ "prefab", (tag, text, context) => new PrefabComponent(context) },
3031
};
3132

3233

Diff for: Runtime/Frameworks/UIToolkit/Components/UIToolkitComponent.cs

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ public override void DestroySelf()
229229
Element.RemoveFromHierarchy();
230230
}
231231

232+
public override void Relayout() { }
233+
232234
#region Setters
233235

234236
public override void SetEventListener(string eventName, Callback fun)

Diff for: Runtime/Helpers/Callback.cs

+24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,30 @@ public class Callback
1616
public object callback;
1717
public Engine Engine;
1818

19+
public static Callback From(object value, bool checkCallable = false)
20+
{
21+
if (value == null) return null;
22+
23+
#if UNITY_EDITOR
24+
checkCallable = true;
25+
#endif
26+
if (checkCallable)
27+
{
28+
var callable =
29+
(value as JsValue)?.As<FunctionInstance>() != null ||
30+
#if REACT_CLEARSCRIPT
31+
(value is Microsoft.ClearScript.ScriptObject so) ||
32+
#endif
33+
(value is Delegate) ||
34+
(value is Callback);
35+
36+
if (!callable) throw new Exception("Provided callback is not callable");
37+
}
38+
39+
if (value is Callback cb) return cb;
40+
return new Callback(value);
41+
}
42+
1943
public Callback(Func<JsValue, JsValue[], JsValue> callback, Engine engine)
2044
{
2145
this.callback = callback;

Diff for: Runtime/Helpers/Events.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
using UnityEngine.Events;
3+
4+
namespace ReactUnity.Helpers
5+
{
6+
[Serializable]
7+
public class SetPropertyEvent : UnityEvent<string, object> { }
8+
9+
[Serializable]
10+
public class SetEventListenerEvent : UnityEvent<string, Callback> { }
11+
}

Diff for: Runtime/Helpers/Events.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Tests/Runtime/Base/IntroTests.cs

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using ReactUnity.ScriptEngine;
55
using UnityEngine;
66
using UnityEngine.TestTools;
7-
using UnityEngine.UI;
87

98
namespace ReactUnity.Tests
109
{

Diff for: Tests/Runtime/Base/TestBase.cs

+2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ public TestBase(JavascriptEngineType engineType)
2828
{
2929
EngineType = engineType;
3030
}
31+
32+
public void Render() => Component.Render();
3133
}
3234
}

0 commit comments

Comments
 (0)