Client LuaCsForBarotrauma
BarotraumaClient/ClientSource/Items/Components/Signal/CustomInterface.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.ComponentModel;
6 using System.Linq;
7 
9 {
10  partial class CustomInterface
11  {
12  private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
13  private GUILayoutGroup uiElementContainer;
14 
15  private bool readingNetworkEvent;
16 
17  private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
18 
19  public override bool RecreateGUIOnResolutionChange => true;
20 
21  partial void InitProjSpecific()
22  {
23  CreateGUI();
24  }
25 
26  protected override void CreateGUI()
27  {
28  uiElements.Clear();
29  var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label));
30  uiElementContainer = new GUILayoutGroup(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
31  {
32  AbsoluteOffset = GUIStyle.ItemFrameOffset
33  },
34  childAnchor: customInterfaceElementList.Count > 1 ? Anchor.TopCenter : Anchor.Center)
35  {
36  RelativeSpacing = 0.05f,
37  Stretch = visibleElements.Count() > 2,
38  };
39 
40  float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
41  foreach (CustomInterfaceElement ciElement in visibleElements)
42  {
43  if (ciElement.HasPropertyName)
44  {
45  var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform), isHorizontal: true)
46  {
47  RelativeSpacing = 0.02f,
48  UserData = ciElement
49  };
50  new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
51  TextManager.Get(ciElement.Label).Fallback(ciElement.Label));
52  if (!ciElement.IsNumberInput)
53  {
54  var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), ciElement.Signal, style: "GUITextBoxNoIcon")
55  {
56  OverflowClip = true,
57  UserData = ciElement,
58  MaxTextLength = ciElement.MaxTextLength
59  };
60  //reset size restrictions set by the Style to make sure the elements can fit the interface
61  textBox.RectTransform.MinSize = textBox.Frame.RectTransform.MinSize = new Point(0, 0);
62  textBox.RectTransform.MaxSize = textBox.Frame.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
63  textBox.OnDeselected += (tb, key) =>
64  {
65  if (GameMain.Client == null)
66  {
67  TextChanged(tb.UserData as CustomInterfaceElement, textBox.Text);
68  }
69  else
70  {
71  item.CreateClientEvent(this);
72  }
73  };
74 
75  textBox.OnEnterPressed += (tb, text) =>
76  {
77  tb.Deselect();
78  return true;
79  };
80  uiElements.Add(textBox);
81  }
82  else
83  {
84  GUINumberInput numberInput = null;
85  if (ciElement.NumberType == NumberType.Float)
86  {
87  TryParseFloatInvariantCulture(ciElement.Signal, out float floatSignal);
88  TryParseFloatInvariantCulture(ciElement.NumberInputMin, out float numberInputMin);
89  TryParseFloatInvariantCulture(ciElement.NumberInputMax, out float numberInputMax);
90  TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
91  numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Float)
92  {
93  UserData = ciElement,
94  MinValueFloat = numberInputMin,
95  MaxValueFloat = numberInputMax,
96  FloatValue = Math.Clamp(floatSignal, numberInputMin, numberInputMax),
97  DecimalsToDisplay = ciElement.NumberInputDecimalPlaces,
98  ValueStep = numberInputStep,
99  OnValueChanged = (ni) =>
100  {
101  if (GameMain.Client == null)
102  {
103  ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
104  }
105  else if (!readingNetworkEvent)
106  {
107  item.CreateClientEvent(this);
108  }
109  }
110  };
111  }
112  else if (ciElement.NumberType == NumberType.Int)
113  {
114  int.TryParse(ciElement.Signal, out int intSignal);
115  int.TryParse(ciElement.NumberInputMin, out int numberInputMin);
116  int.TryParse(ciElement.NumberInputMax, out int numberInputMax);
117  TryParseFloatInvariantCulture(ciElement.NumberInputStep, out float numberInputStep);
118  numberInput = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), NumberType.Int)
119  {
120  UserData = ciElement,
121  MinValueInt = numberInputMin,
122  MaxValueInt = numberInputMax,
123  IntValue = Math.Clamp(intSignal, numberInputMin, numberInputMax),
124  ValueStep = numberInputStep,
125  OnValueChanged = (ni) =>
126  {
127  if (GameMain.Client == null)
128  {
129  ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
130  }
131  else if (!readingNetworkEvent)
132  {
133  item.CreateClientEvent(this);
134  }
135  }
136  };
137  }
138  else
139  {
140  DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\"",
141  contentPackage: item.Prefab.ContentPackage);
142  }
143  if (numberInput != null)
144  {
145  //reset size restrictions set by the Style to make sure the elements can fit the interface
146  numberInput.RectTransform.MinSize = numberInput.LayoutGroup.RectTransform.MinSize = new Point(0, 0);
147  numberInput.RectTransform.MaxSize = numberInput.LayoutGroup.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
148  uiElements.Add(numberInput);
149  }
150  }
151  }
152  else if (ciElement.ContinuousSignal)
153  {
154  var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
155  {
156  MaxSize = ElementMaxSize
157  }, TextManager.Get(ciElement.Label).Fallback(ciElement.Label))
158  {
159  UserData = ciElement
160  };
161  tickBox.OnSelected += (tBox) =>
162  {
163  if (GameMain.Client == null)
164  {
165  TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
166  }
167  else if (!readingNetworkEvent)
168  {
169  item.CreateClientEvent(this);
170  }
171  return true;
172  };
173  //reset size restrictions set by the Style to make sure the elements can fit the interface
174  tickBox.RectTransform.MinSize = new Point(0, 0);
175  tickBox.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
176  uiElements.Add(tickBox);
177  }
178  else
179  {
180  var btn = new GUIButton(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform),
181  TextManager.Get(ciElement.Label).Fallback(ciElement.Label), style: "DeviceButton")
182  {
183  UserData = ciElement
184  };
185  btn.OnClicked += (_, userdata) =>
186  {
187  CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;
188  if (GameMain.Client == null)
189  {
190  ButtonClicked(btnElement);
191  }
192  else if (!readingNetworkEvent)
193  {
194  item.CreateClientEvent(this, new EventData(btnElement));
195  }
196  return true;
197  };
198 
199  //reset size restrictions set by the Style to make sure the elements can fit the interface
200  btn.RectTransform.MinSize = btn.Frame.RectTransform.MinSize = new Point(0, 0);
201  btn.RectTransform.MaxSize = btn.Frame.RectTransform.MaxSize = ElementMaxSize;
202 
203  uiElements.Add(btn);
204  }
205  }
206  }
207 
208  public override void CreateEditingHUD(SerializableEntityEditor editor)
209  {
210  base.CreateEditingHUD(editor);
211 
212  if (customInterfaceElementList.Count > 0)
213  {
214  PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(customInterfaceElementList[0]);
215  PropertyDescriptor labelProperty = properties.Find("Label", false);
216  PropertyDescriptor signalProperty = properties.Find("Signal", false);
217  for (int i = 0; i < customInterfaceElementList.Count; i++)
218  {
219  editor.CreateStringField(customInterfaceElementList[i],
220  new SerializableProperty(labelProperty),
221  customInterfaceElementList[i].Label, "Label #" + (i + 1), "");
222  editor.CreateStringField(customInterfaceElementList[i],
223  new SerializableProperty(signalProperty),
224  customInterfaceElementList[i].Signal, "Signal #" + (i + 1), "");
225  }
226  }
227  }
228 
229  public void HighlightElement(int index, Color color, float duration, float pulsateAmount = 0.0f)
230  {
231  if (index < 0 || index >= uiElements.Count) { return; }
232  uiElements[index].Flash(color, duration);
233 
234  if (pulsateAmount > 0.0f)
235  {
236  if (uiElements[index] is GUIButton button)
237  {
238  button.Frame.Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
239  button.Frame.RectTransform.SetPosition(Anchor.Center);
240  }
241  else
242  {
243  uiElements[index].Pulsate(Vector2.One, Vector2.One * (1.0f + pulsateAmount), duration);
244  }
245  }
246  }
247 
248  public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
249  {
250  bool elementVisibilityChanged = false;
251  int visibleElementCount = 0;
252  foreach (var uiElement in uiElements)
253  {
254  if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
255  bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
256  if (visible) { visibleElementCount++; }
257  if (uiElement.Visible != visible)
258  {
259  uiElement.Visible = visible;
260  uiElement.IgnoreLayoutGroups = !uiElement.Visible;
261  elementVisibilityChanged = true;
262  }
263  }
264 
265  if (elementVisibilityChanged)
266  {
267  uiElementContainer.Stretch = visibleElementCount > 2;
268  uiElementContainer.ChildAnchor = visibleElementCount > 1 ? Anchor.TopCenter : Anchor.Center;
269  float elementSize = Math.Min(1.0f / visibleElementCount, 1);
270  foreach (var uiElement in uiElements)
271  {
272  uiElement.RectTransform.RelativeSize = new Vector2(1.0f, elementSize);
273  }
274  GuiFrame.Visible = visibleElementCount > 0;
275  uiElementContainer.Recalculate();
276  }
277  }
278 
279  partial void UpdateLabelsProjSpecific()
280  {
281  for (int i = 0; i < labels.Length && i < uiElements.Count; i++)
282  {
283  if (uiElements[i] is GUIButton button)
284  {
285  button.Text = CreateLabelText(i);
286  button.TextBlock.Wrap = button.Text.Contains(' ');
287  }
288  else if (uiElements[i] is GUITickBox tickBox)
289  {
290  tickBox.Text = CreateLabelText(i);
291  tickBox.TextBlock.Wrap = tickBox.Text.Contains(' ');
292  }
293  else if (uiElements[i] is GUITextBox || uiElements[i] is GUINumberInput)
294  {
295  var textBlock = uiElements[i].Parent.GetChild<GUITextBlock>();
296  textBlock.Text = CreateLabelText(i);
297  textBlock.Wrap = textBlock.Text.Contains(' ');
298  }
299  }
300 
301  LocalizedString CreateLabelText(int elementIndex)
302  {
303  var label = customInterfaceElementList[elementIndex].Label;
304  return string.IsNullOrWhiteSpace(label) ?
305  TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
306  TextManager.Get(label).Fallback(label);
307  }
308 
309  uiElementContainer.Recalculate();
310  var textBlocks = new List<GUITextBlock>();
311  foreach (GUIComponent element in uiElementContainer.Children)
312  {
313  if (element is GUIButton btn)
314  {
315  if (btn.TextBlock.TextSize.Y > btn.Rect.Height - btn.TextBlock.Padding.Y - btn.TextBlock.Padding.W)
316  {
317  btn.RectTransform.RelativeSize = new Vector2(btn.RectTransform.RelativeSize.X, btn.RectTransform.RelativeSize.Y * 1.5f);
318  }
319  textBlocks.Add(btn.TextBlock);
320  }
321  else if (element is GUITickBox tickBox)
322  {
323  textBlocks.Add(tickBox.TextBlock);
324  }
325  else if (element is GUILayoutGroup)
326  {
327  textBlocks.Add(element.GetChild<GUITextBlock>());
328  }
329  }
330  uiElementContainer.Recalculate();
331  GUITextBlock.AutoScaleAndNormalize(textBlocks);
332  }
333 
334  partial void UpdateSignalsProjSpecific()
335  {
336  if (signals == null) { return; }
337  for (int i = 0; i < signals.Length && i < uiElements.Count; i++)
338  {
339  string signal = customInterfaceElementList[i].Signal;
340  if (uiElements[i] is GUITextBox tb)
341  {
342  tb.Text = Screen.Selected is { IsEditor: true } ?
343  signal :
344  TextManager.Get(signal).Fallback(signal).Value;
345  }
346  else if (uiElements[i] is GUINumberInput ni)
347  {
348  if (ni.InputType == NumberType.Int)
349  {
350  int.TryParse(signal, out int value);
351  ni.IntValue = value;
352  }
353  }
354  }
355  }
356 
357  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
358  {
359  //extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
360  for (int i = 0; i < customInterfaceElementList.Count; i++)
361  {
362  var element = customInterfaceElementList[i];
363  if (element.HasPropertyName)
364  {
365  if (!element.IsNumberInput)
366  {
367  msg.WriteString(((GUITextBox)uiElements[i]).Text);
368  }
369  else
370  {
371  switch (element.NumberType)
372  {
373  case NumberType.Float:
374  msg.WriteString(((GUINumberInput)uiElements[i]).FloatValue.ToString());
375  break;
376  case NumberType.Int:
377  default:
378  msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
379  break;
380  }
381  }
382  }
383  else if (element.ContinuousSignal)
384  {
385  msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
386  }
387  else
388  {
389  msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
390  }
391  }
392  }
393 
394  public void ClientEventRead(IReadMessage msg, float sendingTime)
395  {
396  readingNetworkEvent = true;
397  try
398  {
399  for (int i = 0; i < customInterfaceElementList.Count; i++)
400  {
401  var element = customInterfaceElementList[i];
402  if (element.HasPropertyName)
403  {
404  string newValue = msg.ReadString();
405  if (!element.IsNumberInput)
406  {
407  TextChanged(element, newValue);
408  }
409  else
410  {
411  switch (element.NumberType)
412  {
413  case NumberType.Int when int.TryParse(newValue, out int value):
414  ValueChanged(element, value);
415  break;
416  case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
417  ValueChanged(element, value);
418  break;
419  }
420  }
421  }
422  else
423  {
424  bool elementState = msg.ReadBoolean();
425  if (element.ContinuousSignal)
426  {
427  ((GUITickBox)uiElements[i]).Selected = elementState;
428  TickBoxToggled(element, elementState);
429  }
430  else if (elementState)
431  {
432  ButtonClicked(element);
433  }
434  }
435  }
436 
437  UpdateSignalsProjSpecific();
438  }
439  finally
440  {
441  readingNetworkEvent = false;
442  }
443  }
444  }
445 }
OnClickedHandler OnClicked
Definition: GUIButton.cs:16
virtual Rectangle Rect
RectTransform RectTransform
OnSelectedHandler OnSelected
Definition: GUITickBox.cs:13
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static GameClient Client
Definition: GameMain.cs:188
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
void HighlightElement(int index, Color color, float duration, float pulsateAmount=0.0f)
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
Defines a point in the event that GoTo actions can jump to.
Definition: Label.cs:7
ContentPackage? ContentPackage
Definition: Prefab.cs:37
Point?? MinSize
Min size in pixels. Does not affect scaling.
Point?? MaxSize
Max size in pixels. Does not affect scaling.
GUIComponent CreateStringField(ISerializableEntity entity, SerializableProperty property, string value, LocalizedString displayName, LocalizedString toolTip)
NumberType
Definition: Enums.cs:715