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.OnSelected += (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 
794  HashSet<MapEntity> editedEntities = new HashSet<MapEntity>();
795  propertyBox.OnTextChanged += (textBox, text) =>
796  {
797  foreach (var entity in MapEntity.SelectedList)
798  {
799  editedEntities.Add(entity);
800  }
801  return true;
802  };
803  propertyBox.OnDeselected += (textBox, keys) => OnApply(textBox);
804  propertyBox.OnEnterPressed += (box, text) => OnApply(box);
805  refresh += () =>
806  {
807  if (propertyBox.Selected) { return; }
808 
809  propertyBox.Text = StripPrefabTags(property.GetValue(entity).ToString());
810  };
811 
812  bool OnApply(GUITextBox textBox)
813  {
814  List<MapEntity> prevSelected = MapEntity.SelectedList.ToList();
815  //reselect the entities that were selected during editing
816  //otherwise multi-editing won't work when we deselect the entities with unapplied changes in the textbox
817  if (editedEntities.Count > 1)
818  {
819  foreach (var entity in editedEntities)
820  {
821  MapEntity.SelectedList.Add(entity);
822  }
823  }
824  if (SetPropertyValue(property, entity, textBox.Text))
825  {
826  TrySendNetworkUpdate(entity, property);
827  textBox.Text = StripPrefabTags(property.GetValue(entity).ToString());
828  textBox.Flash(GUIStyle.Green, flashDuration: 1f);
829  }
830  //restore the entities that were selected before applying
831  MapEntity.SelectedList.Clear();
832  foreach (var entity in prevSelected)
833  {
834  MapEntity.SelectedList.Add(entity);
835  }
836  return true;
837  }
838 
839  if (!translationTextTag.IsEmpty)
840  {
841  new GUIButton(new RectTransform(new Vector2(browseButtonWidth, 1), frame.RectTransform, Anchor.TopRight), "...", style: "GUIButtonSmall")
842  {
843  OnClicked = (bt, userData) => { CreateTextPicker(translationTextTag.Value, entity, property, propertyBox); return true; }
844  };
845  propertyBox.OnTextChanged += (tb, text) =>
846  {
847  LocalizedString translatedText = TextManager.Get(text);
848  if (translatedText.IsNullOrEmpty())
849  {
850  propertyBox.TextColor = Color.Gray;
851  propertyBox.ToolTip = TextManager.GetWithVariable("StringPropertyCannotTranslate", "[tag]", text ?? string.Empty);
852  }
853  else
854  {
855  propertyBox.TextColor = GUIStyle.Green;
856  propertyBox.ToolTip = TextManager.GetWithVariable("StringPropertyTranslate", "[translation]", translatedText);
857  }
858  return true;
859  };
860  propertyBox.Text = value;
861  }
862 
863  if (isItemTagBox)
864  {
865  // create prefab tag box
866  var prefabFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), mainFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
867  {
868  Stretch = true
869  };
870 
871  new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), prefabFrame.RectTransform), TextManager.Get("predefinedtags.name"), font: GUIStyle.SmallFont, textAlignment: Alignment.Left)
872  {
873  ToolTip = TextManager.Get("predefinedtags.description")
874  };
875 
876  new GUITextBox(new RectTransform(new Vector2(inputFieldWidth, 1), prefabFrame.RectTransform), createPenIcon: false)
877  {
878  Readonly = true,
879  Font = GUIStyle.SmallFont,
880  Text = GetPrefabTags(it),
881  OverflowClip = true,
882  ToolTip = TextManager.Get("predefinedtags.description")
883  };
884 
885  // add container tag popup button to the modifiable tag box
886  new GUIButton(new RectTransform(new Vector2(browseButtonWidth, 1), frame.RectTransform, Anchor.TopRight), "...")
887  {
888  OnClicked = (_, _) => { it.CreateContainerTagPicker(propertyBox); return true; }
889  };
890  }
891 
892  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
893  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), new GUIComponent[] { propertyBox }); }
894  return frame;
895 
896  static bool IsItemTagBox(ISerializableEntity entity, string propertyName, [NotNullWhen(true)] out Item it)
897  {
898  if (entity is Item item && propertyName.Equals(nameof(Item.Tags), StringComparison.OrdinalIgnoreCase))
899  {
900  it = item;
901  return true;
902  }
903  it = null;
904  return false;
905  }
906 
907  string StripPrefabTags(string text)
908  {
909  if (!isItemTagBox) { return text; }
910 
911  string prefabTags = GetPrefabTags(it);
912  if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(prefabTags)) { return text; }
913 
914  string[] splitTags = text.Split(',');
915  return string.Join(',', splitTags.Where(t => !it.Prefab.Tags.Contains(t)));
916  }
917 
918  static string GetPrefabTags(Item it) => string.Join(',', it.Prefab.Tags);
919  }
920 
921  public GUIComponent CreatePointField(ISerializableEntity entity, SerializableProperty property, Point value, LocalizedString displayName, LocalizedString toolTip)
922  {
923  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
924  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
925  {
926  ToolTip = toolTip
927  };
928  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
929  {
930  Stretch = true,
931  RelativeSpacing = 0.05f
932  };
933  var editableAttribute = property.GetAttribute<Editable>();
934  var fields = new GUIComponent[2];
935  for (int i = 1; i >= 0; i--)
936  {
937  var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
938 
939  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
940  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
941  {
942  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
943  }
944 
945  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
946  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
947  NumberType.Int)
948  {
949  Font = GUIStyle.SmallFont
950  };
951 
952  if (i == 0)
953  numberInput.IntValue = value.X;
954  else
955  numberInput.IntValue = value.Y;
956 
957  numberInput.MinValueInt = editableAttribute.MinValueInt;
958  numberInput.MaxValueInt = editableAttribute.MaxValueInt;
959 
960  int comp = i;
961  numberInput.OnValueChanged += (numInput) =>
962  {
963  Point newVal = (Point)property.GetValue(entity);
964  if (comp == 0)
965  newVal.X = numInput.IntValue;
966  else
967  newVal.Y = numInput.IntValue;
968 
969  if (SetPropertyValue(property, entity, newVal))
970  {
971  TrySendNetworkUpdate(entity, property);
972  }
973  };
974  fields[i] = numberInput;
975  }
976  refresh += () =>
977  {
978  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
979  {
980  Point value = (Point)property.GetValue(entity);
981  ((GUINumberInput)fields[0]).IntValue = value.X;
982  ((GUINumberInput)fields[1]).IntValue = value.Y;
983  }
984  };
985  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
986  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
987  return frame;
988  }
989 
990  public GUIComponent CreateVector2Field(ISerializableEntity entity, SerializableProperty property, Vector2 value, LocalizedString displayName, LocalizedString toolTip)
991  {
992  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
993  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - inputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
994  {
995  ToolTip = toolTip
996  };
997  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(inputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
998  {
999  Stretch = true,
1000  RelativeSpacing = 0.05f
1001  };
1002  var editableAttribute = property.GetAttribute<Editable>();
1003  var fields = new GUIComponent[2];
1004  for (int i = 1; i >= 0; i--)
1005  {
1006  var element = new GUIFrame(new RectTransform(new Vector2(0.45f, 1), inputArea.RectTransform), style: null);
1007 
1008  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1009  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1010  {
1011  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1012  }
1013  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1014  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1015  NumberType.Float)
1016  {
1017  Font = GUIStyle.SmallFont
1018  };
1019 
1020  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1021  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1022  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1023  numberInput.ValueStep = editableAttribute.ValueStep;
1024  numberInput.PlusMinusButtonVisibility = editableAttribute
1025  .ForceShowPlusMinusButtons ? GUINumberInput.ButtonVisibility.ForceVisible : default;
1026 
1027  numberInput.FloatValue = i == 0 ? value.X : value.Y;
1028 
1029  int comp = i;
1030  numberInput.OnValueChanged += (numInput) =>
1031  {
1032  Vector2 newVal = (Vector2)property.GetValue(entity);
1033  if (comp == 0)
1034  {
1035  newVal.X = numInput.FloatValue;
1036  }
1037  else
1038  {
1039  newVal.Y = numInput.FloatValue;
1040  }
1041 
1042  if (SetPropertyValue(property, entity, newVal))
1043  {
1044  TrySendNetworkUpdate(entity, property);
1045  }
1046  };
1047  HandleSetterValueTampering(numberInput, () =>
1048  {
1049  Vector2 currVal = (Vector2)property.GetValue(entity);
1050  return comp == 0 ? currVal.X : currVal.Y;
1051  });
1052  fields[i] = numberInput;
1053  }
1054  refresh += () =>
1055  {
1056  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1057  {
1058  Vector2 value = (Vector2)property.GetValue(entity);
1059  ((GUINumberInput)fields[0]).FloatValue = value.X;
1060  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1061  }
1062  };
1063  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1064  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1065  return frame;
1066  }
1067 
1068  public GUIComponent CreateVector3Field(ISerializableEntity entity, SerializableProperty property, Vector3 value, LocalizedString displayName, LocalizedString toolTip)
1069  {
1070  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1071  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1072  {
1073  ToolTip = toolTip
1074  };
1075  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(largeInputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1076  {
1077  Stretch = true,
1078  RelativeSpacing = 0.03f
1079  };
1080  var editableAttribute = property.GetAttribute<Editable>();
1081  var fields = new GUIComponent[3];
1082  for (int i = 2; i >= 0; i--)
1083  {
1084  var element = new GUIFrame(new RectTransform(new Vector2(0.33f, 1), inputArea.RectTransform), style: null);
1085 
1086  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1087  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1088  {
1089  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1090  }
1091 
1092  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1093  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1094  NumberType.Float)
1095  {
1096  Font = GUIStyle.SmallFont
1097  };
1098 
1099  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1100  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1101  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1102  numberInput.ValueStep = editableAttribute.ValueStep;
1103 
1104  if (i == 0)
1105  numberInput.FloatValue = value.X;
1106  else if (i == 1)
1107  numberInput.FloatValue = value.Y;
1108  else if (i == 2)
1109  numberInput.FloatValue = value.Z;
1110 
1111  int comp = i;
1112  numberInput.OnValueChanged += (numInput) =>
1113  {
1114  Vector3 newVal = (Vector3)property.GetValue(entity);
1115  if (comp == 0)
1116  newVal.X = numInput.FloatValue;
1117  else if (comp == 1)
1118  newVal.Y = numInput.FloatValue;
1119  else
1120  newVal.Z = numInput.FloatValue;
1121 
1122  if (SetPropertyValue(property, entity, newVal))
1123  {
1124  TrySendNetworkUpdate(entity, property);
1125  }
1126  };
1127  fields[i] = numberInput;
1128  }
1129  refresh += () =>
1130  {
1131  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1132  {
1133  Vector3 value = (Vector3)property.GetValue(entity);
1134  ((GUINumberInput)fields[0]).FloatValue = value.X;
1135  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1136  ((GUINumberInput)fields[2]).FloatValue = value.Z;
1137  }
1138  };
1139  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1140  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1141  return frame;
1142  }
1143 
1144  public GUIComponent CreateVector4Field(ISerializableEntity entity, SerializableProperty property, Vector4 value, LocalizedString displayName, LocalizedString toolTip)
1145  {
1146  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1147  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1148  {
1149  ToolTip = toolTip
1150  };
1151  var editableAttribute = property.GetAttribute<Editable>();
1152  var fields = new GUIComponent[4];
1153  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(largeInputFieldWidth, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1154  {
1155  Stretch = true,
1156  RelativeSpacing = 0.01f
1157  };
1158  for (int i = 3; i >= 0; i--)
1159  {
1160  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);
1161 
1162  LocalizedString componentLabel = GUI.VectorComponentLabels[i];
1163  if (editableAttribute.VectorComponentLabels != null && i < editableAttribute.VectorComponentLabels.Length)
1164  {
1165  componentLabel = TextManager.Get(editableAttribute.VectorComponentLabels[i]);
1166  }
1167 
1168  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), componentLabel, font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1169  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1170  NumberType.Float)
1171  {
1172  Font = GUIStyle.SmallFont
1173  };
1174 
1175  numberInput.MinValueFloat = editableAttribute.MinValueFloat;
1176  numberInput.MaxValueFloat = editableAttribute.MaxValueFloat;
1177  numberInput.DecimalsToDisplay = editableAttribute.DecimalCount;
1178  numberInput.ValueStep = editableAttribute.ValueStep;
1179 
1180  if (i == 0)
1181  numberInput.FloatValue = value.X;
1182  else if (i == 1)
1183  numberInput.FloatValue = value.Y;
1184  else if (i == 2)
1185  numberInput.FloatValue = value.Z;
1186  else
1187  numberInput.FloatValue = value.W;
1188 
1189  int comp = i;
1190  numberInput.OnValueChanged += (numInput) =>
1191  {
1192  Vector4 newVal = (Vector4)property.GetValue(entity);
1193  if (comp == 0)
1194  newVal.X = numInput.FloatValue;
1195  else if (comp == 1)
1196  newVal.Y = numInput.FloatValue;
1197  else if (comp == 2)
1198  newVal.Z = numInput.FloatValue;
1199  else
1200  newVal.W = numInput.FloatValue;
1201 
1202  if (SetPropertyValue(property, entity, newVal))
1203  {
1204  TrySendNetworkUpdate(entity, property);
1205  }
1206  };
1207  fields[i] = numberInput;
1208  }
1209  refresh += () =>
1210  {
1211  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1212  {
1213  Vector4 value = (Vector4)property.GetValue(entity);
1214  ((GUINumberInput)fields[0]).FloatValue = value.X;
1215  ((GUINumberInput)fields[1]).FloatValue = value.Y;
1216  ((GUINumberInput)fields[2]).FloatValue = value.Z;
1217  ((GUINumberInput)fields[3]).FloatValue = value.W;
1218  }
1219  };
1220  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1221  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1222  return frame;
1223  }
1224 
1225  public GUIComponent CreateColorField(ISerializableEntity entity, SerializableProperty property, Color value, LocalizedString displayName, LocalizedString toolTip)
1226  {
1227  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1228  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f - largeInputFieldWidth, 1), frame.RectTransform) { MinSize = new Point(80, 26) }, displayName, font: GUIStyle.SmallFont)
1229  {
1230  ToolTip = displayName + '\n' + toolTip
1231  };
1232  label.Text = ToolBox.LimitString(label.Text, label.Font, label.Rect.Width);
1233  var colorBoxBack = new GUIFrame(new RectTransform(new Vector2(0.04f, 1), frame.RectTransform)
1234  {
1235  AbsoluteOffset = new Point(label.Rect.Width, 0)
1236  }, color: Color.Black, style: null);
1237  var colorBox = new GUIButton(new RectTransform(new Vector2(largeInputFieldWidth, 0.9f), colorBoxBack.RectTransform, Anchor.Center), style: null)
1238  {
1239  UserData = "colorpreview",
1240  OnClicked = (component, data) =>
1241  {
1242  if (!SubEditorScreen.IsSubEditor()) { return false; }
1243  if (GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { Closed: false, UserData: "colorpicker" })) { return false; }
1244 
1245  GUIMessageBox msgBox = SubEditorScreen.CreatePropertyColorPicker((Color) property.GetValue(entity), property, entity);
1246  return true;
1247  }
1248  };
1249  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)
1250  {
1251  Stretch = true,
1252  RelativeSpacing = 0.001f
1253  };
1254  var fields = new GUIComponent[4];
1255  for (int i = 3; i >= 0; i--)
1256  {
1257  var element = new GUILayoutGroup(new RectTransform(new Vector2(0.18f, 1), inputArea.RectTransform), isHorizontal: true)
1258  {
1259  Stretch = true
1260  };
1261  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);
1262  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1263  NumberType.Int)
1264  {
1265  Font = GUIStyle.SmallFont
1266  };
1267  numberInput.MinValueInt = 0;
1268  numberInput.MaxValueInt = 255;
1269 
1270  if (i == 0)
1271  numberInput.IntValue = value.R;
1272  else if (i == 1)
1273  numberInput.IntValue = value.G;
1274  else if (i == 2)
1275  numberInput.IntValue = value.B;
1276  else
1277  numberInput.IntValue = value.A;
1278 
1279  numberInput.Font = GUIStyle.SmallFont;
1280 
1281  int comp = i;
1282  numberInput.OnValueChanged += (numInput) =>
1283  {
1284  Color newVal = (Color)property.GetValue(entity);
1285  if (comp == 0)
1286  newVal.R = (byte)numInput.IntValue;
1287  else if (comp == 1)
1288  newVal.G = (byte)numInput.IntValue;
1289  else if (comp == 2)
1290  newVal.B = (byte)numInput.IntValue;
1291  else
1292  newVal.A = (byte)numInput.IntValue;
1293 
1294  if (SetPropertyValue(property, entity, newVal))
1295  {
1296  TrySendNetworkUpdate(entity, property);
1297  colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = newVal;
1298  }
1299  };
1300  colorBox.Color = colorBox.HoverColor = colorBox.PressedColor = colorBox.SelectedTextColor = (Color)property.GetValue(entity);
1301  fields[i] = numberInput;
1302  }
1303  refresh += () =>
1304  {
1305  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1306  {
1307  Color value = (Color)property.GetValue(entity);
1308  ((GUINumberInput)fields[0]).IntValue = value.R;
1309  ((GUINumberInput)fields[1]).IntValue = value.G;
1310  ((GUINumberInput)fields[2]).IntValue = value.B;
1311  ((GUINumberInput)fields[3]).IntValue = value.A;
1312  }
1313  };
1314  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Max(c => c.MinSize.Y));
1315  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1316  return frame;
1317  }
1318 
1319  public GUIComponent CreateRectangleField(ISerializableEntity entity, SerializableProperty property, Rectangle value, LocalizedString displayName, LocalizedString toolTip)
1320  {
1321  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1322  var label = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1323  {
1324  ToolTip = displayName + '\n' + toolTip
1325  };
1326  label.Text = ToolBox.LimitString(label.Text, label.Font, label.Rect.Width);
1327  var fields = new GUIComponent[4];
1328  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1), frame.RectTransform, Anchor.TopRight), isHorizontal: true, childAnchor: Anchor.CenterRight)
1329  {
1330  Stretch = true,
1331  RelativeSpacing = 0.01f
1332  };
1333  for (int i = 3; i >= 0; i--)
1334  {
1335  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);
1336  new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform, Anchor.CenterLeft), GUI.RectComponentLabels[i], font: GUIStyle.SmallFont, textAlignment: Alignment.Center);
1337  GUINumberInput numberInput = new GUINumberInput(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.CenterRight),
1338  NumberType.Int)
1339  {
1340  Font = GUIStyle.SmallFont
1341  };
1342  // Not sure if the min value could in any case be negative.
1343  numberInput.MinValueInt = 0;
1344  // Just something reasonable to keep the value in the input rect.
1345  numberInput.MaxValueInt = 9999;
1346 
1347  if (i == 0)
1348  numberInput.IntValue = value.X;
1349  else if (i == 1)
1350  numberInput.IntValue = value.Y;
1351  else if (i == 2)
1352  numberInput.IntValue = value.Width;
1353  else
1354  numberInput.IntValue = value.Height;
1355 
1356  int comp = i;
1357  numberInput.OnValueChanged += (numInput) =>
1358  {
1359  Rectangle newVal = (Rectangle)property.GetValue(entity);
1360  if (comp == 0)
1361  newVal.X = numInput.IntValue;
1362  else if (comp == 1)
1363  newVal.Y = numInput.IntValue;
1364  else if (comp == 2)
1365  newVal.Width = numInput.IntValue;
1366  else
1367  newVal.Height = numInput.IntValue;
1368 
1369  if (SetPropertyValue(property, entity, newVal))
1370  {
1371  TrySendNetworkUpdate(entity, property);
1372  }
1373  };
1374  fields[i] = numberInput;
1375  }
1376  refresh += () =>
1377  {
1378  if (!fields.Any(f => ((GUINumberInput)f).TextBox.Selected))
1379  {
1380  Rectangle value = (Rectangle)property.GetValue(entity);
1381  ((GUINumberInput)fields[0]).IntValue = value.X;
1382  ((GUINumberInput)fields[1]).IntValue = value.Y;
1383  ((GUINumberInput)fields[2]).IntValue = value.Width;
1384  ((GUINumberInput)fields[3]).IntValue = value.Height;
1385  }
1386  };
1387  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1388  return frame;
1389  }
1390 
1392  {
1393  int elementCount = (value.Length + 1);
1394  var frame = new GUIFrame(new RectTransform(new Point(Rect.Width, elementCount * elementHeight), layoutGroup.RectTransform, isFixedSize: true), color: Color.Transparent);
1395  var label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f / elementCount), frame.RectTransform), displayName, font: GUIStyle.SmallFont)
1396  {
1397  ToolTip = toolTip
1398  };
1399  var editableAttribute = property.GetAttribute<Editable>();
1400  var fields = new GUIComponent[value.Length];
1401  var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, (float)(elementCount - 1) / elementCount), frame.RectTransform, anchor: Anchor.BottomLeft))
1402  {
1403  RelativeSpacing = 0.01f
1404  };
1405  elementCount -= 1;
1406 
1407  for (int i = 0; i < value.Length; i++)
1408  {
1409  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);
1410  var elementLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, element.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
1411  // Set the label to be (i + 1) so it's easier to understand for non-programmers
1412  string componentLabel = (i + 1).ToString();
1413  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);
1414  GUITextBox textBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), elementLayoutGroup.RectTransform), text: value[i])
1415  {
1416  Font = GUIStyle.SmallFont,
1417  Readonly = Readonly
1418  };
1419  int comp = i;
1420  textBox.OnEnterPressed += (textBox, text) => OnApply(textBox);
1421  textBox.OnDeselected += (textBox, keys) => OnApply(textBox);
1422  fields[i] = textBox;
1423 
1424  bool OnApply(GUITextBox textBox)
1425  {
1426  // Reserve the semicolon for serializing the value
1427  bool containsForbiddenCharacters = textBox.Text.Contains(';');
1428  string[] newValue = (string[])property.GetValue(entity);
1429  if (!containsForbiddenCharacters)
1430  {
1431  newValue[comp] = textBox.Text;
1432  if (SetPropertyValue(property, entity, newValue))
1433  {
1434  TrySendNetworkUpdate(entity, property);
1435  textBox.Flash(color: GUIStyle.Green, flashDuration: 1f);
1436  }
1437  }
1438  else
1439  {
1440  textBox.Text = newValue[comp];
1441  textBox.Flash(color: GUIStyle.Red, flashDuration: 1f);
1442  }
1443  return true;
1444  }
1445  }
1446 
1447  refresh += () =>
1448  {
1449  if (fields.None(f => ((GUITextBox)f).Selected))
1450  {
1451  string[] value = (string[])property.GetValue(entity);
1452  for (int i = 0; i < fields.Length; i++)
1453  {
1454  ((GUITextBox)fields[i]).Text = value[i];
1455  }
1456  }
1457  };
1458 
1459  frame.RectTransform.MinSize = new Point(0, frame.RectTransform.Children.Sum(c => c.MinSize.Y));
1460  if (!Fields.ContainsKey(property.Name)) { Fields.Add(property.Name.ToIdentifier(), fields); }
1461  return frame;
1462  }
1463 
1464  public void CreateTextPicker(string textTag, ISerializableEntity entity, SerializableProperty property, GUITextBox textBox)
1465  {
1466  var msgBox = new GUIMessageBox("", "", new LocalizedString[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.5f), new Point(300, 400));
1467  msgBox.Buttons[0].OnClicked = msgBox.Close;
1468 
1469  var textList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.8f), msgBox.Content.RectTransform, Anchor.TopCenter))
1470  {
1471  PlaySoundOnSelect = true,
1472  OnSelected = (component, userData) =>
1473  {
1474  string text = userData as string ?? "";
1475 
1476  if (SetPropertyValue(property, entity, text))
1477  {
1478  TrySendNetworkUpdate(entity, property);
1479  textBox.Text = (string)property.GetValue(entity);
1480  textBox.Deselect();
1481  }
1482  return true;
1483  }
1484  };
1485 
1486  var tagTextPairs = TextManager.GetAllTagTextPairs().ToList();
1487  tagTextPairs.Sort((t1, t2) => { return t1.Value.CompareTo(t2.Value); });
1488  foreach (KeyValuePair<Identifier, string> tagTextPair in tagTextPairs)
1489  {
1490  if (!tagTextPair.Key.StartsWith(textTag)) { continue; }
1491  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) },
1492  ToolBox.LimitString(tagTextPair.Value, GUIStyle.Font, textList.Content.Rect.Width))
1493  {
1494  UserData = tagTextPair.Key.ToString()
1495  };
1496  }
1497 
1498  if (entity is IHasExtraTextPickerEntries hasExtraTextPickerEntries)
1499  {
1500  foreach (string extraEntry in hasExtraTextPickerEntries.GetExtraTextPickerEntries())
1501  {
1502  new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), textList.Content.RectTransform) { MinSize = new Point(0, 20) },
1503  ToolBox.LimitString(extraEntry, GUIStyle.Font, textList.Content.Rect.Width), GUIStyle.Green)
1504  {
1505  UserData = extraEntry
1506  };
1507  }
1508  }
1509  }
1510 
1511  private static void TrySendNetworkUpdate(ISerializableEntity entity, SerializableProperty property)
1512  {
1513  if (IsEntityRemoved(entity)) { return; }
1514 
1515  if (GameMain.Client != null)
1516  {
1517  if (entity is Item item)
1518  {
1519  GameMain.Client.CreateEntityEvent(item, new Item.ChangePropertyEventData(property, item));
1520  }
1521  else if (entity is ItemComponent ic)
1522  {
1523  GameMain.Client.CreateEntityEvent(ic.Item, new Item.ChangePropertyEventData(property, ic));
1524  }
1525  }
1526  }
1527 
1528  private bool SetPropertyValue(SerializableProperty property, object entity, object value)
1529  {
1530  if (LockEditing || IsEntityRemoved(entity) || Readonly) { return false; }
1531 
1532  object oldData = property.GetValue(entity);
1533  // some properties have null as the default string value
1534  if (oldData == null && value is string) { oldData = ""; }
1535  if (entity is ISerializableEntity sEntity && Screen.Selected is SubEditorScreen && !Equals(oldData, value))
1536  {
1537  List<ISerializableEntity> entities = new List<ISerializableEntity> { sEntity };
1538  Dictionary<ISerializableEntity, object> affected = MultiSetProperties(property, entity, value);
1539 
1540  Dictionary<object, List<ISerializableEntity>> oldValues = new Dictionary<object, List<ISerializableEntity>> {{ oldData!, new List<ISerializableEntity> { sEntity }}};
1541 
1542  affected.ForEach(aEntity =>
1543  {
1544  var (item, oldVal) = aEntity;
1545  entities.Add(item);
1546 
1547  if (!oldValues.ContainsKey(oldVal))
1548  {
1549  oldValues.Add(oldVal, new List<ISerializableEntity> { item });
1550  }
1551  else
1552  {
1553  oldValues[oldVal].Add(item);
1554  }
1555  });
1556 
1557  PropertyCommand cmd = new PropertyCommand(entities, property.Name.ToIdentifier(), value, oldValues);
1558  if (CommandBuffer != null)
1559  {
1560  if (CommandBuffer.Item1 == property && CommandBuffer.Item2.PropertyCount == cmd.PropertyCount)
1561  {
1562  if (!CommandBuffer.Item2.MergeInto(cmd))
1563  {
1565  }
1566  }
1567  else
1568  {
1570  }
1571  }
1572 
1573  NextCommandPush = DateTime.Now.AddSeconds(1);
1574  CommandBuffer = Tuple.Create(property, cmd);
1575  PropertyChangesActive = true;
1576  }
1577 
1578  return property.TrySetValue(entity, value);
1579  }
1580 
1581  public static bool IsEntityRemoved(object entity)
1582  => entity is Entity { Removed: true } or ItemComponent { Item.Removed: true };
1583 
1584  public static void CommitCommandBuffer()
1585  {
1586  if (CommandBuffer != null)
1587  {
1589  }
1590  CommandBuffer = null;
1591  PropertyChangesActive = false;
1592  }
1593 
1602  private Dictionary<ISerializableEntity, object> MultiSetProperties(SerializableProperty property, object parentObject, object value)
1603  {
1604  Dictionary<ISerializableEntity, object> affected = new Dictionary<ISerializableEntity, object>();
1605 
1606  if (!(Screen.Selected is SubEditorScreen) || MapEntity.SelectedList.Count <= 1) { return affected; }
1607  if (!(parentObject is ItemComponent || parentObject is Item || parentObject is Structure || parentObject is Hull)) { return affected; }
1608 
1609  foreach (var entity in MapEntity.SelectedList.Where(entity => entity != parentObject))
1610  {
1611  switch (parentObject)
1612  {
1613  case Hull _:
1614  case Structure _:
1615  case Item _:
1616  if (entity.GetType() == parentObject.GetType())
1617  {
1618  SafeAdd((ISerializableEntity) entity, property);
1619  property.PropertyInfo.SetValue(entity, value);
1620  }
1621  else if (entity is ISerializableEntity { SerializableProperties: { } } sEntity)
1622  {
1623  var props = sEntity.SerializableProperties;
1624  if (props.TryGetValue(property.Name.ToIdentifier(), out SerializableProperty foundProp) && foundProp.Attributes.OfType<Editable>().Any())
1625  {
1626  SafeAdd(sEntity, foundProp);
1627  foundProp.PropertyInfo.SetValue(entity, value);
1628  }
1629  }
1630  break;
1631  case ItemComponent parentComponent when entity is Item otherItem:
1632  if (otherItem == parentComponent.Item) { continue; }
1633  int componentIndex = parentComponent.Item.Components.FindAll(c => c.GetType() == parentComponent.GetType()).IndexOf(parentComponent);
1634  //find the component of the same type and same index from the other item
1635  var otherComponents = otherItem.Components.FindAll(c => c.GetType() == parentComponent.GetType());
1636  if (componentIndex >= 0 && componentIndex < otherComponents.Count)
1637  {
1638  var component = otherComponents[componentIndex];
1639  Debug.Assert(component.GetType() == parentObject.GetType());
1640  SafeAdd(component, property);
1641  if (value is string stringValue &&
1642  property.PropertyType.IsEnum &&
1643  Enum.TryParse(property.PropertyType, stringValue, out var enumValue))
1644  {
1645  property.PropertyInfo.SetValue(component, enumValue);
1646  }
1647  else
1648  {
1649  try
1650  {
1651  property.PropertyInfo.SetValue(component, value);
1652  }
1653  catch (ArgumentException e)
1654  {
1655  DebugConsole.ThrowError($"Failed to set the value of the property \"{property.Name}\" to {value?.ToString() ?? "null"}", e);
1656  }
1657  }
1658  }
1659  break;
1660  }
1661  }
1662 
1663  return affected;
1664 
1665  void SafeAdd(ISerializableEntity entity, SerializableProperty prop)
1666  {
1667  object obj = prop.GetValue(entity);
1668  if (prop.PropertyType == typeof(string) && obj == null) { obj = string.Empty; }
1669  affected.Add(entity, obj);
1670  }
1671  }
1672  }
1673 
1678  {
1679  public IEnumerable<string> GetExtraTextPickerEntries();
1680  }
1681 }
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:30
Implement this interface to insert extra entires to the text pickers created for the SerializableEnti...
IEnumerable< string > GetExtraTextPickerEntries()
NumberType
Definition: Enums.cs:715