Client LuaCsForBarotrauma
SerializableEntityEditor.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 
12 namespace Barotrauma
13 {
15  {
16  private readonly int elementHeight;
17  private readonly GUILayoutGroup layoutGroup;
18  private readonly float inputFieldWidth = 0.5f;
19  private readonly float largeInputFieldWidth = 0.8f;
20 #if DEBUG
21  public static List<string> MissingLocalizations = new List<string>();
22 #endif
23 
24  public static bool LockEditing;
25  public static bool PropertyChangesActive;
26  public static DateTime NextCommandPush;
27  public static Tuple<SerializableProperty, PropertyCommand> CommandBuffer;
28 
29  private bool isReadonly;
30  public bool Readonly
31  {
32  get => isReadonly;
33  set
34  {
35  foreach (var component in Fields.SelectMany(f => f.Value))
36  {
37  switch (component)
38  {
39  case GUINumberInput numInput:
40  numInput.Readonly = value;
41  break;
42  case GUITextBox textBox:
43  textBox.Readonly = value;
44  break;
45  default:
46  component.Enabled = !value;
47  break;
48  }
49  }
50  isReadonly = value;
51  }
52  }
53 
54  private Action refresh;
55 
56  public int ContentHeight
57  {
58  get
59  {
60  if (layoutGroup.NeedsToRecalculate) layoutGroup.Recalculate();
61 
62  int spacing = layoutGroup.CountChildren == 0 ? 0 : ((layoutGroup.CountChildren - 1) * layoutGroup.AbsoluteSpacing);
63  return spacing + layoutGroup.Children.Sum(c => c.RectTransform.NonScaledSize.Y);
64  }
65  }
66 
67  public int ContentCount
68  {
69  get { return layoutGroup.CountChildren; }
70  }
71 
75  public Dictionary<Identifier, GUIComponent[]> Fields { get; private set; } = new Dictionary<Identifier, GUIComponent[]>();
76 
77  public void UpdateValue(SerializableProperty property, object newValue, bool flash = true)
78  {
79  if (!Fields.TryGetValue(property.Name.ToIdentifier(), out GUIComponent[] fields))
80  {
81  DebugConsole.ThrowError($"No field for {property.Name} found!");
82  return;
83  }
84  if (newValue is float f)
85  {
86  foreach (var field in fields)
87  {
88  if (field is GUINumberInput numInput)
89  {
90  if (numInput.InputType == NumberType.Float)
91  {
92  numInput.FloatValue = f;
93  if (flash)
94  {
95  numInput.Flash(GUIStyle.Green);
96  }
97  }
98  }
99  }
100  }
101  else if (newValue is int integer)
102  {
103  foreach (var field in fields)
104  {
105  if (field is GUINumberInput numInput)
106  {
107  if (numInput.InputType == NumberType.Int)
108  {
109  numInput.IntValue = integer;
110  if (flash)
111  {
112  numInput.Flash(GUIStyle.Green);
113  }
114  }
115  }
116  }
117  }
118  else if (newValue is bool b)
119  {
120  if (fields[0] is GUITickBox tickBox)
121  {
122  tickBox.Selected = b;
123  if (flash)
124  {
125  tickBox.Flash(GUIStyle.Green);
126  }
127  }
128  }
129  else if (newValue is string s)
130  {
131  if (fields[0] is GUITextBox textBox)
132  {
133  textBox.Text = s;
134  if (flash)
135  {
136  textBox.Flash(GUIStyle.Green);
137  }
138  }
139  }
140  else if (newValue.GetType().IsEnum)
141  {
142  if (fields[0] is GUIDropDown dropDown)
143  {
144  dropDown.Select((int)newValue);
145  if (flash)
146  {
147  dropDown.Flash(GUIStyle.Green);
148  }
149  }
150  }
151  else if (newValue is Vector2 v2)
152  {
153  for (int i = 0; i < fields.Length; i++)
154  {
155  var field = fields[i];
156  if (field is GUINumberInput numInput)
157  {
158  if (numInput.InputType == NumberType.Float)
159  {
160  numInput.FloatValue = i == 0 ? v2.X : v2.Y;
161  if (flash)
162  {
163  numInput.Flash(GUIStyle.Green);
164  }
165  }
166  }
167  }
168  }
169  else if (newValue is Vector3 v3)
170  {
171  for (int i = 0; i < fields.Length; i++)
172  {
173  var field = fields[i];
174  if (field is GUINumberInput numInput)
175  {
176  if (numInput.InputType == NumberType.Float)
177  {
178  switch (i)
179  {
180  case 0:
181  numInput.FloatValue = v3.X;
182  break;
183  case 1:
184  numInput.FloatValue = v3.Y;
185  break;
186  case 2:
187  numInput.FloatValue = v3.Z;
188  break;
189  }
190  if (flash)
191  {
192  numInput.Flash(GUIStyle.Green);
193  }
194  }
195  }
196  }
197  }
198  else if (newValue is Vector4 v4)
199  {
200  for (int i = 0; i < fields.Length; i++)
201  {
202  var field = fields[i];
203  if (field is GUINumberInput numInput)
204  {
205  if (numInput.InputType == NumberType.Float)
206  {
207  switch (i)
208  {
209  case 0:
210  numInput.FloatValue = v4.X;
211  break;
212  case 1:
213  numInput.FloatValue = v4.Y;
214  break;
215  case 2:
216  numInput.FloatValue = v4.Z;
217  break;
218  case 3:
219  numInput.FloatValue = v4.W;
220  break;
221  }
222  if (flash)
223  {
224  numInput.Flash(GUIStyle.Green);
225  }
226  }
227  }
228  }
229  }
230  else if (newValue is Color c)
231  {
232  for (int i = 0; i < fields.Length; i++)
233  {
234  var field = fields[i];
235  if (field is GUINumberInput numInput)
236  {
237  if (numInput.InputType == NumberType.Int)
238  {
239  switch (i)
240  {
241  case 0:
242  numInput.IntValue = c.R;
243  break;
244  case 1:
245  numInput.IntValue = c.G;
246  break;
247  case 2:
248  numInput.IntValue = c.B;
249  break;
250  case 3:
251  numInput.IntValue = c.A;
252  break;
253  }
254  if (flash)
255  {
256  numInput.Flash(GUIStyle.Green);
257  }
258  }
259  }
260  }
261 
262  if (fields.FirstOrDefault() is { } comp && comp.Parent?.Parent?.Parent is { } parent)
263  {
264  if (parent.FindChild("colorpreview", true) is GUIButton preview)
265  {
266  preview.Color = preview.HoverColor = preview.PressedColor = preview.SelectedTextColor = c;
267  }
268  }
269  }
270  else if (newValue is Rectangle r)
271  {
272  for (int i = 0; i < fields.Length; i++)
273  {
274  var field = fields[i];
275  if (field is GUINumberInput numInput)
276  {
277  if (numInput.InputType == NumberType.Int)
278  {
279  switch (i)
280  {
281  case 0:
282  numInput.IntValue = r.X;
283  break;
284  case 1:
285  numInput.IntValue = r.Y;
286  break;
287  case 2:
288  numInput.IntValue = r.Width;
289  break;
290  case 3:
291  numInput.IntValue = r.Height;
292  break;
293  }
294  if (flash)
295  {
296  numInput.Flash(GUIStyle.Green);
297  }
298  }
299  }
300  }
301  }
302  else if (newValue is string[] a)
303  {
304  for (int i = 0; i < fields.Length; i++)
305  {
306  if (i >= a.Length) { break; }
307  if (fields[i] is GUITextBox textBox)
308  {
309  textBox.Text = a[i];
310  if (flash)
311  {
312  textBox.Flash(GUIStyle.Green);
313  }
314  }
315  }
316  }
317  }
318 
319  public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null)
320  : this(parent, entity, inGame ?
321  SerializableProperty.GetProperties<InGameEditable>(entity).Union(SerializableProperty.GetProperties<ConditionallyEditable>(entity).Where(p => p.GetAttribute<ConditionallyEditable>()?.IsEditable(entity) ?? false))
322  : SerializableProperty.GetProperties<Editable>(entity).Where(p => p.GetAttribute<ConditionallyEditable>()?.IsEditable(entity) ?? true), showName, style, elementHeight, titleFont)
323  {
324  }
325 
326  public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable<SerializableProperty> properties, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null)
327  : base(style, new RectTransform(Vector2.One, parent))
328  {
329  elementHeight = (int)(elementHeight * GUI.Scale);
330  var tickBoxStyle = GUIStyle.GetComponentStyle("GUITickBox");
331  var textBoxStyle = GUIStyle.GetComponentStyle("GUITextBox");
332  var numberInputStyle = GUIStyle.GetComponentStyle("GUINumberInput");
333  if (tickBoxStyle.Height.HasValue) { this.elementHeight = Math.Max(tickBoxStyle.Height.Value, this.elementHeight); }
334  if (textBoxStyle.Height.HasValue) { this.elementHeight = Math.Max(textBoxStyle.Height.Value, this.elementHeight); }
335  if (numberInputStyle.Height.HasValue) { this.elementHeight = Math.Max(numberInputStyle.Height.Value, this.elementHeight); }
336 
337  layoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, RectTransform)) { AbsoluteSpacing = (int)(5 * GUI.Scale) };
338  if (showName)
339  {
340  new GUITextBlock(new RectTransform(new Point(layoutGroup.Rect.Width, this.elementHeight), layoutGroup.RectTransform, isFixedSize: true), entity.Name, font: titleFont ?? GUIStyle.Font)
341  {
342  TextColor = Color.White,
343  Color = Color.Black
344  };
345  }
346 
347  List<Header> headers = new List<Header>()
348  {
349  //"no header" comes first = properties under no header are listed first
350  null
351  };
352  //check which header each property is under
353  Dictionary<SerializableProperty, Header> propertyHeaders = new Dictionary<SerializableProperty, Header>();
354  Header prevHeader = null;
355  foreach (var property in properties)
356  {
357  var header = property.GetAttribute<Header>();
358  if (header != null)
359  {
360  prevHeader = header;
361  //Attribute.Equals is based on the equality of the fields,
362  //so in practice we treat identical headers split into different files/classes as the same header
363  if (!headers.Contains(header))
364  {
365  //collect headers into a list in the order they're encountered in
366  //(to keep them in the same order as they're defined in the code, as the dictionary is not in any particular order)
367  headers.Add(header);
368  }
369  }
370  propertyHeaders[property] = prevHeader;
371  }
372 
373  prevHeader = null;
374  foreach (Header header in headers)
375  {
376  //go through all the properties that belong under this header
377  foreach (var property in properties)
378  {
379  if (!Equals(propertyHeaders[property], header)) { continue; }
380  //don't create a header if the previous header has the same text as this one (= if we already created this header before)
381  if (header != null && !Equals(header, prevHeader))
382  {
383  new GUITextBlock(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true),
384  header.Text, textColor: GUIStyle.TextColorBright, font: GUIStyle.SubHeadingFont);
385  prevHeader = header;
386  }
387  CreateNewField(property, entity);
388  }
389  }
390 
391  //scale the size of this component and the layout group to fit the children
392  Recalculate();
393  }
394 
395  public void AddCustomContent(GUIComponent component, int childIndex)
396  {
397  component.RectTransform.Parent = layoutGroup.RectTransform;
398  component.RectTransform.RepositionChildInHierarchy(Math.Min(childIndex, layoutGroup.CountChildren - 1));
399  layoutGroup.Recalculate();
400  Recalculate();
401  }
402 
403  public void RefreshValues()
404  {
405  refresh?.Invoke();
406  }
407 
409 
411  {
412  object value = property.GetValue(entity);
413  if (property.PropertyType == typeof(string) && value == null)
414  {
415  value = "";
416  }
417 
418  Identifier propertyTag = $"{property.PropertyInfo.DeclaringType.Name}.{property.PropertyInfo.Name}".ToIdentifier();
419  Identifier fallbackTag = property.PropertyInfo.Name.ToIdentifier();
420  LocalizedString displayName = TextManager.Get(propertyTag, $"sp.{propertyTag}.name".ToIdentifier());
421  if (displayName.IsNullOrEmpty())
422  {
423  Editable editable = property.GetAttribute<Editable>();
424  if (editable != null && !string.IsNullOrEmpty(editable.FallBackTextTag))
425  {
426  displayName = TextManager.Get(editable.FallBackTextTag);
427  }
428  else
429  {
430  displayName = TextManager.Get(fallbackTag, $"sp.{fallbackTag}.name".ToIdentifier());
431  }
432  }
433 
434  if (displayName.IsNullOrEmpty())
435  {
436  displayName = property.Name.FormatCamelCaseWithSpaces();
437 #if DEBUG
438  InGameEditable editable = property.GetAttribute<InGameEditable>();
439  if (editable != null)
440  {
441  if (!MissingLocalizations.Contains($"sp.{propertyTag}.name|{displayName}"))
442  {
443  DebugConsole.NewMessage("Missing Localization for property: " + propertyTag);
444  MissingLocalizations.Add($"sp.{propertyTag}.name|{displayName}");
445  MissingLocalizations.Add($"sp.{propertyTag}.description|{property.GetAttribute<Serialize>().Description}");
446  }
447  }
448 #endif
449  }
450 
451  LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description");
452  if (entity.GetType() != property.PropertyInfo.DeclaringType)
453  {
454  Identifier propertyTagForDerivedClass = $"{entity.GetType().Name}.{property.PropertyInfo.Name}".ToIdentifier();
455  var toolTipForDerivedClass = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
456  if (!toolTipForDerivedClass.IsNullOrEmpty())
457  {
458  toolTip = toolTipForDerivedClass;
459  }
460  }
461  if (toolTip.IsNullOrEmpty())
462  {
463  toolTip = TextManager.Get($"{propertyTag}.description", $"{fallbackTag}.description", $"sp.{fallbackTag}.description");
464  }
465  if (toolTip.IsNullOrEmpty())
466  {
467  toolTip = property.GetAttribute<Serialize>().Description;
468  }
469 
470  GUIComponent propertyField = null;
471  if (value is bool boolVal)
472  {
473  propertyField = CreateBoolField(entity, property, boolVal, displayName, toolTip);
474  }
475  else if (value.GetType().IsEnum)
476  {
477  if (value.GetType().IsDefined(typeof(FlagsAttribute), inherit: false))
478  {
479  propertyField = CreateEnumFlagField(entity, property, value, displayName, toolTip);
480  }
481  else
482  {
483  propertyField = CreateEnumField(entity, property, value, displayName, toolTip);
484  }
485  }
486  else if (value is int i)
487  {
488  propertyField = CreateIntField(entity, property, i, displayName, toolTip);
489  }
490  else if (value is float f)
491  {
492  propertyField = CreateFloatField(entity, property, f, displayName, toolTip);
493  }
494  else if (value is Point p)
495  {
496  propertyField = CreatePointField(entity, property, p, displayName, toolTip);
497  }
498  else if (value is Vector2 v2)
499  {
500  propertyField = CreateVector2Field(entity, property, v2, displayName, toolTip);
501  }
502  else if (value is Vector3 v3)
503  {
504  propertyField = CreateVector3Field(entity, property, v3, displayName, toolTip);
505  }
506  else if (value is Vector4 v4)
507  {
508  propertyField = CreateVector4Field(entity, property, v4, displayName, toolTip);
509  }
510  else if (value is Color c)
511  {
512  propertyField = CreateColorField(entity, property, c, displayName, toolTip);
513  }
514  else if (value is Rectangle r)
515  {
516  propertyField = CreateRectangleField(entity, property, r, displayName, toolTip);
517  }
518  else if(value is string[] a)
519  {
520  propertyField = CreateStringArrayField(entity, property, a, displayName, toolTip);
521  }
522  else if (value is string or Identifier)
523  {
524  propertyField = CreateStringField(entity, property, value.ToString(), displayName, toolTip);
525  }
526  return propertyField;
527  }
528 
529  public GUIComponent CreateBoolField(ISerializableEntity entity, SerializableProperty property, bool value, LocalizedString displayName, LocalizedString toolTip)
530  {
531  var editableAttribute = property.GetAttribute<Editable>();
532  if (editableAttribute.ReadOnly)
533  {
534  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
535  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
536  {
537  ToolTip = toolTip
538  };
539  var valueField = new GUITextBlock(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), value.ToString())
540  {
541  ToolTip = toolTip,
542  Font = GUIStyle.SmallFont
543  };
544  return valueField;
545  }
546  else
547  {
548  GUITickBox propertyTickBox = new GUITickBox(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform, isFixedSize: true), displayName)
549  {
550  Font = GUIStyle.SmallFont,
551  Enabled = !Readonly,
552  Selected = value,
553  ToolTip = toolTip,
554  OnSelected = (tickBox) =>
555  {
556  if (SetPropertyValue(property, entity, tickBox.Selected))
557  {
558  TrySendNetworkUpdate(entity, property);
559  }
560  // Ensure that the values stay in sync (could be that we force the value in the property accessor).
561  bool propertyValue = (bool)property.GetValue(entity);
562  if (tickBox.Selected != propertyValue)
563  {
564  tickBox.Selected = propertyValue;
565  tickBox.Flash(Color.Red);
566  }
567  return true;
568  }
569  };
570  refresh += () =>
571  {
572  propertyTickBox.Selected = (bool)property.GetValue(entity);
573  };
574  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { propertyTickBox }); }
575  return propertyTickBox;
576  }
577  }
578 
579  public GUIComponent CreateIntField(ISerializableEntity entity, SerializableProperty property, int value, LocalizedString displayName, LocalizedString toolTip)
580  {
581  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
582  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
583  {
584  ToolTip = toolTip
585  };
586  var editableAttribute = property.GetAttribute<Editable>();
587  GUIComponent field;
588  if (editableAttribute.ReadOnly)
589  {
590  var numberInput = new GUITextBlock(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), value.ToString())
591  {
592  ToolTip = toolTip,
593  Font = GUIStyle.SmallFont
594  };
595  field = numberInput;
596  }
597  else
598  {
599  var numberInput = new GUINumberInput(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), NumberType.Int)
600  {
601  ToolTip = toolTip,
602  Font = GUIStyle.SmallFont,
604  };
605  numberInput.MinValueInt = editableAttribute.MinValueInt;
606  numberInput.MaxValueInt = editableAttribute.MaxValueInt;
607  numberInput.IntValue = value;
608  numberInput.OnValueChanged += (numInput) =>
609  {
610  if (SetPropertyValue(property, entity, numInput.IntValue))
611  {
612  TrySendNetworkUpdate(entity, property);
613  }
614  };
615  refresh += () =>
616  {
617  if (!numberInput.TextBox.Selected) { numberInput.IntValue = (int)property.GetValue(entity); }
618  };
619  field = numberInput;
620  }
621  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { field }); }
622  return frame;
623  }
624 
625  public GUIComponent CreateFloatField(ISerializableEntity entity, SerializableProperty property, float value, LocalizedString displayName, LocalizedString toolTip)
626  {
627  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent)
628  {
629  CanBeFocused = false
630  };
631  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
632  {
633  ToolTip = toolTip
634  };
635 
636  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform,
637  Anchor.TopRight), NumberType.Float)
638  {
639  ToolTip = toolTip,
640  Font = GUIStyle.SmallFont
641  };
642  var editableAttribute = property.GetAttribute<Editable>();
643  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
644  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
645  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
646  numberInput.ValueStep = editableAttribute.ValueStep;
647  numberInput.PlusMinusButtonVisibility = editableAttribute
648  .ForceShowPlusMinusButtons ? GUINumberInput.ButtonVisibility.ForceVisible : default;
649  numberInput.FloatValue = value;
650 
651  numberInput.OnValueChanged += numInput =>
652  {
653  if (SetPropertyValue(property, entity, numInput.FloatValue))
654  {
655  TrySendNetworkUpdate(entity, property);
656  }
657  };
658 
659  HandleSetterValueTampering(numberInput, () => property.GetFloatValue(entity));
660  refresh += () =>
661  {
662  if (!numberInput.TextBox.Selected) { numberInput.FloatValue = (float)property.GetValue(entity); }
663  };
664  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { numberInput }); }
665  return frame;
666  }
667 
668  private static void HandleSetterValueTampering(GUINumberInput numberInput, Func<float> getter)
669  {
670  // Lots of UI boilerplate to handle all(?) cases where the property's setter may be called
671  // and modify the input value (e.g. rotation value wrapping)
672  void HandleSetterModifyingInput(GUINumberInput numInput)
673  {
674  var inputFloatValue = numInput.FloatValue;
675  var resultingFloatValue = getter();
676  if (!MathUtils.NearlyEqual(resultingFloatValue, inputFloatValue))
677  {
678  numInput.FloatValue = resultingFloatValue;
679  }
680  }
681  bool HandleSetterModifyingInputOnButtonPressed() { HandleSetterModifyingInput(numberInput); return true; }
682  bool HandleSetterModifyingInputOnButtonClicked(GUIButton _, object __) { HandleSetterModifyingInput(numberInput); return true; }
683 
684  numberInput.OnValueEntered += HandleSetterModifyingInput;
685  numberInput.PlusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed;
686  numberInput.PlusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked;
687  numberInput.MinusButton.OnPressed += HandleSetterModifyingInputOnButtonPressed;
688  numberInput.MinusButton.OnClicked += HandleSetterModifyingInputOnButtonClicked;
689  }
690 
691  public GUIComponent CreateEnumField(ISerializableEntity entity, SerializableProperty property, object value, LocalizedString displayName, LocalizedString toolTip)
692  {
693  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
694  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
695  {
696  ToolTip = toolTip
697  };
698  GUIDropDown enumDropDown = new GUIDropDown(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight),
699  elementCount: Enum.GetValues(value.GetType()).Length)
700  {
701  ToolTip = toolTip
702  };
703  foreach (object enumValue in Enum.GetValues(value.GetType()))
704  {
705  enumDropDown.AddItem(enumValue.ToString(), enumValue);
706  }
707  enumDropDown.SelectItem(value);
708  enumDropDown.OnSelected += (selected, val) =>
709  {
710  if (SetPropertyValue(property, entity, val))
711  {
712  TrySendNetworkUpdate(entity, property);
713  }
714  return true;
715  };
716  refresh += () =>
717  {
718  if (!enumDropDown.Dropped) { enumDropDown.SelectItem(property.GetValue(entity)); }
719  };
720  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { enumDropDown }); }
721  return frame;
722  }
723 
725  {
726  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementHeight), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
727  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
728  {
729  ToolTip = toolTip
730  };
731  GUIDropDown enumDropDown = new GUIDropDown(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight),
732  elementCount: Enum.GetValues(value.GetType()).Length, selectMultiple: true)
733  {
734  ToolTip = toolTip
735  };
736 
737  bool isFlagsAttribute = value.GetType().IsDefined(typeof(FlagsAttribute), false);
738 
739  bool hasNoneOption = false;
740  foreach (object enumValue in Enum.GetValues(value.GetType()))
741  {
742  if (isFlagsAttribute && !MathHelper.IsPowerOfTwo((int)enumValue)) { continue; }
743  hasNoneOption |= (int)enumValue == 0;
744  enumDropDown.AddItem(enumValue.ToString(), enumValue);
745  if (((int)enumValue != 0 || (int)value == 0) && ((Enum)value).HasFlag((Enum)enumValue))
746  {
747  enumDropDown.SelectItem(enumValue);
748  }
749  }
750  enumDropDown.MustSelectAtLeastOne = !hasNoneOption;
751  enumDropDown.AfterSelected += (selected, val) =>
752  {
753  if (SetPropertyValue(property, entity, string.Join(", ", enumDropDown.SelectedDataMultiple.Select(d => d.ToString()))))
754  {
755  TrySendNetworkUpdate(entity, property);
756  }
757  return true;
758  };
759 
760  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { enumDropDown }); }
761  return frame;
762  }
763 
764  public GUIComponent CreateStringField(ISerializableEntity entity, SerializableProperty property, string value, LocalizedString displayName, LocalizedString toolTip)
765  {
766  bool isItemTagBox = IsItemTagBox(entity, property.Name, out Item it);
767  var mainFrame = new GUILayoutGroup(new RectTransform(new Point(Rect.Width, isItemTagBox ? elementHeight * 2 : elementHeight), layoutGroup.RectTransform, isFixedSize: true));
768 
769  var frame = new GUILayoutGroup(new RectTransform(isItemTagBox ? new Vector2(1f, 0.5f) : Vector2.One, mainFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
770  {
771  Stretch = true
772  };
773 
774  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont, textAlignment: Alignment.Left)
775  {
776  ToolTip = toolTip
777  };
778 
779  Identifier translationTextTag = property.GetAttribute<Serialize>()?.TranslationTextTag ?? Identifier.Empty;
780  const float browseButtonWidth = 0.1f;
781  var editableAttribute = property.GetAttribute<Editable>();
782  float textBoxWidth = inputFieldWidth;
783  if (!translationTextTag.IsEmpty || isItemTagBox) { textBoxWidth -= browseButtonWidth; }
784  GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(textBoxWidth, 1), frame.RectTransform))
785  {
786  Enabled = editableAttribute != null && !editableAttribute.ReadOnly,
787  Readonly = Readonly,
788  ToolTip = toolTip,
789  Font = GUIStyle.SmallFont,
790  Text = StripPrefabTags(value),
791  OverflowClip = true,
792  };
793  if (editableAttribute != null && editableAttribute.MaxLength > 0)
794  {
795  propertyBox.MaxTextLength = editableAttribute.MaxLength;
796  }
797 
798  HashSet<MapEntity> editedEntities = new HashSet<MapEntity>();
799  propertyBox.OnTextChanged += (textBox, text) =>
800  {
801  foreach (var entity in MapEntity.SelectedList)
802  {
803  editedEntities.Add(entity);
804  }
805  return true;
806  };
807  propertyBox.OnDeselected += (textBox, keys) => OnApply(textBox);
808  propertyBox.OnEnterPressed += (box, text) => OnApply(box);
809  refresh += () =>
810  {
811  if (propertyBox.Selected) { return; }
812  propertyBox.Text = StripPrefabTags(property.GetValue(entity)?.ToString());
813  };
814 
815  bool OnApply(GUITextBox textBox)
816  {
817  List<MapEntity> prevSelected = MapEntity.SelectedList.ToList();
818  //reselect the entities that were selected during editing
819  //otherwise multi-editing won't work when we deselect the entities with unapplied changes in the textbox
820  if (editedEntities.Count > 1)
821  {
822  foreach (var entity in editedEntities)
823  {
824  MapEntity.SelectedList.Add(entity);
825  }
826  }
827  if (SetPropertyValue(property, entity, textBox.Text))
828  {
829  TrySendNetworkUpdate(entity, property);
830  textBox.Text = StripPrefabTags(property.GetValue(entity).ToString());
831  textBox.Flash(GUIStyle.Green, flashDuration: 1f);
832  }
833  //restore the entities that were selected before applying
834  MapEntity.SelectedList.Clear();
835  foreach (var entity in prevSelected)
836  {
837  MapEntity.SelectedList.Add(entity);
838  }
839  return true;
840  }
841 
842  if (!translationTextTag.IsEmpty)
843  {
844  new GUIButton(new RectTransform(new Vector2(browseButtonWidth, 1), frame.RectTransform, Anchor.TopRight), "...", style: "GUIButtonSmall")
845  {
846  OnClicked = (bt, userData) => { CreateTextPicker(translationTextTag.Value, entity, property, propertyBox); return true; }
847  };
848  propertyBox.OnTextChanged += (tb, text) =>
849  {
850  LocalizedString translatedText = TextManager.Get(text);
851  if (translatedText.IsNullOrEmpty())
852  {
853  propertyBox.TextColor = Color.Gray;
854  propertyBox.ToolTip = TextManager.GetWithVariable("StringPropertyCannotTranslate", "[tag]", text ?? string.Empty);
855  }
856  else
857  {
858  propertyBox.TextColor = GUIStyle.Green;
859  propertyBox.ToolTip = TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", translatedText);
860  }
861  return true;
862  };
863  propertyBox.Text = value;
864  }
865 
866  if (isItemTagBox)
867  {
868  // create prefab tag box
869  var prefabFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), mainFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
870  {
871  Stretch = true
872  };
873 
874  new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), prefabFrame.RectTransform), TextManager.Get("predefinedtags.name"), font: GUIStyle.SmallFont, textAlignment: Alignment.Left)
875  {
876  ToolTip = TextManager.Get("predefinedtags.description")
877  };
878 
879  new GUITextBox(new RectTransform(new Vector2(inputFieldWidth, 1), prefabFrame.RectTransform), createPenIcon: false)
880  {
881  Readonly = true,
882  Font = GUIStyle.SmallFont,
883  Text = GetPrefabTags(it),
884  OverflowClip = true,
885  ToolTip = TextManager.Get("predefinedtags.description")
886  };
887 
888  // add container tag popup button to the modifiable tag box
889  new GUIButton(new RectTransform(new Vector2(browseButtonWidth, 1), frame.RectTransform, Anchor.TopRight), "...")
890  {
891  OnClicked = (_, _) => { it.CreateContainerTagPicker(propertyBox); return true; }
892  };
893  }
894 
895  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
896  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { propertyBox }); }
897  return frame;
898 
899  static bool IsItemTagBox(ISerializableEntity entity, string propertyName, [NotNullWhen(true)] out Item it)
900  {
901  if (entity is Item item && propertyName.Equals(nameof(Item.Tags), StringComparison.OrdinalIgnoreCase))
902  {
903  it = item;
904  return true;
905  }
906  it = null;
907  return false;
908  }
909 
910  string StripPrefabTags(string text)
911  {
912  if (!isItemTagBox) { return text; }
913 
914  string prefabTags = GetPrefabTags(it);
915  if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(prefabTags)) { return text; }
916 
917  string[] splitTags = text.Split(',');
918  return string.Join(',', splitTags.Where(t => !it.Prefab.Tags.Contains(t)));
919  }
920 
921  static string GetPrefabTags(Item it) => string.Join(',', it.Prefab.Tags);
922  }
923 
924  public GUIComponent CreatePointField(ISerializableEntity entity, SerializableProperty property, Point value, LocalizedString displayName, LocalizedString toolTip)
925  {
926  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
927  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
928  {
929  ToolTip = toolTip
930  };
931  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
932  {
933  Stretch = true,
934  RelativeSpacing = 0.05f
935  };
936  var editableAttribute = property.GetAttribute<Editable>();
937  var fields = new GUIComponent[2];
938  for (int i = 1; i >= 0; i--)
939  {
940  var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
941 
942  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
943  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
944  {
945  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
946  }
947 
948  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
949  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
950  NumberType.Int)
951  {
952  Font = GUIStyle.SmallFont
953  };
954 
955  if (i == 0)
956  numberInput.IntValue = value.X;
957  else
958  numberInput.IntValue = value.Y;
959 
960  numberInput.MinValueInt = editableAttribute.MinValueInt;
961  numberInput.MaxValueInt = editableAttribute.MaxValueInt;
962 
963  int comp = i;
964  numberInput.OnValueChanged += (numInput) =>
965  {
966  Point newVal = (Point)property.GetValue(entity);
967  if (comp == 0)
968  newVal.X = numInput.IntValue;
969  else
970  newVal.Y = numInput.IntValue;
971 
972  if (SetPropertyValue(property, entity, newVal))
973  {
974  TrySendNetworkUpdate(entity, property);
975  }
976  };
977  fields[i] = numberInput;
978  }
979  refresh += () =>
980  {
981  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
982  {
983  Point value = (Point)property.GetValue(entity);
984  ((GUINumberInput)fields[0]).IntValue = value.X;
985  ((GUINumberInput)fields[1]).IntValue = value.Y;
986  }
987  };
988  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
989  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
990  return frame;
991  }
992 
993  public GUIComponent CreateVector2Field(ISerializableEntity entity, SerializableProperty property, Vector2 value, LocalizedString displayName, LocalizedString toolTip)
994  {
995  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
996  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
997  {
998  ToolTip = toolTip
999  };
1000  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1001  {
1002  Stretch = true,
1003  RelativeSpacing = 0.05f
1004  };
1005  var editableAttribute = property.GetAttribute<Editable>();
1006  var fields = new GUIComponent[2];
1007  for (int i = 1; i >= 0; i--)
1008  {
1009  var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
1010 
1011  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1012  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1013  {
1014  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1015  }
1016  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1017  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1018  NumberType.Float)
1019  {
1020  Font = GUIStyle.SmallFont
1021  };
1022 
1023  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1024  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1025  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1026  numberInput.ValueStep = editableAttribute.ValueStep;
1027  numberInput.PlusMinusButtonVisibility = editableAttribute
1028  .ForceShowPlusMinusButtons ? GUINumberInput.ButtonVisibility.ForceVisible : default;
1029 
1030  numberInput.FloatValue = i == 0 ? value.X : value.Y;
1031 
1032  int comp = i;
1033  numberInput.OnValueChanged += (numInput) =>
1034  {
1035  Vector2 newVal = (Vector2)property.GetValue(entity);
1036  if (comp == 0)
1037  {
1038  newVal.X = numInput.FloatValue;
1039  }
1040  else
1041  {
1042  newVal.Y = numInput.FloatValue;
1043  }
1044 
1045  if (SetPropertyValue(property, entity, newVal))
1046  {
1047  TrySendNetworkUpdate(entity, property);
1048  }
1049  };
1050  HandleSetterValueTampering(numberInput, () =>
1051  {
1052  Vector2 currVal = (Vector2)property.GetValue(entity);
1053  return comp == 0 ? currVal.X : currVal.Y;
1054  });
1055  fields[i] = numberInput;
1056  }
1057  refresh += () =>
1058  {
1059  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1060  {
1061  Vector2 value = (Vector2)property.GetValue(entity);
1062  ((GUINumberInput)fields[0]).FloatValue = value.X;
1063  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1064  }
1065  };
1066  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1067  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1068  return frame;
1069  }
1070 
1071  public GUIComponent CreateVector3Field(ISerializableEntity entity, SerializableProperty property, Vector3 value, LocalizedString displayName, LocalizedString toolTip)
1072  {
1073  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1074  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1075  {
1076  ToolTip = toolTip
1077  };
1078  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(largeInputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1079  {
1080  Stretch = true,
1081  RelativeSpacing = 0.03f
1082  };
1083  var editableAttribute = property.GetAttribute<Editable>();
1084  var fields = new GUIComponent[3];
1085  for (int i = 2; i >= 0; i--)
1086  {
1087  var element = new GUIFrame(new RectTransform(new Vector2(0.33f, 1), inputArea.RectTransform), style: null);
1088 
1089  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1090  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1091  {
1092  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1093  }
1094 
1095  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1096  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1097  NumberType.Float)
1098  {
1099  Font = GUIStyle.SmallFont
1100  };
1101 
1102  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1103  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1104  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1105  numberInput.ValueStep = editableAttribute.ValueStep;
1106 
1107  if (i == 0)
1108  numberInput.FloatValue = value.X;
1109  else if (i == 1)
1110  numberInput.FloatValue = value.Y;
1111  else if (i == 2)
1112  numberInput.FloatValue = value.Z;
1113 
1114  int comp = i;
1115  numberInput.OnValueChanged += (numInput) =>
1116  {
1117  Vector3 newVal = (Vector3)property.GetValue(entity);
1118  if (comp == 0)
1119  newVal.X = numInput.FloatValue;
1120  else if (comp == 1)
1121  newVal.Y = numInput.FloatValue;
1122  else
1123  newVal.Z = numInput.FloatValue;
1124 
1125  if (SetPropertyValue(property, entity, newVal))
1126  {
1127  TrySendNetworkUpdate(entity, property);
1128  }
1129  };
1130  fields[i] = numberInput;
1131  }
1132  refresh += () =>
1133  {
1134  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1135  {
1136  Vector3 value = (Vector3)property.GetValue(entity);
1137  ((GUINumberInput)fields[0]).FloatValue = value.X;
1138  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1139  ((GUINumberInput)fields[2]).FloatValue = value.Z;
1140  }
1141  };
1142  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1143  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1144  return frame;
1145  }
1146 
1147  public GUIComponent CreateVector4Field(ISerializableEntity entity, SerializableProperty property, Vector4 value, LocalizedString displayName, LocalizedString toolTip)
1148  {
1149  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1150  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1151  {
1152  ToolTip = toolTip
1153  };
1154  var editableAttribute = property.GetAttribute<Editable>();
1155  var fields = new GUIComponent[4];
1156  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(largeInputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1157  {
1158  Stretch = true,
1159  RelativeSpacing = 0.01f
1160  };
1161  for (int i = 3; i >= 0; i--)
1162  {
1163  var element = new GUIFrame(new RectTransform(new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style: null);
1164 
1165  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1166  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1167  {
1168  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1169  }
1170 
1171  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1172  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1173  NumberType.Float)
1174  {
1175  Font = GUIStyle.SmallFont
1176  };
1177 
1178  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1179  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1180  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1181  numberInput.ValueStep = editableAttribute.ValueStep;
1182 
1183  if (i == 0)
1184  numberInput.FloatValue = value.X;
1185  else if (i == 1)
1186  numberInput.FloatValue = value.Y;
1187  else if (i == 2)
1188  numberInput.FloatValue = value.Z;
1189  else
1190  numberInput.FloatValue = value.W;
1191 
1192  int comp = i;
1193  numberInput.OnValueChanged += (numInput) =>
1194  {
1195  Vector4 newVal = (Vector4)property.GetValue(entity);
1196  if (comp == 0)
1197  newVal.X = numInput.FloatValue;
1198  else if (comp == 1)
1199  newVal.Y = numInput.FloatValue;
1200  else if (comp == 2)
1201  newVal.Z = numInput.FloatValue;
1202  else
1203  newVal.W = numInput.FloatValue;
1204 
1205  if (SetPropertyValue(property, entity, newVal))
1206  {
1207  TrySendNetworkUpdate(entity, property);
1208  }
1209  };
1210  fields[i] = numberInput;
1211  }
1212  refresh += () =>
1213  {
1214  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1215  {
1216  Vector4 value = (Vector4)property.GetValue(entity);
1217  ((GUINumberInput)fields[0]).FloatValue = value.X;
1218  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1219  ((GUINumberInput)fields[2]).FloatValue = value.Z;
1220  ((GUINumberInput)fields[3]).FloatValue = value.W;
1221  }
1222  };
1223  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1224  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1225  return frame;
1226  }
1227 
1228  public GUIComponent CreateColorField(ISerializableEntity entity, SerializableProperty property, Color value, LocalizedString displayName, LocalizedString toolTip)
1229  {
1230  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1231  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform) { MinSize = new Point(80, 26) }, displayName, font: GUIStyle.SmallFont)
1232  {
1233  ToolTip = displayName + '\n' + toolTip
1234  };
1235  label.Text = ToolBox.LimitString(label.Text, label.Font, label.Rect.Width);
1236  var colorBoxBack = new GUIFrame(new RectTransform(new Vector2(0.04f, 1), frame.RectTransform)
1237  {
1238  AbsoluteOffset = new Point(label.Rect.Width, 0)
1239  }, color: Color.Black, style: null);
1240  var colorBox = new GUIButton(new RectTransform(new Vector2(largeInputFieldWidth, 0.9f), colorBoxBack.RectTransform, Anchor.Center), style: null)
1241  {
1242  UserData = "colorpreview",
1243  OnClicked = (component, data) =>
1244  {
1245  if (!SubEditorScreen.IsSubEditor()) { return false; }
1246  if (GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { Closed: false, UserData: "colorpicker" })) { return false; }
1247 
1248  GUIMessageBox msgBox = SubEditorScreen.CreatePropertyColorPicker((Color) property.GetValue(entity), property, entity);
1249  return true;
1250  }
1251  };
1252  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(Math.Max((frame.Rect.Width - label.Rect.Width - colorBoxBack.Rect.Width) / (float)frame.Rect.Width, 0.5f), 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1253  {
1254  Stretch = true,
1255  RelativeSpacing = 0.001f
1256  };
1257  var fields = new GUIComponent[4];
1258  for (int i = 3; i >= 0; i--)
1259  {
1260  var element = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 1), inputArea.RectTransform), isHorizontal: true)
1261  {
1262  Stretch = true
1263  };
1264  new GUITextBlock(new RectTransform(new Vector2(0.2f, 1), element.RectTransform, Anchor.CenterLeft) { MinSize = new Point(15, 0) }, GUI.ColorComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1265  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1266  NumberType.Int)
1267  {
1268  Font = GUIStyle.SmallFont
1269  };
1270  numberInput.MinValueInt = 0;
1271  numberInput.MaxValueInt = 255;
1272 
1273  if (i == 0)
1274  numberInput.IntValue = value.R;
1275  else if (i == 1)
1276  numberInput.IntValue = value.G;
1277  else if (i == 2)
1278  numberInput.IntValue = value.B;
1279  else
1280  numberInput.IntValue = value.A;
1281 
1282  numberInput.Font = GUIStyle.SmallFont;
1283 
1284  int comp = i;
1285  numberInput.OnValueChanged += (numInput) =>
1286  {
1287  Color newVal = (Color)property.GetValue(entity);
1288  if (comp == 0)
1289  newVal.R = (byte)numInput.IntValue;
1290  else if (comp == 1)
1291  newVal.G = (byte)numInput.IntValue;
1292  else if (comp == 2)
1293  newVal.B = (byte)numInput.IntValue;
1294  else
1295  newVal.A = (byte)numInput.IntValue;
1296 
1297  if (SetPropertyValue(property, entity, newVal))
1298  {
1299  TrySendNetworkUpdate(entity, property);
1300  colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = newVal;
1301  }
1302  };
1303  colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = (Color)property.GetValue(entity);
1304  fields[i] = numberInput;
1305  }
1306  refresh += () =>
1307  {
1308  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1309  {
1310  Color value = (Color)property.GetValue(entity);
1311  ((GUINumberInput)fields[0]).IntValue = value.R;
1312  ((GUINumberInput)fields[1]).IntValue = value.G;
1313  ((GUINumberInput)fields[2]).IntValue = value.B;
1314  ((GUINumberInput)fields[3]).IntValue = value.A;
1315  }
1316  };
1317  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1318  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1319  return frame;
1320  }
1321 
1322  public GUIComponent CreateRectangleField(ISerializableEntity entity, SerializableProperty property, Rectangle value, LocalizedString displayName, LocalizedString toolTip)
1323  {
1324  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1325  var label = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1326  {
1327  ToolTip = displayName + '\n' + toolTip
1328  };
1329  label.Text = ToolBox.LimitString(label.Text, label.Font, label.Rect.Width);
1330  var fields = new GUIComponent[4];
1331  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1332  {
1333  Stretch = true,
1334  RelativeSpacing = 0.01f
1335  };
1336  for (int i = 3; i >= 0; i--)
1337  {
1338  var element = new GUIFrame(new RectTransform(new Vector2(0.22f, 1), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point(150, 50) }, style: null);
1339  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), GUI.RectComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1340  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1341  NumberType.Int)
1342  {
1343  Font = GUIStyle.SmallFont
1344  };
1345  // Not sure if the min value could in any case be negative.
1346  numberInput.MinValueInt = 0;
1347  // Just something reasonable to keep the value in the input rect.
1348  numberInput.MaxValueInt = 9999;
1349 
1350  if (i == 0)
1351  numberInput.IntValue = value.X;
1352  else if (i == 1)
1353  numberInput.IntValue = value.Y;
1354  else if (i == 2)
1355  numberInput.IntValue = value.Width;
1356  else
1357  numberInput.IntValue = value.Height;
1358 
1359  int comp = i;
1360  numberInput.OnValueChanged += (numInput) =>
1361  {
1362  Rectangle newVal = (Rectangle)property.GetValue(entity);
1363  if (comp == 0)
1364  newVal.X = numInput.IntValue;
1365  else if (comp == 1)
1366  newVal.Y = numInput.IntValue;
1367  else if (comp == 2)
1368  newVal.Width = numInput.IntValue;
1369  else
1370  newVal.Height = numInput.IntValue;
1371 
1372  if (SetPropertyValue(property, entity, newVal))
1373  {
1374  TrySendNetworkUpdate(entity, property);
1375  }
1376  };
1377  fields[i] = numberInput;
1378  }
1379  refresh += () =>
1380  {
1381  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1382  {
1383  Rectangle value = (Rectangle)property.GetValue(entity);
1384  ((GUINumberInput)fields[0]).IntValue = value.X;
1385  ((GUINumberInput)fields[1]).IntValue = value.Y;
1386  ((GUINumberInput)fields[2]).IntValue = value.Width;
1387  ((GUINumberInput)fields[3]).IntValue = value.Height;
1388  }
1389  };
1390  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1391  return frame;
1392  }
1393 
1395  {
1396  int elementCount = (value.Length + 1);
1397  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementCount * elementHeight), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1398  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f / elementCount), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1399  {
1400  ToolTip = toolTip
1401  };
1402  var editableAttribute = property.GetAttribute<Editable>();
1403  var fields = new GUIComponent[value.Length];
1404  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, (float)(elementCount - 1) / elementCount), frame.RectTransform, anchor: Anchor.BottomLeft))
1405  {
1406  RelativeSpacing = 0.01f
1407  };
1408  elementCount -= 1;
1409 
1410  for (int i = 0; i < value.Length; i++)
1411  {
1412  var element = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f / elementCount), inputArea.RectTransform) { MinSize = new Point(50, 0), MaxSize = new Point((int)(0.9f * inputArea.Rect.Width), 50) }, style: null);
1413  var elementLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, element.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
1414  // Set the label to be (i + 1) so it's easier to understand for non-programmers
1415  string componentLabel = (i + 1).ToString();
1416  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), elementLayoutGroup.RectTransform) { MaxSize = new Point(25, elementLayoutGroup.Rect.Height) }, componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1417  GUITextBox textBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), elementLayoutGroup.RectTransform), text: value[i])
1418  {
1419  Font = GUIStyle.SmallFont,
1420  Readonly = Readonly
1421  };
1422  int comp = i;
1423  textBox.OnEnterPressed += (textBox, text) => OnApply(textBox);
1424  textBox.OnDeselected += (textBox, keys) => OnApply(textBox);
1425  fields[i] = textBox;
1426 
1427  bool OnApply(GUITextBox textBox)
1428  {
1429  // Reserve the semicolon for serializing the value
1430  bool containsForbiddenCharacters = textBox.Text.Contains(';');
1431  string[] newValue = (string[])property.GetValue(entity);
1432  if (!containsForbiddenCharacters)
1433  {
1434  newValue[comp] = textBox.Text;
1435  if (SetPropertyValue(property, entity, newValue))
1436  {
1437  TrySendNetworkUpdate(entity, property);
1438  textBox.Flash(color: GUIStyle.Green, flashDuration: 1f);
1439  }
1440  }
1441  else
1442  {
1443  textBox.Text = newValue[comp];
1444  textBox.Flash(color: GUIStyle.Red, flashDuration: 1f);
1445  }
1446  return true;
1447  }
1448  }
1449 
1450  refresh += () =>
1451  {
1452  if (fields.None(f => ((GUITextBox)f).Selected))
1453  {
1454  string[] value = (string[])property.GetValue(entity);
1455  for (int i = 0; i < fields.Length; i++)
1456  {
1457  ((GUITextBox)fields[i]).Text = value[i];
1458  }
1459  }
1460  };
1461 
1462  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Sum(c => c.MinSize.Y));
1463  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1464  return frame;
1465  }
1466 
1467  public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox)
1468  {
1469  var msgBox = new GUIMessageBox("", "", new LocalizedString[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.5f), new Point(300, 400));
1470  msgBox.Buttons[0].OnClicked = msgBox.Close;
1471 
1472  var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter))
1473  {
1474  PlaySoundOnSelect = true,
1475  OnSelected = (component, userData) =>
1476  {
1477  string text = userData as string ?? "";
1478 
1479  if (SetPropertyValue(property, entity, text))
1480  {
1481  TrySendNetworkUpdate(entity, property);
1482  textBox.Text = (string)property.GetValue(entity);
1483  textBox.Deselect();
1484  }
1485  return true;
1486  }
1487  };
1488 
1489  var tagTextPairs = TextManager.GetAllTagTextPairs().ToList();
1490  tagTextPairs.Sort((t1, t2) => { return t1.Value.CompareTo(t2.Value); });
1491  foreach (KeyValuePair<Identifier, string> tagTextPair in tagTextPairs)
1492  {
1493  if (!tagTextPair.Key.StartsWith(textTag)) { continue; }
1494  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) },
1495  ToolBox.LimitString(tagTextPair.Value, GUIStyle.Font, textList.Content.Rect.Width))
1496  {
1497  UserData = tagTextPair.Key.ToString()
1498  };
1499  }
1500 
1501  if (entity is IHasExtraTextPickerEntries hasExtraTextPickerEntries)
1502  {
1503  foreach (string extraEntry in hasExtraTextPickerEntries.GetExtraTextPickerEntries())
1504  {
1505  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) },
1506  ToolBox.LimitString(extraEntry, GUIStyle.Font, textList.Content.Rect.Width), GUIStyle.Green)
1507  {
1508  UserData = extraEntry
1509  };
1510  }
1511  }
1512  }
1513 
1514  private static void TrySendNetworkUpdate(ISerializableEntity entity, SerializableProperty property)
1515  {
1516  if (IsEntityRemoved(entity)) { return; }
1517 
1518  if (GameMain.Client != null)
1519  {
1520  if (entity is Item item)
1521  {
1522  GameMain.Client.CreateEntityEvent(item, new Item.ChangePropertyEventData(property, item));
1523  }
1524  else if (entity is ItemComponent ic)
1525  {
1526  GameMain.Client.CreateEntityEvent(ic.Item, new Item.ChangePropertyEventData(property, ic));
1527  }
1528  }
1529  }
1530 
1531  private bool SetPropertyValue(SerializableProperty property, object entity, object value)
1532  {
1533  if (LockEditing || IsEntityRemoved(entity) || Readonly) { return false; }
1534 
1535  object oldData = property.GetValue(entity);
1536  // some properties have null as the default string value
1537  if (oldData == null && value is string) { oldData = ""; }
1538  if (entity is ISerializableEntity sEntity && Screen.Selected is SubEditorScreen && !Equals(oldData, value))
1539  {
1540  List<ISerializableEntity> entities = new List<ISerializableEntity> { sEntity };
1541  Dictionary<ISerializableEntity, object> affected = MultiSetProperties(property, entity, value);
1542 
1543  Dictionary<object, List<ISerializableEntity>> oldValues = new Dictionary<object, List<ISerializableEntity>> {{ oldData!, new List<ISerializableEntity> { sEntity }}};
1544 
1545  affected.ForEach(aEntity =>
1546  {
1547  var (item, oldVal) = aEntity;
1548  entities.Add(item);
1549 
1550  if (!oldValues.ContainsKey(oldVal))
1551  {
1552  oldValues.Add(oldVal, new List<ISerializableEntity> { item });
1553  }
1554  else
1555  {
1556  oldValues[oldVal].Add(item);
1557  }
1558  });
1559 
1560  PropertyCommand cmd = new PropertyCommand(entities, property.Name.ToIdentifier(), value, oldValues);
1561  if (CommandBuffer != null)
1562  {
1563  if (CommandBuffer.Item1 == property && CommandBuffer.Item2.PropertyCount == cmd.PropertyCount)
1564  {
1565  if (!CommandBuffer.Item2.MergeInto(cmd))
1566  {
1568  }
1569  }
1570  else
1571  {
1573  }
1574  }
1575 
1576  NextCommandPush = DateTime.Now.AddSeconds(1);
1577  CommandBuffer = Tuple.Create(property, cmd);
1578  PropertyChangesActive = true;
1579  }
1580 
1581  return property.TrySetValue(entity, value);
1582  }
1583 
1584  public static bool IsEntityRemoved(object entity)
1585  => entity is Entity { Removed: true } or ItemComponent { Item.Removed: true };
1586 
1587  public static void CommitCommandBuffer()
1588  {
1589  if (CommandBuffer != null)
1590  {
1592  }
1593  CommandBuffer = null;
1594  PropertyChangesActive = false;
1595  }
1596 
1605  private Dictionary<ISerializableEntity, object> MultiSetProperties(SerializableProperty property, object parentObject, object value)
1606  {
1607  Dictionary<ISerializableEntity, object> affected = new Dictionary<ISerializableEntity, object>();
1608 
1609  if (!(Screen.Selected is SubEditorScreen) || MapEntity.SelectedList.Count <= 1) { return affected; }
1610  if (!(parentObject is ItemComponent || parentObject is Item || parentObject is Structure || parentObject is Hull)) { return affected; }
1611 
1612  foreach (var entity in MapEntity.SelectedList.Where(entity => entity != parentObject))
1613  {
1614  switch (parentObject)
1615  {
1616  case Hull _:
1617  case Structure _:
1618  case Item _:
1619  if (entity.GetType() == parentObject.GetType())
1620  {
1621  SafeAdd((ISerializableEntity) entity, property);
1622  property.PropertyInfo.SetValue(entity, value);
1623  }
1624  else if (entity is ISerializableEntity { SerializableProperties: { } } sEntity)
1625  {
1626  var props = sEntity.SerializableProperties;
1627  if (props.TryGetValue(property.Name.ToIdentifier(), out SerializableProperty foundProp) && foundProp.Attributes.OfType<Editable>().Any())
1628  {
1629  SafeAdd(sEntity, foundProp);
1630  foundProp.PropertyInfo.SetValue(entity, value);
1631  }
1632  }
1633  break;
1634  case ItemComponent parentComponent when entity is Item otherItem:
1635  if (otherItem == parentComponent.Item) { continue; }
1636  int componentIndex = parentComponent.Item.Components.FindAll(c => c.GetType() == parentComponent.GetType()).IndexOf(parentComponent);
1637  //find the component of the same type and same index from the other item
1638  var otherComponents = otherItem.Components.FindAll(c => c.GetType() == parentComponent.GetType());
1639  if (componentIndex >= 0 && componentIndex < otherComponents.Count)
1640  {
1641  var component = otherComponents[componentIndex];
1642  Debug.Assert(component.GetType() == parentObject.GetType());
1643  SafeAdd(component, property);
1644  if (value is string stringValue &&
1645  property.PropertyType.IsEnum &&
1646  Enum.TryParse(property.PropertyType, stringValue, out var enumValue))
1647  {
1648  property.PropertyInfo.SetValue(component, enumValue);
1649  }
1650  else
1651  {
1652  try
1653  {
1654  property.PropertyInfo.SetValue(component, value);
1655  }
1656  catch (ArgumentException e)
1657  {
1658  DebugConsole.ThrowError($"Failed to set the value of the property \"{property.Name}\" to {value?.ToString() ?? "null"}", e);
1659  }
1660  }
1661  }
1662  break;
1663  }
1664  }
1665 
1666  return affected;
1667 
1668  void SafeAdd(ISerializableEntity entity, SerializableProperty prop)
1669  {
1670  object obj = prop.GetValue(entity);
1671  if (prop.PropertyType == typeof(string) && obj == null) { obj = string.Empty; }
1672  affected.Add(entity, obj);
1673  }
1674  }
1675  }
1676 
1681  {
1682  public IEnumerable<string> GetExtraTextPickerEntries();
1683  }
1684 }
OnClickedHandler OnClicked
Definition: GUIButton.cs:16
OnPressedHandler OnPressed
Definition: GUIButton.cs:19
virtual GUIFont Font
virtual bool PlaySoundOnSelect
virtual RichString ToolTip
virtual Rectangle Rect
RectTransform RectTransform
static readonly List< GUIComponent > MessageBoxes
List< GUIButton > Buttons
OnValueChangedHandler OnValueChanged
OnValueEnteredHandler OnValueEntered
ButtonVisibility PlusMinusButtonVisibility
Whether or not the default +- buttons should be shown. Defaults to Automatic, which enables it for al...
TextBoxEvent OnDeselected
Definition: GUITextBox.cs:17
override RichString ToolTip
Definition: GUITextBox.cs:186
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
Definition: GUITextBox.cs:38
OnEnterHandler OnEnterPressed
Definition: GUITextBox.cs:29
override void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectOffset=null)
Definition: GUITextBox.cs:411
override bool Selected
Definition: GUITickBox.cs:18
readonly LocalizedString Text
The base class for components holding the different functionalities of the item
void Resize(Point absoluteSize, bool resizeChildren=true)
RectTransform?? Parent
bool RepositionChildInHierarchy(int index)
RectTransform(Vector2 relativeSize, RectTransform parent, Anchor anchor=Anchor.TopLeft, Pivot? pivot=null, Point? minSize=null, Point? maxSize=null, ScaleBasis scaleBasis=ScaleBasis.Normal)
Point?? MinSize
Min size in pixels. Does not affect scaling.
Point NonScaledSize
Size before scale multiplications.
GUIComponent CreateEnumField(ISerializableEntity entity, SerializableProperty property, object value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateStringField(ISerializableEntity entity, SerializableProperty property, string value, LocalizedString displayName, LocalizedString toolTip)
void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox)
GUIComponent CreateNewField(SerializableProperty property, ISerializableEntity entity)
GUIComponent CreateBoolField(ISerializableEntity entity, SerializableProperty property, bool value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateIntField(ISerializableEntity entity, SerializableProperty property, int value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateVector2Field(ISerializableEntity entity, SerializableProperty property, Vector2 value, LocalizedString displayName, LocalizedString toolTip)
static Tuple< SerializableProperty, PropertyCommand > CommandBuffer
Dictionary< Identifier, GUIComponent[]> Fields
Holds the references to the input fields.
GUIComponent CreateStringArrayField(ISerializableEntity entity, SerializableProperty property, string[] value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateFloatField(ISerializableEntity entity, SerializableProperty property, float value, LocalizedString displayName, LocalizedString toolTip)
SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, bool inGame, bool showName, string style="", int elementHeight=24, GUIFont titleFont=null)
GUIComponent CreatePointField(ISerializableEntity entity, SerializableProperty property, Point value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateVector4Field(ISerializableEntity entity, SerializableProperty property, Vector4 value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateEnumFlagField(ISerializableEntity entity, SerializableProperty property, object value, LocalizedString displayName, LocalizedString toolTip)
SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable< SerializableProperty > properties, bool showName, string style="", int elementHeight=24, GUIFont titleFont=null)
static bool IsEntityRemoved(object entity)
GUIComponent CreateVector3Field(ISerializableEntity entity, SerializableProperty property, Vector3 value, LocalizedString displayName, LocalizedString toolTip)
void UpdateValue(SerializableProperty property, object newValue, bool flash=true)
GUIComponent CreateColorField(ISerializableEntity entity, SerializableProperty property, Color value, LocalizedString displayName, LocalizedString toolTip)
GUIComponent CreateRectangleField(ISerializableEntity entity, SerializableProperty property, Rectangle value, LocalizedString displayName, LocalizedString toolTip)
void AddCustomContent(GUIComponent component, int childIndex)
float GetFloatValue(object parentObject)
object GetValue(object parentObject)
static GUIMessageBox CreatePropertyColorPicker(Color originalColor, SerializableProperty property, ISerializableEntity entity)
static void StoreCommand(Command command)
string FallBackTextTag
If a translation can't be found for the property name, this tag is used instead
Definition: Editable.cs:33
Implement this interface to insert extra entires to the text pickers created for the SerializableEnti...
IEnumerable< string > GetExtraTextPickerEntries()
NumberType
Definition: Enums.cs:741
@ Structure
Structures and hulls, but also items (for backwards support)!