Client LuaCsForBarotrauma
GUIDropDown.cs
1 using EventInput;
2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
4 using Microsoft.Xna.Framework.Input;
5 using System.Collections.Generic;
6 using System.Linq;
7 
8 namespace Barotrauma
9 {
11  {
14  public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
21 
26 
28 
29  private readonly GUIButton button;
30  private readonly GUIImage icon;
31  private readonly GUIListBox listBox;
32 
33  private RectTransform currentHighestParent;
34  private List<RectTransform> parentHierarchy = new List<RectTransform>();
35 
36  private readonly bool selectMultiple;
37 
38  public bool Dropped { get; set; }
39 
40  public bool AllowNonText { get; set; }
41 
42  public object SelectedItemData
43  {
44  get
45  {
46  if (listBox.SelectedComponent == null) return null;
47  return listBox.SelectedComponent.UserData;
48  }
49  }
50 
51  public override bool Enabled
52  {
53  get { return listBox.Enabled; }
54  set { listBox.Enabled = value; }
55  }
56 
57  public bool ButtonEnabled
58  {
59  get { return button.Enabled; }
60  set
61  {
62  button.Enabled = value;
63  if (icon != null) { icon.Enabled = value; }
64  }
65  }
66 
68  {
69  get { return listBox.SelectedComponent; }
70  }
71 
72  public override bool Selected
73  {
74  get
75  {
76  return Dropped;
77  }
78  set
79  {
80  Dropped = value;
81  }
82  }
83 
85  {
86  get { return listBox; }
87  }
88 
89  public object SelectedData
90  {
91  get
92  {
93  return listBox.SelectedComponent?.UserData;
94  }
95  }
96 
97  public int SelectedIndex
98  {
99  get
100  {
101  if (listBox.SelectedComponent == null) return -1;
102  return listBox.Content.GetChildIndex(listBox.SelectedComponent);
103  }
104  }
105 
106  public Color ButtonTextColor
107  {
108  get { return button.TextColor; }
109  set { button.TextColor = value; }
110  }
111 
112  public override GUIFont Font
113  {
114  get { return button?.Font ?? base.Font; }
115  set
116  {
117  if (button != null) { button.Font = value; }
118  }
119  }
120 
121  public void ReceiveTextInput(char inputChar)
122  {
123  GUI.KeyboardDispatcher.Subscriber = null;
124  }
125  public void ReceiveTextInput(string text) { }
126  public void ReceiveCommandInput(char command) { }
127  public void ReceiveEditingInput(string text, int start, int length) { }
128 
129  public void ReceiveSpecialInput(Keys key)
130  {
131  switch (key)
132  {
133  case Keys.Up:
134  case Keys.Down:
135  listBox.ReceiveSpecialInput(key);
136  GUI.KeyboardDispatcher.Subscriber = this;
137  break;
138  default:
139  GUI.KeyboardDispatcher.Subscriber = null;
140  break;
141  }
142  }
143 
144  private readonly List<object> selectedDataMultiple = new List<object>();
145  public IEnumerable<object> SelectedDataMultiple
146  {
147  get { return selectedDataMultiple; }
148  }
149 
150  private readonly List<int> selectedIndexMultiple = new List<int>();
151  public IEnumerable<int> SelectedIndexMultiple
152  {
153  get { return selectedIndexMultiple; }
154  }
155 
156  public bool MustSelectAtLeastOne;
157 
159  {
160  get { return button.Text; }
161  set { button.Text = value; }
162  }
163 
164  public override RichString ToolTip
165  {
166  get
167  {
168  return base.ToolTip;
169  }
170  set
171  {
172  base.ToolTip = value;
173  button.ToolTip = value;
174  listBox.ToolTip = value;
175  }
176  }
177 
178  public GUIImage DropDownIcon => icon;
179 
180  public Vector4 Padding => button.TextBlock.Padding;
181 
182  public GUIDropDown(RectTransform rectT, LocalizedString text = null, int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false, Alignment textAlignment = Alignment.CenterLeft, float listBoxScale = 1) : base(style, rectT)
183  {
184  text ??= LocalizedString.EmptyString;
185 
186  HoverCursor = CursorState.Hand;
187  CanBeFocused = true;
188 
189  this.selectMultiple = selectMultiple;
190 
191  button = new GUIButton(new RectTransform(Vector2.One, rectT), text, textAlignment, style: "GUIDropDown")
192  {
193  OnClicked = OnClicked,
194  TextBlock = { OverflowClip = true }
195  };
196  GUIStyle.Apply(button, "", this);
197  button.TextBlock.SetTextPos();
198 
199  Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
200  Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
201  listBox = new GUIListBox(new RectTransform(new Point((int)(Rect.Width * listBoxScale), Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
202  { IsFixedSize = false }, style: null)
203  {
204  Enabled = !selectMultiple,
205  PlaySoundOnSelect = true,
206  };
207  if (!selectMultiple)
208  {
209  listBox.AfterSelected = (component, obj) =>
210  {
211  SelectItem(component, obj);
212  AfterSelected?.Invoke(component, obj);
213  return true;
214  };
215  }
216  GUIStyle.Apply(listBox, "GUIListBox", this);
217  GUIStyle.Apply(listBox.ContentBackground, "GUIListBox", this);
218 
219  if (button.Style.ChildStyles.ContainsKey("dropdownicon".ToIdentifier()))
220  {
221  icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
222  icon.ApplyStyle(button.Style.ChildStyles["dropdownicon".ToIdentifier()]);
223  //move the text away from the icon
224  button.TextBlock.Padding += new Vector4(0, 0, icon.Rect.Width, 0);
225  }
226 
227  currentHighestParent = FindHighestParent();
228  currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
229  rectT.ParentChanged += _ => RefreshListBoxParent();
230  }
231 
232 
238  private RectTransform FindHighestParent()
239  {
240  parentHierarchy.Clear();
241 
242  //collect entire parent hierarchy to a list
243  parentHierarchy = new List<RectTransform>() { RectTransform.Parent };
244  RectTransform parent = parentHierarchy.Last();
245  while (parent?.Parent != null)
246  {
247  parentHierarchy.Add(parent.Parent);
248  parent = parent.Parent;
249  }
250 
251  //find the highest parent that has a guicomponent with a style
252  //(and so should be rendered and not just some empty parent/root element used for constructing a layout)
253  for (int i = parentHierarchy.Count - 1; i > 0; i--)
254  {
255  if (parentHierarchy[i] is GUICanvas ||
256  parentHierarchy[i].GUIComponent == null ||
257  parentHierarchy[i].GUIComponent.Style == null ||
258  parentHierarchy[i].GUIComponent == Screen.Selected?.Frame)
259  {
260  parentHierarchy.RemoveAt(i);
261  }
262  else
263  {
264  break;
265  }
266  }
267  return parentHierarchy.Last();
268  }
269 
270  public GUIComponent AddItem(LocalizedString text, object userData = null, LocalizedString toolTip = null, Color? color = null, Color? textColor = null)
271  {
272  toolTip ??= "";
273  if (selectMultiple)
274  {
275  var frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, style: "ListBoxElement", color: color)
276  {
277  UserData = userData,
278  ToolTip = toolTip
279  };
280  var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.8f), frame.RectTransform, anchor: Anchor.CenterLeft) { MaxSize = new Point(int.MaxValue, (int)(button.Rect.Height * 0.8f)) }, text)
281  {
282  UserData = userData,
283  ToolTip = toolTip,
284  OnSelected = (GUITickBox tb) =>
285  {
286  if (MustSelectAtLeastOne && selectedIndexMultiple.Count <= 1 && !tb.Selected)
287  {
288  tb.Selected = true;
289  return false;
290  }
291 
292  if (OnSelected != null && !OnSelected.Invoke(tb.Parent, tb.Parent.UserData))
293  {
294  return false;
295  }
296 
297  List<LocalizedString> texts = new List<LocalizedString>();
298  selectedDataMultiple.Clear();
299  selectedIndexMultiple.Clear();
300  int i = 0;
301  foreach (GUIComponent child in ListBox.Content.Children)
302  {
303  var tickBox = child.GetChild<GUITickBox>();
304  if (tickBox is { Selected: true })
305  {
306  selectedDataMultiple.Add(child.UserData);
307  selectedIndexMultiple.Add(i);
308  texts.Add(tickBox.Text);
309  }
310  i++;
311  }
312  button.Text = LocalizedString.Join(", ", texts);
313  AfterSelected?.Invoke(tb.Parent, SelectedData);
314  return true;
315  }
316  };
317  return frame;
318  }
319  else
320  {
321  return new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width, button.Rect.Height), listBox.Content.RectTransform) { IsFixedSize = false }, text, style: "ListBoxElement", color: color, textColor: textColor)
322  {
323  UserData = userData,
324  ToolTip = toolTip
325  };
326  }
327  }
328 
329  public override void ClearChildren()
330  {
331  listBox.ClearChildren();
332  }
333 
334  public IEnumerable<GUIComponent> GetChildren()
335  {
336  return listBox.Content.Children;
337  }
338 
339  private bool SelectItem(GUIComponent component, object obj)
340  {
341  if (selectMultiple)
342  {
343  foreach (GUIComponent child in ListBox.Content.Children)
344  {
345  var tickBox = child.GetChild<GUITickBox>();
346  if (Equals(obj, child.UserData)) { tickBox.Selected = true; }
347  }
348  }
349  else
350  {
351  if (component is not GUITextBlock textBlock)
352  {
353  textBlock = component.GetChild<GUITextBlock>();
354  if (textBlock is null && !AllowNonText) { return false; }
355  }
356  button.Text = textBlock?.Text ?? "";
357  }
358  OnSelected?.Invoke(component, obj);
359  Dropped = false;
360  return true;
361  }
362 
363  public void SelectItem(object userData)
364  {
365  if (selectMultiple)
366  {
367  SelectItem(listBox.Content.FindChild(userData), userData);
368  }
369  else
370  {
371  listBox.Select(userData);
372  }
374  }
375 
376  public void Select(int index)
377  {
378  if (selectMultiple)
379  {
380  var child = listBox.Content.GetChild(index);
381  if (child != null)
382  {
383  SelectItem(null, child.UserData);
384  }
385  }
386  else
387  {
388  listBox.Select(index);
389  }
390  AfterSelected?.Invoke(this, SelectedData);
391  }
392 
393  private bool wasOpened;
394 
395  private bool OnClicked(GUIComponent component, object obj)
396  {
397  if (wasOpened) return false;
398 
399  wasOpened = true;
400  Dropped = !Dropped;
401  if (Dropped && Enabled)
402  {
403  OnDropped?.Invoke(this, UserData);
404  listBox.UpdateScrollBarSize();
405  listBox.UpdateDimensions();
406 
407  GUI.KeyboardDispatcher.Subscriber = this;
408  }
409  else if (GUI.KeyboardDispatcher.Subscriber == this)
410  {
411  GUI.KeyboardDispatcher.Subscriber = null;
412  }
413  return true;
414  }
415 
416  public void RefreshListBoxParent()
417  {
418  currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
419  if (RectTransform.Parent == null) { return; }
420 
421  currentHighestParent = FindHighestParent();
422  currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
423  }
424 
425  private void AddListBoxToGUIUpdateList(GUIComponent parent)
426  {
427  //the parent is not our parent anymore :(
428  //can happen when subscribed to a parent higher in the hierarchy (instead of the direct parent),
429  //and somewhere between this component and the higher parent a component was removed
430  for (int i = 1; i < parentHierarchy.Count; i++)
431  {
432  if (parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
433  {
434  continue;
435  }
436 
437  parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
438  return;
439  }
440 
441  if (Dropped)
442  {
443  listBox.AddToGUIUpdateList(false, 1);
444  }
445  }
446 
447  public override void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true)
448  {
449  if (!Visible) return;
450 
451  AutoDraw = false;
452  Draw(spriteBatch);
453  if (alsoChildren)
454  {
455  button.DrawManually(spriteBatch, alsoChildren, recursive);
456  }
457  }
458 
459  public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
460  {
461  base.AddToGUIUpdateList(true, order);
462  if (!ignoreChildren)
463  {
464  button.AddToGUIUpdateList(false, order);
465  }
466  }
467 
468  protected override void Update(float deltaTime)
469  {
470  if (!Visible) return;
471  wasOpened = false;
472  base.Update(deltaTime);
474  {
475  Rectangle listBoxRect = listBox.Rect;
476  if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
477  {
478  Dropped = false;
479  if (GUI.KeyboardDispatcher.Subscriber == this)
480  {
481  GUI.KeyboardDispatcher.Subscriber = null;
482  }
483  }
484  }
485  }
486  }
487 }
GUIComponent(string style, RectTransform rectT)
This is the new constructor.
GUIComponent GetChild(int index)
Definition: GUIComponent.cs:54
bool IsParentOf(GUIComponent component, bool recursive=true)
Definition: GUIComponent.cs:75
virtual bool PlaySoundOnSelect
Action< GUIComponent > OnAddedToGUIUpdateList
virtual void Draw(SpriteBatch spriteBatch)
virtual Rectangle Rect
RectTransform RectTransform
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
override void Update(float deltaTime)
Definition: GUIDropDown.cs:468
OnSelectedHandler AfterSelected
Triggers after an item has been selected from the dropdown, all validation has been done and the new ...
Definition: GUIDropDown.cs:25
void SelectItem(object userData)
Definition: GUIDropDown.cs:363
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
Definition: GUIDropDown.cs:270
void ReceiveTextInput(string text)
Definition: GUIDropDown.cs:125
void ReceiveSpecialInput(Keys key)
Definition: GUIDropDown.cs:129
GUIDropDown(RectTransform rectT, LocalizedString text=null, int elementCount=4, string style="", bool selectMultiple=false, bool dropAbove=false, Alignment textAlignment=Alignment.CenterLeft, float listBoxScale=1)
Definition: GUIDropDown.cs:182
void ReceiveTextInput(char inputChar)
Definition: GUIDropDown.cs:121
IEnumerable< GUIComponent > GetChildren()
Definition: GUIDropDown.cs:334
OnSelectedHandler OnSelected
Triggers when some item is cliecked from the dropdown. Note that SelectedData is not set yet when thi...
Definition: GUIDropDown.cs:20
IEnumerable< int > SelectedIndexMultiple
Definition: GUIDropDown.cs:152
override GUIFont??? Font
Definition: GUIDropDown.cs:113
void ReceiveEditingInput(string text, int start, int length)
Definition: GUIDropDown.cs:127
override bool Selected
Definition: GUIDropDown.cs:73
void Select(int index)
Definition: GUIDropDown.cs:376
override bool Enabled
Definition: GUIDropDown.cs:52
OnSelectedHandler OnDropped
Definition: GUIDropDown.cs:27
override void DrawManually(SpriteBatch spriteBatch, bool alsoChildren=false, bool recursive=true)
By default, all the gui elements are drawn automatically in the same order they appear on the update ...
Definition: GUIDropDown.cs:447
override void ClearChildren()
Definition: GUIDropDown.cs:329
override void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
Definition: GUIDropDown.cs:459
override RichString ToolTip
Definition: GUIDropDown.cs:165
delegate bool OnSelectedHandler(GUIComponent selected, object obj=null)
IEnumerable< object > SelectedDataMultiple
Definition: GUIDropDown.cs:146
GUIComponent SelectedComponent
Definition: GUIDropDown.cs:68
LocalizedString Text
Definition: GUIDropDown.cs:159
void ReceiveCommandInput(char command)
Definition: GUIDropDown.cs:126
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
LocalizedString Text
Definition: GUITickBox.cs:109
override bool Selected
Definition: GUITickBox.cs:18
static readonly RawLString EmptyString
static LocalizedString Join(string separator, params LocalizedString[] subStrs)
GUIComponent GUIComponent
Should be assigned only by GUIComponent. Note that RectTransform is created first and the GUIComponen...
RectTransform?? Parent
Action< RectTransform > ParentChanged
CursorState
Definition: GUI.cs:40