Godot Version
Godot .NET 4.4
Question
I’m making some custom UI element in Godot, One is a container with some pre-added child controls. a strange thing happened that some kinds of the child will lost their binding after the UI scene run. all the children are binded with [Export]
label.
After a run, the panel at right shows they lost their binding
Here is the code of one kind of Control that will lost binding after running the scene:
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static LabeledInputField;
public partial class MotionEventExpandable : Control
{
//Control objects
[Export]
public ScrollContainer UIContainer;
[Export]
public VBoxContainer UIAutoResizeContainer;
[Export]
public VBoxContainer UINonChildContainer;
[Export]
public Clickable UIHeader;
[Export]
public Label UIHeaderLabel;
[Export]
public Line2D UISeparateLine;
[Export]
public VBoxContainer UIChildContainer;
[Export]
public LabeledInputField UIStartTime;
[Export]
public LabeledInputField UIEndTime;
[Export]
public LabeledInputField UIStartBeat;
[Export]
public LabeledInputField UIEndBeat;
[Export]
public LabeledVector3InputField UITargetRelatiaveVector3;
[Export]
public LabeledEventTypeDropDown UIEventType;
[Export]
public LabeledEasingDropDown UIEasing;
//Public settings
[Export]
public bool Animate = true;
[Export]
public float AnimationDuration = 0.3f;
//Signals
//UI related events
[Signal]
public delegate void ChildChangedEventHandler();
[Signal]
public delegate void SizeChangedEventHandler();
[Signal]
public delegate void IsExpandChangedEventHandler();
[Signal]
public delegate void ChildEnteredEventHandler();
[Signal]
public delegate void ChildExitedEventHandler();
[Signal]
public delegate void DataChangedEventHandler();
//Private value properties
private string _label = "Label";
private bool _isExpand;
private float _parentBPM = 60f;
private MRDataType.MREditorMotionEvent _data;
//Private UI properties
private float _height = UIPreset.DefaultControlHeight;
private float _width = UIPreset.DefaultControlWidth;
private int _fontSize = UIPreset.DefaultTextFieldFontSize;
//Private running properties
private Tween _tween;
//Public UI Properties
[Export]
public float Height
{
get => _height;
set
{
_height = value;
if (UIContainer != null)
{
UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
EmitSignal(nameof(SizeChangedEventHandler));
}
}
}
[Export]
public float Width
{
get => _width;
set
{
_width = value;
if (UIContainer != null)
{
UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
EmitSignal(SignalName.SizeChanged);
}
}
}
[Export]
public int FontSize
{
get => _fontSize;
set
{
_fontSize = value;
if (UIHeaderLabel != null)
{
UIHeaderLabel.AddThemeFontSizeOverride("font_size", value);
}
}
}
[Export]
public bool IsExpand
{
get => _isExpand;
set
{
if (_isExpand != value)
{
_isExpand = value;
EmitSignal(SignalName.IsExpandChanged);
}
}
}
//Public value properties
[Export]
public string Label
{
get => _label;
set
{
_label = value;
if (UIHeaderLabel != null)
{
UIHeaderLabel.Text = value;
}
}
}
public float ParentBPM
{
get => _parentBPM;
set
{
CheckTimeValue();
_parentBPM = value;
float BeatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float Endtime = float.Parse(UIStartTime.Text) + 60f / value * BeatSpan;
UIEndTime.Text = Endtime.ToString();
}
}
public MRDataType.MREditorMotionEvent Data
{
get => _data;
set
{
_data = value;
EmitSignal(nameof(DataChanged));
}
}
public override void _Ready()
{
UIStartTime.AllowedInputType = InputType.Float;
UIEndTime.AllowedInputType = InputType.Float;
UIStartBeat.AllowedInputType = InputType.Float;
UIEndBeat.AllowedInputType = InputType.Float;
//Register events
AddUserSignal(nameof(ChildChangedEventHandler));
AddUserSignal(nameof(SizeChangedEventHandler));
AddUserSignal(nameof(IsExpandChangedEventHandler));
AddUserSignal(nameof(ChildEnteredEventHandler));
AddUserSignal(nameof(ChildExitedEventHandler));
//Bind signals
UIHeader.Clicked += UIHeader_Clicked;
UIStartTime.TextChanged += UIStartTime_TextChanged;
UIEndTime.TextChanged += UIEndTime_TextChanged;
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
InitializeUI();
CheckTimeValue();
}
private void UIEndBeat_TextChanged()
{
CheckTimeValue();
UIEndTime.TextChanged -= UIEndBeat_TextChanged;
float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float timeSpan = beatSpan * 60f / ParentBPM;
UIEndTime.Text = (float.Parse(UIStartTime.Text) + timeSpan).ToString();
UIEndTime.TextChanged += UIEndBeat_TextChanged;
}
private void UIStartBeat_TextChanged()
{
CheckTimeValue();
UIStartTime.TextChanged -= UIStartBeat_TextChanged;
float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float timeSpan = beatSpan * 60f / ParentBPM;
UIStartTime.Text = (float.Parse(UIEndTime.Text) - timeSpan).ToString();
UIStartTime.TextChanged += UIStartBeat_TextChanged;
}
private void UIEndTime_TextChanged()
{
CheckTimeValue();
UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
float beatSpan = timeSpan / (ParentBPM / 60f);
UIEndBeat.Text = (float.Parse(UIStartBeat.Text) + beatSpan).ToString();
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
}
private void UIStartTime_TextChanged()
{
CheckTimeValue();
UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
float beatSpan = timeSpan / (ParentBPM / 60f);
UIStartBeat.Text = (float.Parse(UIEndBeat.Text) - beatSpan).ToString();
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
}
private void CheckTimeValue()
{
if (!float.TryParse(UIStartTime.Text, out float a))
{
UIStartTime.TextChanged -= UIStartTime_TextChanged;
UIStartTime.Text = "0";
UIStartTime.TextChanged += UIStartTime_TextChanged;
}
if (!float.TryParse(UIEndTime.Text, out float b))
{
UIEndTime.TextChanged -= UIEndTime_TextChanged;
UIEndTime.Text = "0";
UIEndTime.TextChanged += UIEndTime_TextChanged;
}
if (!float.TryParse(UIStartBeat.Text, out float c))
{
UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
UIStartBeat.Text = "0";
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
}
if (!float.TryParse(UIEndBeat.Text, out float d))
{
UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
UIEndBeat.Text = "0";
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
}
}
private void InitializeUI()
{
IsExpand = true;
UISeparateLine.SetPointPosition(0, new Vector2(0, 0));
UISeparateLine.SetPointPosition(1, new Vector2(UIContainer.Size.X, 0));
UIChildContainer.QueueRedraw();
UIStartTime.AllowedInputType = InputType.Float;
UIEndTime.AllowedInputType = InputType.Float;
UIStartBeat.AllowedInputType = InputType.Float;
UIEndBeat.AllowedInputType = InputType.Float;
}
//Public function
/// <summary>
/// Add child to Container
/// </summary>
/// <param name="node">Child control</param>
public void AddChild(Node node)
{
UIChildContainer.AddChild(node);
UIChildContainer.QueueSort();
}
/// <summary>
/// Insert child to Container
/// </summary>
/// <param name="node">Child control</param>
/// <param name="index">Insert index</param>
public void InsertChild(Node node, int index)
{
UIChildContainer.AddChild(node);
UIChildContainer.MoveChild(node, index);
UIChildContainer.QueueSort();
}
/// <summary>
/// Clear all children in Container
/// </summary>
public void ClearChild()
{
foreach (Node child in UIChildContainer.GetChildren())
{
UIChildContainer.RemoveChild(child);
}
}
/// <summary>
/// Remove target child
/// </summary>
/// <param name="node">Target child node</param>
public new void RemoveChild(Node node)
{
UIChildContainer.RemoveChild(node);
}
/// <summary>
/// Remove tarrget child with index
/// </summary>
/// <param name="index">Target child index</param>
public void RemoveChildFromIndex(int index)
{
foreach (Node child in UIChildContainer.GetChildren())
{
if (child.GetIndex() == index)
{
UIChildContainer.RemoveChild(child);
}
}
}
//Private functions
private void UIContainer_Resized()
{
EmitSignal(SignalName.SizeChanged);
}
private void UIHeader_Clicked()
{
IsExpand = !IsExpand;
ToggleExpand();
}
private void ToggleExpand()
{
if (IsExpand)
{
UpdateContentState();
}
if (_tween != null && _tween.IsValid())
{
_tween.Kill();
}
float targetHeight = IsExpand? UIAutoResizeContainer.Size.Y : UINonChildContainer.Size.Y;
Vector2 startVector2 = new Vector2(Mathf.Abs(UISeparateLine.Points[0].X - UISeparateLine.Points[1].X), 0);
Vector2 targetstartVector2 = new Vector2(IsExpand ? UIContainer.Size.X : 0, 0);
if (!Animate)
{
UIContainer.Size = new Vector2(_width, targetHeight);
return;
}
_tween = CreateTween()
.SetEase(Tween.EaseType.Out)
.SetTrans(Tween.TransitionType.Circ);
_tween.TweenProperty(UIContainer, "size", new Vector2(UIContainer.Size.X, targetHeight), AnimationDuration);
_tween.Parallel().TweenMethod(
Callable.From<Vector2>(vec =>
{
startVector2 = vec;
UISeparateLine.SetPointPosition(0, new Vector2(0.5f * UIContainer.Size.X - 0.5f * startVector2.X, 0));
UISeparateLine.SetPointPosition(1, new Vector2(0.5f * UIContainer.Size.X + 0.5f * startVector2.X, 0));
}),
startVector2,
targetstartVector2,
AnimationDuration
);
_tween.Finished += () =>
{
UpdateContentState();
};
}
private void UpdateContentState()
{
//Force update layout
if (IsExpand)
{
foreach (Control child in UIChildContainer.GetChildren())
{
child.Visible = IsExpand;
child.ProcessMode = IsExpand ?
ProcessModeEnum.Inherit : ProcessModeEnum.Disabled;
child.MouseFilter = IsExpand ?
Control.MouseFilterEnum.Stop : Control.MouseFilterEnum.Ignore;
child.QueueRedraw();
}
UIChildContainer.QueueRedraw();
}
}
}
Here is the code of the parent that having this issue:
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using static LabeledInputField;
public partial class MotionEventExpandable : Control
{
//Control objects
[Export]
public ScrollContainer UIContainer;
[Export]
public VBoxContainer UIAutoResizeContainer;
[Export]
public VBoxContainer UINonChildContainer;
[Export]
public Clickable UIHeader;
[Export]
public Label UIHeaderLabel;
[Export]
public Line2D UISeparateLine;
[Export]
public VBoxContainer UIChildContainer;
[Export]
public LabeledInputField UIStartTime;
[Export]
public LabeledInputField UIEndTime;
[Export]
public LabeledInputField UIStartBeat;
[Export]
public LabeledInputField UIEndBeat;
[Export]
public LabeledVector3InputField UITargetRelatiaveVector3;
[Export]
public LabeledEventTypeDropDown UIEventType;
[Export]
public LabeledEasingDropDown UIEasing;
//Public settings
[Export]
public bool Animate = true;
[Export]
public float AnimationDuration = 0.3f;
//Signals
//UI related events
[Signal]
public delegate void ChildChangedEventHandler();
[Signal]
public delegate void SizeChangedEventHandler();
[Signal]
public delegate void IsExpandChangedEventHandler();
[Signal]
public delegate void ChildEnteredEventHandler();
[Signal]
public delegate void ChildExitedEventHandler();
[Signal]
public delegate void DataChangedEventHandler();
//Private value properties
private string _label = "Label";
private bool _isExpand;
private float _parentBPM = 60f;
private MRDataType.MREditorMotionEvent _data;
//Private UI properties
private float _height = UIPreset.DefaultControlHeight;
private float _width = UIPreset.DefaultControlWidth;
private int _fontSize = UIPreset.DefaultTextFieldFontSize;
//Private running properties
private Tween _tween;
//Public UI Properties
[Export]
public float Height
{
get => _height;
set
{
_height = value;
if (UIContainer != null)
{
UIContainer.Size = new Vector2(UIContainer.Size.X, _height);
EmitSignal(nameof(SizeChangedEventHandler));
}
}
}
[Export]
public float Width
{
get => _width;
set
{
_width = value;
if (UIContainer != null)
{
UIContainer.Size = new Vector2(_width, UIContainer.Size.Y);
EmitSignal(SignalName.SizeChanged);
}
}
}
[Export]
public int FontSize
{
get => _fontSize;
set
{
_fontSize = value;
if (UIHeaderLabel != null)
{
UIHeaderLabel.AddThemeFontSizeOverride("font_size", value);
}
}
}
[Export]
public bool IsExpand
{
get => _isExpand;
set
{
if (_isExpand != value)
{
_isExpand = value;
EmitSignal(SignalName.IsExpandChanged);
}
}
}
//Public value properties
[Export]
public string Label
{
get => _label;
set
{
_label = value;
if (UIHeaderLabel != null)
{
UIHeaderLabel.Text = value;
}
}
}
public float ParentBPM
{
get => _parentBPM;
set
{
CheckTimeValue();
_parentBPM = value;
float BeatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float Endtime = float.Parse(UIStartTime.Text) + 60f / value * BeatSpan;
UIEndTime.Text = Endtime.ToString();
}
}
public MRDataType.MREditorMotionEvent Data
{
get => _data;
set
{
_data = value;
EmitSignal(nameof(DataChanged));
}
}
public override void _Ready()
{
UIStartTime.AllowedInputType = InputType.Float;
UIEndTime.AllowedInputType = InputType.Float;
UIStartBeat.AllowedInputType = InputType.Float;
UIEndBeat.AllowedInputType = InputType.Float;
//Register events
AddUserSignal(nameof(ChildChangedEventHandler));
AddUserSignal(nameof(SizeChangedEventHandler));
AddUserSignal(nameof(IsExpandChangedEventHandler));
AddUserSignal(nameof(ChildEnteredEventHandler));
AddUserSignal(nameof(ChildExitedEventHandler));
//Bind signals
UIHeader.Clicked += UIHeader_Clicked;
UIStartTime.TextChanged += UIStartTime_TextChanged;
UIEndTime.TextChanged += UIEndTime_TextChanged;
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
InitializeUI();
CheckTimeValue();
}
private void UIEndBeat_TextChanged()
{
CheckTimeValue();
UIEndTime.TextChanged -= UIEndBeat_TextChanged;
float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float timeSpan = beatSpan * 60f / ParentBPM;
UIEndTime.Text = (float.Parse(UIStartTime.Text) + timeSpan).ToString();
UIEndTime.TextChanged += UIEndBeat_TextChanged;
}
private void UIStartBeat_TextChanged()
{
CheckTimeValue();
UIStartTime.TextChanged -= UIStartBeat_TextChanged;
float beatSpan = float.Parse(UIEndBeat.Text) - float.Parse(UIStartBeat.Text);
float timeSpan = beatSpan * 60f / ParentBPM;
UIStartTime.Text = (float.Parse(UIEndTime.Text) - timeSpan).ToString();
UIStartTime.TextChanged += UIStartBeat_TextChanged;
}
private void UIEndTime_TextChanged()
{
CheckTimeValue();
UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
float beatSpan = timeSpan / (ParentBPM / 60f);
UIEndBeat.Text = (float.Parse(UIStartBeat.Text) + beatSpan).ToString();
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
}
private void UIStartTime_TextChanged()
{
CheckTimeValue();
UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
float timeSpan = float.Parse(UIEndTime.Text) - float.Parse(UIStartTime.Text);
float beatSpan = timeSpan / (ParentBPM / 60f);
UIStartBeat.Text = (float.Parse(UIEndBeat.Text) - beatSpan).ToString();
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
}
private void CheckTimeValue()
{
if (!float.TryParse(UIStartTime.Text, out float a))
{
UIStartTime.TextChanged -= UIStartTime_TextChanged;
UIStartTime.Text = "0";
UIStartTime.TextChanged += UIStartTime_TextChanged;
}
if (!float.TryParse(UIEndTime.Text, out float b))
{
UIEndTime.TextChanged -= UIEndTime_TextChanged;
UIEndTime.Text = "0";
UIEndTime.TextChanged += UIEndTime_TextChanged;
}
if (!float.TryParse(UIStartBeat.Text, out float c))
{
UIStartBeat.TextChanged -= UIStartBeat_TextChanged;
UIStartBeat.Text = "0";
UIStartBeat.TextChanged += UIStartBeat_TextChanged;
}
if (!float.TryParse(UIEndBeat.Text, out float d))
{
UIEndBeat.TextChanged -= UIEndBeat_TextChanged;
UIEndBeat.Text = "0";
UIEndBeat.TextChanged += UIEndBeat_TextChanged;
}
}
private void InitializeUI()
{
IsExpand = true;
UISeparateLine.SetPointPosition(0, new Vector2(0, 0));
UISeparateLine.SetPointPosition(1, new Vector2(UIContainer.Size.X, 0));
UIChildContainer.QueueRedraw();
UIStartTime.AllowedInputType = InputType.Float;
UIEndTime.AllowedInputType = InputType.Float;
UIStartBeat.AllowedInputType = InputType.Float;
UIEndBeat.AllowedInputType = InputType.Float;
}
//Public function
/// <summary>
/// Add child to Container
/// </summary>
/// <param name="node">Child control</param>
public void AddChild(Node node)
{
UIChildContainer.AddChild(node);
UIChildContainer.QueueSort();
}
/// <summary>
/// Insert child to Container
/// </summary>
/// <param name="node">Child control</param>
/// <param name="index">Insert index</param>
public void InsertChild(Node node, int index)
{
UIChildContainer.AddChild(node);
UIChildContainer.MoveChild(node, index);
UIChildContainer.QueueSort();
}
/// <summary>
/// Clear all children in Container
/// </summary>
public void ClearChild()
{
foreach (Node child in UIChildContainer.GetChildren())
{
UIChildContainer.RemoveChild(child);
}
}
/// <summary>
/// Remove target child
/// </summary>
/// <param name="node">Target child node</param>
public new void RemoveChild(Node node)
{
UIChildContainer.RemoveChild(node);
}
/// <summary>
/// Remove tarrget child with index
/// </summary>
/// <param name="index">Target child index</param>
public void RemoveChildFromIndex(int index)
{
foreach (Node child in UIChildContainer.GetChildren())
{
if (child.GetIndex() == index)
{
UIChildContainer.RemoveChild(child);
}
}
}
//Private functions
private void UIContainer_Resized()
{
EmitSignal(SignalName.SizeChanged);
}
private void UIHeader_Clicked()
{
IsExpand = !IsExpand;
ToggleExpand();
}
private void ToggleExpand()
{
if (IsExpand)
{
UpdateContentState();
}
if (_tween != null && _tween.IsValid())
{
_tween.Kill();
}
float targetHeight = IsExpand? UIAutoResizeContainer.Size.Y : UINonChildContainer.Size.Y;
Vector2 startVector2 = new Vector2(Mathf.Abs(UISeparateLine.Points[0].X - UISeparateLine.Points[1].X), 0);
Vector2 targetstartVector2 = new Vector2(IsExpand ? UIContainer.Size.X : 0, 0);
if (!Animate)
{
UIContainer.Size = new Vector2(_width, targetHeight);
return;
}
_tween = CreateTween()
.SetEase(Tween.EaseType.Out)
.SetTrans(Tween.TransitionType.Circ);
_tween.TweenProperty(UIContainer, "size", new Vector2(UIContainer.Size.X, targetHeight), AnimationDuration);
_tween.Parallel().TweenMethod(
Callable.From<Vector2>(vec =>
{
startVector2 = vec;
UISeparateLine.SetPointPosition(0, new Vector2(0.5f * UIContainer.Size.X - 0.5f * startVector2.X, 0));
UISeparateLine.SetPointPosition(1, new Vector2(0.5f * UIContainer.Size.X + 0.5f * startVector2.X, 0));
}),
startVector2,
targetstartVector2,
AnimationDuration
);
_tween.Finished += () =>
{
UpdateContentState();
};
}
private void UpdateContentState()
{
//Force update layout
if (IsExpand)
{
foreach (Control child in UIChildContainer.GetChildren())
{
child.Visible = IsExpand;
child.ProcessMode = IsExpand ?
ProcessModeEnum.Inherit : ProcessModeEnum.Disabled;
child.MouseFilter = IsExpand ?
Control.MouseFilterEnum.Stop : Control.MouseFilterEnum.Ignore;
child.QueueRedraw();
}
UIChildContainer.QueueRedraw();
}
}
}