514 lines
24 KiB
C#
514 lines
24 KiB
C#
using Barotrauma.Networking;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
partial class CustomInterface
|
|
{
|
|
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
|
|
private GUILayoutGroup uiElementContainer;
|
|
|
|
private bool suppressNetworkEvents;
|
|
|
|
private GUIComponent insufficientPowerWarning;
|
|
|
|
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
|
|
|
|
public override bool RecreateGUIOnResolutionChange => true;
|
|
|
|
partial void InitProjSpecific()
|
|
{
|
|
CreateGUI();
|
|
}
|
|
|
|
protected override void CreateGUI()
|
|
{
|
|
uiElements.Clear();
|
|
var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label));
|
|
uiElementContainer = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
|
{
|
|
AbsoluteOffset = GUIStyle.ItemFrameOffset
|
|
},
|
|
childAnchor: customInterfaceElementList.Count > 1 ? Anchor.TopCenter : Anchor.Center)
|
|
{
|
|
RelativeSpacing = 0.05f,
|
|
Stretch = visibleElements.Count() > 2,
|
|
};
|
|
|
|
float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
|
|
foreach (CustomInterfaceElement ciElement in visibleElements)
|
|
{
|
|
if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Number or CustomInterfaceElement.InputTypeOption.Text)
|
|
{
|
|
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform), isHorizontal: true)
|
|
{
|
|
RelativeSpacing = 0.02f,
|
|
UserData = ciElement
|
|
};
|
|
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
|
|
TextManager.Get(ciElement.Label).Fallback(ciElement.Label));
|
|
if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Text)
|
|
{
|
|
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
|
|
{
|
|
OverflowClip = true,
|
|
UserData = ciElement,
|
|
MaxTextLength = ciElement.MaxTextLength
|
|
};
|
|
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
|
textBox.RectTransform.MinSize = textBox.Frame.RectTransform.MinSize = new Point(0, 0);
|
|
textBox.RectTransform.MaxSize = textBox.Frame.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
|
|
textBox.OnDeselected += (tb, key) =>
|
|
{
|
|
if (GameMain.Client == null)
|
|
{
|
|
TextChanged(tb.UserData as CustomInterfaceElement, textBox.Text);
|
|
}
|
|
else
|
|
{
|
|
CreateClientEventWithCorrectionDelay();
|
|
}
|
|
};
|
|
|
|
textBox.OnEnterPressed += (tb, text) =>
|
|
{
|
|
tb.Deselect();
|
|
return true;
|
|
};
|
|
uiElements.Add(textBox);
|
|
}
|
|
else
|
|
{
|
|
GUINumberInput numberInput = null;
|
|
if (ciElement.NumberType == NumberType.Float)
|
|
{
|
|
TryParseFloatInvariantCulture(ciElement.Signal, out float floatSignal);
|
|
TryParseFloatInvariantCulture(ciElement.NumberInputMin, out float numberInputMin);
|
|
TryParseFloatInvariantCulture(ciElement.NumberInputMax, out float numberInputMax);
|
|
TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
|
|
numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Float)
|
|
{
|
|
UserData = ciElement,
|
|
MinValueFloat = numberInputMin,
|
|
MaxValueFloat = numberInputMax,
|
|
FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax),
|
|
DecimalsToDisplay = ciElement.NumberInputDecimalPlaces,
|
|
ValueStep = numberInputStep,
|
|
OnValueChanged = (ni) =>
|
|
{
|
|
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
|
|
if (!suppressNetworkEvents && GameMain.Client != null)
|
|
{
|
|
CreateClientEventWithCorrectionDelay();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
else if (ciElement.NumberType == NumberType.Int)
|
|
{
|
|
int.TryParse(ciElement.Signal, out int intSignal);
|
|
int.TryParse(ciElement.NumberInputMin, out int numberInputMin);
|
|
int.TryParse(ciElement.NumberInputMax, out int numberInputMax);
|
|
TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
|
|
numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Int)
|
|
{
|
|
UserData = ciElement,
|
|
MinValueInt = numberInputMin,
|
|
MaxValueInt = numberInputMax,
|
|
IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax),
|
|
ValueStep = numberInputStep,
|
|
OnValueChanged = (ni) =>
|
|
{
|
|
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
|
|
if (!suppressNetworkEvents && GameMain.Client != null)
|
|
{
|
|
CreateClientEventWithCorrectionDelay();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\"",
|
|
contentPackage: item.Prefab.ContentPackage);
|
|
}
|
|
if (numberInput != null)
|
|
{
|
|
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
|
numberInput.RectTransform.MinSize = numberInput.LayoutGroup.RectTransform.MinSize = new Point(0, 0);
|
|
numberInput.RectTransform.MaxSize = numberInput.LayoutGroup.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
|
|
uiElements.Add(numberInput);
|
|
}
|
|
}
|
|
}
|
|
else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.TickBox)
|
|
{
|
|
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
|
|
{
|
|
MaxSize = ElementMaxSize
|
|
}, TextManager.Get(ciElement.Label).Fallback(ciElement.Label))
|
|
{
|
|
UserData = ciElement
|
|
};
|
|
tickBox.OnSelected += (tBox) =>
|
|
{
|
|
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
|
|
if (!suppressNetworkEvents && GameMain.Client != null)
|
|
{
|
|
CreateClientEventWithCorrectionDelay();
|
|
}
|
|
return true;
|
|
};
|
|
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
|
tickBox.RectTransform.MinSize = new Point(0, 0);
|
|
tickBox.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
|
|
uiElements.Add(tickBox);
|
|
}
|
|
else if (ciElement.InputType is CustomInterfaceElement.InputTypeOption.Button)
|
|
{
|
|
var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform),
|
|
TextManager.Get(ciElement.Label).Fallback(ciElement.Label), style: "DeviceButton")
|
|
{
|
|
UserData = ciElement
|
|
};
|
|
btn.OnClicked += (_, userdata) =>
|
|
{
|
|
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;
|
|
if (GameMain.Client == null)
|
|
{
|
|
ButtonClicked(btnElement);
|
|
}
|
|
else if (!suppressNetworkEvents && GameMain.Client != null)
|
|
{
|
|
//don't use CreateClientEventWithCorrectionDelay here, because buttons have no state,
|
|
//which means we don't need to worry about server updates interfering with client-side changes to the values in the interface
|
|
item.CreateClientEvent(this, new EventData(btnElement));
|
|
}
|
|
return true;
|
|
};
|
|
|
|
//reset size restrictions set by the Style to make sure the elements can fit the interface
|
|
btn.RectTransform.MinSize = btn.Frame.RectTransform.MinSize = new Point(0, 0);
|
|
btn.RectTransform.MaxSize = btn.Frame.RectTransform.MaxSize = ElementMaxSize;
|
|
|
|
uiElements.Add(btn);
|
|
}
|
|
}
|
|
|
|
if (ShowInsufficientPowerWarning)
|
|
{
|
|
insufficientPowerWarning = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), GuiFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { MinSize = new Point(0, GUI.IntScale(30)) },
|
|
TextManager.Get("SteeringNoPowerTip"), font: GUIStyle.Font, wrap: true, style: "GUIToolTip", textAlignment: Alignment.Center)
|
|
{
|
|
AutoScaleHorizontal = true,
|
|
Visible = false
|
|
};
|
|
}
|
|
|
|
void CreateClientEventWithCorrectionDelay()
|
|
{
|
|
item.CreateClientEvent(this);
|
|
correctionTimer = CorrectionDelay;
|
|
}
|
|
}
|
|
|
|
public override void CreateEditingHUD(SerializableEntityEditor editor)
|
|
{
|
|
base.CreateEditingHUD(editor);
|
|
|
|
if (customInterfaceElementList.Count > 0)
|
|
{
|
|
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(customInterfaceElementList[0]);
|
|
PropertyDescriptor labelProperty = properties.Find("Label", false);
|
|
PropertyDescriptor signalProperty = properties.Find("Signal", false);
|
|
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
|
{
|
|
editor.CreateStringField(customInterfaceElementList[i],
|
|
new SerializableProperty(labelProperty),
|
|
customInterfaceElementList[i].Label, "Label #" + (i + 1), "");
|
|
editor.CreateStringField(customInterfaceElementList[i],
|
|
new SerializableProperty(signalProperty),
|
|
customInterfaceElementList[i].Signal, "Signal #" + (i + 1), "");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void HighlightElement(int index, Color color, float duration, float pulsateAmount = 0.0f)
|
|
{
|
|
if (index < 0 || index >= uiElements.Count) { return; }
|
|
uiElements[index].Flash(color, duration);
|
|
|
|
if (pulsateAmount > 0.0f)
|
|
{
|
|
if (uiElements[index] is GUIButton button)
|
|
{
|
|
button.Frame.Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
|
|
button.Frame.RectTransform.SetPosition(Anchor.Center);
|
|
}
|
|
else
|
|
{
|
|
uiElements[index].Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
|
|
{
|
|
bool elementVisibilityChanged = false;
|
|
int visibleElementCount = 0;
|
|
foreach (var uiElement in uiElements)
|
|
{
|
|
if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
|
|
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
|
|
if (visible)
|
|
{
|
|
visibleElementCount++;
|
|
if (element.GetValueInterval > 0.0f && correctionTimer <= 0.0f)
|
|
{
|
|
element.GetValueTimer -= deltaTime;
|
|
if (element.GetValueTimer <= 0.0f)
|
|
{
|
|
SetSignalToPropertyValue(element);
|
|
UpdateSignalProjSpecific(uiElement);
|
|
element.GetValueTimer = element.GetValueInterval;
|
|
}
|
|
}
|
|
}
|
|
if (uiElement.Visible != visible)
|
|
{
|
|
uiElement.Visible = visible;
|
|
uiElement.IgnoreLayoutGroups = !uiElement.Visible;
|
|
elementVisibilityChanged = true;
|
|
}
|
|
}
|
|
|
|
if (elementVisibilityChanged)
|
|
{
|
|
uiElementContainer.Stretch = visibleElementCount > 2;
|
|
uiElementContainer.ChildAnchor = visibleElementCount > 1 ? Anchor.TopCenter : Anchor.Center;
|
|
float elementSize = Math.Min(1.0f / visibleElementCount, 1);
|
|
foreach (var uiElement in uiElements)
|
|
{
|
|
uiElement.RectTransform.RelativeSize = new Vector2(1.0f, elementSize);
|
|
}
|
|
GuiFrame.Visible = visibleElementCount > 0;
|
|
uiElementContainer.Recalculate();
|
|
}
|
|
|
|
if (insufficientPowerWarning != null)
|
|
{
|
|
insufficientPowerWarning.Visible = item.GetComponents<Powered>().Any(p => p.PowerConsumption > 0.0f && p.Voltage < p.MinVoltage);
|
|
}
|
|
}
|
|
|
|
partial void UpdateLabelsProjSpecific()
|
|
{
|
|
for (int i = 0; i < labels.Length && i < uiElements.Count; i++)
|
|
{
|
|
if (uiElements[i] is GUIButton button)
|
|
{
|
|
button.Text = CreateLabelText(i);
|
|
button.TextBlock.Wrap = button.Text.Contains(' ');
|
|
}
|
|
else if (uiElements[i] is GUITickBox tickBox)
|
|
{
|
|
tickBox.Text = CreateLabelText(i);
|
|
tickBox.TextBlock.Wrap = tickBox.Text.Contains(' ');
|
|
}
|
|
else if (uiElements[i] is GUITextBox || uiElements[i] is GUINumberInput)
|
|
{
|
|
var textBlock = uiElements[i].Parent.GetChild<GUITextBlock>();
|
|
textBlock.Text = CreateLabelText(i);
|
|
textBlock.Wrap = textBlock.Text.Contains(' ');
|
|
}
|
|
}
|
|
|
|
LocalizedString CreateLabelText(int elementIndex)
|
|
{
|
|
string label = customInterfaceElementList[elementIndex].Label;
|
|
return string.IsNullOrWhiteSpace(label) ?
|
|
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
|
|
TextManager.Get(label).Fallback(label);
|
|
}
|
|
|
|
uiElementContainer.Recalculate();
|
|
var textBlocks = new List<GUITextBlock>();
|
|
foreach (GUIComponent element in uiElementContainer.Children)
|
|
{
|
|
if (element is GUIButton btn)
|
|
{
|
|
if (btn.TextBlock.TextSize.Y > btn.Rect.Height - btn.TextBlock.Padding.Y - btn.TextBlock.Padding.W)
|
|
{
|
|
btn.RectTransform.RelativeSize = new Vector2(btn.RectTransform.RelativeSize.X, btn.RectTransform.RelativeSize.Y * 1.5f);
|
|
}
|
|
textBlocks.Add(btn.TextBlock);
|
|
}
|
|
else if (element is GUITickBox tickBox)
|
|
{
|
|
textBlocks.Add(tickBox.TextBlock);
|
|
}
|
|
else if (element is GUILayoutGroup)
|
|
{
|
|
textBlocks.Add(element.GetChild<GUITextBlock>());
|
|
}
|
|
}
|
|
uiElementContainer.Recalculate();
|
|
GUITextBlock.AutoScaleAndNormalize(textBlocks);
|
|
}
|
|
|
|
partial void UpdateSignalsProjSpecific()
|
|
{
|
|
if (signals == null) { return; }
|
|
for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
|
|
{
|
|
UpdateSignalProjSpecific(uiElements[i]);
|
|
}
|
|
}
|
|
|
|
private void UpdateSignalProjSpecific(GUIComponent uiElement)
|
|
{
|
|
if (uiElement.UserData is not CustomInterfaceElement element) { return; }
|
|
|
|
suppressNetworkEvents = true;
|
|
|
|
string signal = element.Signal;
|
|
if (uiElement is GUITextBox tb)
|
|
{
|
|
tb.Text = Screen.Selected is { IsEditor: true } ?
|
|
signal :
|
|
TextManager.Get(signal).Fallback(signal).Value;
|
|
}
|
|
else if (uiElement is GUINumberInput ni)
|
|
{
|
|
if (ni.InputType == NumberType.Int)
|
|
{
|
|
if (int.TryParse(signal, out int value))
|
|
{
|
|
ni.IntValue = value;
|
|
}
|
|
else if (float.TryParse(signal, out float floatValue))
|
|
{
|
|
ni.IntValue = (int)MathF.Round(floatValue);
|
|
}
|
|
}
|
|
}
|
|
else if (uiElement is GUITickBox tickBox)
|
|
{
|
|
tickBox.Selected = signal.Equals("true", StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
suppressNetworkEvents = false;
|
|
}
|
|
|
|
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
|
|
{
|
|
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
|
|
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
|
{
|
|
var element = customInterfaceElementList[i];
|
|
switch (element.InputType)
|
|
{
|
|
case CustomInterfaceElement.InputTypeOption.Number:
|
|
switch (element.NumberType)
|
|
{
|
|
case NumberType.Float:
|
|
msg.WriteString(((GUINumberInput)uiElements[i]).FloatValue.ToString());
|
|
break;
|
|
case NumberType.Int:
|
|
default:
|
|
msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
|
|
break;
|
|
}
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.Text:
|
|
msg.WriteString(((GUITextBox)uiElements[i]).Text);
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.TickBox:
|
|
msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.Button:
|
|
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
|
{
|
|
int msgStartPos = msg.BitPosition;
|
|
suppressNetworkEvents = true;
|
|
try
|
|
{
|
|
string[] stringValues = new string[customInterfaceElementList.Count];
|
|
bool[] boolValues = new bool[customInterfaceElementList.Count];
|
|
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
|
{
|
|
var element = customInterfaceElementList[i];
|
|
switch (element.InputType)
|
|
{
|
|
case CustomInterfaceElement.InputTypeOption.Number:
|
|
case CustomInterfaceElement.InputTypeOption.Text:
|
|
stringValues[i] = msg.ReadString();
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.TickBox:
|
|
case CustomInterfaceElement.InputTypeOption.Button:
|
|
boolValues[i] = msg.ReadBoolean();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (correctionTimer > 0.0f)
|
|
{
|
|
int msgLength = msg.BitPosition - msgStartPos;
|
|
msg.BitPosition = msgStartPos;
|
|
StartDelayedCorrection(msg.ExtractBits(msgLength), sendingTime);
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
|
{
|
|
var element = customInterfaceElementList[i];
|
|
switch (element.InputType)
|
|
{
|
|
case CustomInterfaceElement.InputTypeOption.Number:
|
|
switch (element.NumberType)
|
|
{
|
|
case NumberType.Int when int.TryParse(stringValues[i], out int value):
|
|
ValueChanged(element, value);
|
|
break;
|
|
case NumberType.Float when TryParseFloatInvariantCulture(stringValues[i], out float value):
|
|
ValueChanged(element, value);
|
|
break;
|
|
}
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.Text:
|
|
TextChanged(element, stringValues[i]);
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.TickBox:
|
|
bool tickBoxState = boolValues[i];
|
|
((GUITickBox)uiElements[i]).Selected = tickBoxState;
|
|
TickBoxToggled(element, tickBoxState);
|
|
break;
|
|
case CustomInterfaceElement.InputTypeOption.Button:
|
|
if (boolValues[i])
|
|
{
|
|
ButtonClicked(element);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
UpdateSignalsProjSpecific();
|
|
}
|
|
finally
|
|
{
|
|
suppressNetworkEvents = false;
|
|
}
|
|
}
|
|
}
|
|
}
|