2 using Microsoft.Xna.Framework;
3 using Microsoft.Xna.Framework.Graphics;
5 using System.Collections.Generic;
8 using Microsoft.Xna.Framework.Input;
36 private readonly Dictionary<GUIComponent, bool> childVisible =
new Dictionary<GUIComponent, bool>();
38 private int totalSize;
39 private bool childrenNeedsRecalculation;
40 private bool scrollBarNeedsRecalculation;
41 private bool dimensionsNeedsRecalculation;
44 private int ScrollBarSize
50 float desiredSize = 25.0f;
51 float scaledSize = desiredSize * GUI.Scale;
52 return (
int)Math.Min((desiredSize + scaledSize) / 2.0f,
Rect.Height / 3);
60 RequireShiftToSelectMultiple,
79 private bool useGridLayout;
114 get {
return useGridLayout; }
117 if (useGridLayout == value)
return;
118 useGridLayout = value;
119 childrenNeedsRecalculation =
true;
120 scrollBarNeedsRecalculation =
true;
127 private readonly
bool useMouseDownToSelect =
false;
129 private Vector4? overridePadding;
134 if (overridePadding.HasValue) {
return overridePadding.Value; }
135 if (
Style ==
null) {
return Vector4.Zero; }
140 dimensionsNeedsRecalculation =
true;
141 overridePadding = value;
191 get {
return totalSize; }
196 public override Color Color
228 dimensionsNeedsRecalculation =
true;
236 private bool IsScrollBarOnDefaultSide {
get;
set; }
250 return currentDragMode;
254 if (value ==
DragMode.NoDragging && currentDragMode !=
DragMode.NoDragging && isDraggingElement)
258 currentDragMode = value;
263 private Point dragMousePosRelativeToTopLeftCorner;
264 private bool isDraggingElement => draggedElement !=
null;
272 return draggedElement;
276 if (value == draggedElement) {
return; }
277 draggedElement = value;
280 if (value ==
null) {
return; }
298 private readonly
bool isHorizontal;
354 public GUIListBox(
RectTransform rectT,
bool isHorizontal =
false, Color?
color =
null,
string style =
"",
bool isScrollBarOnDefaultSide =
true,
bool useMouseDownToSelect =
false) : base(style, rectT)
356 this.isHorizontal = isHorizontal;
359 selected =
new List<GUIComponent>();
360 this.useMouseDownToSelect = useMouseDownToSelect;
371 scrollBarNeedsRecalculation =
true;
372 childrenNeedsRecalculation =
true;
382 IsScrollBarOnDefaultSide = isScrollBarOnDefaultSide;
387 size =
new Point((
int)(
Rect.Width -
Padding.X -
Padding.Z), (
int)(ScrollBarSize * GUI.Scale));
388 anchor = isScrollBarOnDefaultSide ?
Anchor.BottomCenter :
Anchor.TopCenter;
394 anchor = isScrollBarOnDefaultSide ?
Anchor.CenterRight :
Anchor.CenterLeft;
399 AbsoluteOffset = isHorizontal ?
400 new Point(0, IsScrollBarOnDefaultSide ? (
int)
Padding.W : (
int)
Padding.Y) :
401 new Point(IsScrollBarOnDefaultSide ? (
int)
Padding.Z : (
int)
Padding.X, 0)
403 isHorizontal: isHorizontal);
417 throw new InvalidOperationException($
"Children were added to {nameof(GUIListBox)}, Add them to {nameof(GUIListBox)}.{nameof(Content)} instead.");
422 dimensionsNeedsRecalculation =
false;
446 if (Equals(child.
UserData, userData))
448 Select(i, force, autoScroll);
455 private Point CalculateFrameSize(
bool isHorizontal,
int scrollBarSize)
456 => isHorizontal ?
new Point(
Rect.Width,
Rect.Height - scrollBarSize) : new Point(
Rect.Width - scrollBarSize,
Rect.Height);
474 return new Vector2(x, y);
477 private void CalculateChildrenOffsets(Action<int, Point> callback)
480 int x = (int)topOffset.X;
481 int y = (
int)topOffset.Y;
486 if (child ==
null || !child.
Visible) {
continue; }
489 callback(i,
new Point(x, y));
494 void advanceGridLayout(
495 ref
int primaryCoord,
496 ref
int secondaryCoord,
497 int primaryChildDimension,
498 int secondaryChildDimension,
499 int primaryParentDimension)
501 if (primaryCoord + primaryChildDimension +
Spacing > primaryParentDimension)
504 secondaryCoord += secondaryChildDimension +
Spacing;
505 callback(i,
new Point(x, y));
507 primaryCoord += primaryChildDimension +
Spacing;
514 secondaryCoord: ref x,
515 primaryChildDimension: child.
Rect.Height,
516 secondaryChildDimension: child.
Rect.Width,
523 secondaryCoord: ref y,
524 primaryChildDimension: child.
Rect.Width,
525 secondaryChildDimension: child.
Rect.Height,
543 private void RepositionChildren()
545 CalculateChildrenOffsets((index, offset) =>
548 if (child != draggedElement && child.RectTransform.AbsoluteOffset != offset)
550 child.RectTransform.AbsoluteOffset = offset;
566 int index = children.IndexOf(component);
567 if (index < 0) {
return; }
588 performScroll(component);
594 CoroutineManager.StartCoroutine(ScrollCoroutine());
596 IEnumerable<CoroutineStatus> ScrollCoroutine()
605 float speed = distanceToTravel / duration;
608 t += CoroutineManager.DeltaTime;
609 BarScroll += speed * CoroutineManager.DeltaTime;
617 private double lastDragStartTime;
622 if (Timing.TotalTime > lastDragStartTime + 0.2f)
624 lastDragStartTime = Timing.TotalTime;
629 private bool UpdateDragging()
632 if (!PlayerInput.PrimaryMouseButtonHeld())
634 var draggedElem = draggedElement;
641 RepositionChildren();
642 if (
AllSelected.Contains(draggedElem)) {
return true; }
647 var mousePos = PlayerInput.MousePosition.ToPoint();
653 isHorizontal ?
new Point(offset.X, 0) : new Point(0, offset.Y);
657 int newIndex = index;
659 Point draggedOffsetWhenReleased = Point.Zero;
660 CalculateChildrenOffsets((i, offset) =>
662 if (index != i) {
return; }
663 draggedOffsetWhenReleased = offset;
669 ref
int draggedRectWhenReleasedLocation,
670 int draggedRectWhenReleasedSize)
672 while (mousePos > (draggedRectWhenReleasedLocation + draggedRectWhenReleasedSize) && newIndex <
Content.
CountChildren-1)
675 draggedRectWhenReleasedLocation += draggedRectWhenReleasedSize;
677 while (mousePos < draggedRectWhenReleasedLocation && newIndex > 0)
680 draggedRectWhenReleasedLocation -= draggedRectWhenReleasedSize;
686 int draggedPos =
AllSelected.IndexOf(draggedElement);
687 if (newIndex < draggedPos)
689 newIndex = draggedPos;
703 ref draggedRectWhenReleased.X,
704 draggedRectWhenReleased.Width);
710 ref draggedRectWhenReleased.Y,
711 draggedRectWhenReleased.Height);
714 if (newIndex != index)
719 int indexOfDraggedElem =
AllSelected.IndexOf(draggedElement);
720 IEnumerable<GUIComponent> allSelected =
AllSelected;
721 if (newIndex > index) { allSelected = allSelected.Reverse(); }
722 foreach (var elem
in allSelected)
724 elem.RectTransform.RepositionChildInHierarchy(newIndex +
AllSelected.IndexOf(elem) - indexOfDraggedElem);
740 private void UpdateChildrenRect()
742 if (UpdateDragging()) {
return; }
748 child.CanBeFocused = !
selected.Contains(child);
749 if (!child.CanBeFocused)
760 if (component !=
null && !
selected.Contains(component))
773 if (!(child is {
Visible:
true })) {
continue; }
780 var mouseDown = useMouseDownToSelect ? PlayerInput.PrimaryMouseButtonDown() : PlayerInput.PrimaryMouseButtonClicked();
793 && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child)
795 StartDraggingElement(child);
829 if (!childVisible.ContainsKey(child)) { childVisible[child] = child.
Visible; }
830 if (childVisible[child] != child.
Visible)
832 childVisible[child] = child.
Visible;
833 childrenNeedsRecalculation =
true;
834 scrollBarNeedsRecalculation =
true;
839 if (childrenNeedsRecalculation)
842 childVisible.Clear();
846 GUI.AddToUpdateList(
this);
858 if (!child.Visible)
continue;
859 if (!IsChildInsideFrame(child))
861 if (lastVisible > 0)
break;
876 base.ForceLayoutRecalculation();
885 ClampChildMouseRects(child);
887 RepositionChildren();
888 childrenNeedsRecalculation =
false;
900 ClampChildMouseRects(grandChild);
904 protected override void Update(
float deltaTime)
908 UpdateChildrenRect();
909 RepositionChildren();
911 if (scrollBarNeedsRecalculation)
918 foreach (var (component, _) in childVisible)
921 float y = component.Rect.Y;
923 float height = component.Rect.Height;
926 float distance = (contentY - y) / height;
933 float distance = (y - centerY) / (centerY - height);
937 component.Color = component.HoverColor = ToolBox.GradientLerp(lerp, component.DefaultColor, Color.Transparent);
938 component.DisabledColor = ToolBox.GradientLerp(lerp, component.Style.DisabledColor, Color.Transparent);
939 component.HoverColor = ToolBox.GradientLerp(lerp, component.Style.HoverColor, Color.Transparent);
943 Color gradient = ToolBox.GradientLerp(lerp, child.DefaultColor, Color.Transparent);
944 child.Color = child.HoverColor = gradient;
947 block.TextColor = block.HoverTextColor = gradient;
953 if (scrollToElement !=
null)
957 scrollToElement =
null;
962 float speed = MathHelper.Clamp(Math.Abs(diff) * 0.1f, 5.0f, 100.0f);
965 speed = Math.Abs(diff);
966 scrollToElement =
null;
973 FindScrollableParentListBox(GUI.MouseOn) ==
this ||
1005 if (dimensionsNeedsRecalculation)
1014 if (target?.
Parent ==
null) {
return null; }
1015 return FindScrollableParentListBox(target.
Parent);
1061 if (child is
null) {
return; }
1062 if (!child.
Enabled) {
return; }
1064 bool wasSelected =
true;
1071 if (!wasSelected) {
return; }
1091 int sgn = Math.Sign(lastIndex - firstIndex);
1093 for (
int i = firstIndex + sgn; i != lastIndex; i += sgn)
1100 if (first != last) {
selected.Add(last); }
1144 GUI.KeyboardDispatcher.Subscriber =
this;
1156 public void Select(IEnumerable<GUIComponent> children)
1168 if (GUI.KeyboardDispatcher.Subscriber ==
this)
1170 GUI.KeyboardDispatcher.Subscriber =
null;
1177 scrollBarNeedsRecalculation =
false;
1178 if (
Content ==
null) {
return; }
1196 if (child == children.Last())
1210 if (child == children.Last())
1229 totalSize +=
Rect.Height - last.
Rect.Height;
1234 float minScrollBarSize = 20.0f;
1236 Math.Min(
Content.
Rect.Width / (
float)totalSize, 1.0f) :
1237 Math.Min(
Content.
Rect.Height / (
float)totalSize, 1.0f);
1251 if (child ==
null) {
return; }
1264 protected override void Draw(SpriteBatch spriteBatch)
1270 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
1274 spriteBatch.GraphicsDevice.ScissorRectangle =
Rectangle.Intersect(prevScissorRect,
Content.
Rect);
1275 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState:
GameMain.
ScissorTestEnable);
1278 int lastVisible = 0;
1283 if (!child.
Visible) {
continue; }
1285 if (!IsChildInsideFrame(child))
1287 if (lastVisible > 0) {
break; }
1291 child.
DrawManually(spriteBatch, alsoChildren:
true, recursive:
true);
1299 CalculateChildrenOffsets((index, point) =>
1301 if (draggedElementIndex == index)
1303 drawRect.Location =
Content.
Rect.Location + point;
1306 GUI.DrawRectangle(spriteBatch, drawRect, Color.White * 0.5f, thickness: 2f);
1312 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
1313 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState:
GameMain.
ScissorTestEnable);
1318 draggedElement.
DrawManually(spriteBatch, alsoChildren:
true, recursive:
true);
1329 if (child ==
null) {
return false; }
1347 GUI.KeyboardDispatcher.Subscriber =
null;
1370 GUI.KeyboardDispatcher.Subscriber =
null;
static CoroutineStatus Running
static CoroutineStatus Success
virtual void ForceLayoutRecalculation()
GUIComponent(string style, RectTransform rectT)
This is the new constructor.
bool ClampMouseRectToParent
GUIComponent GetChild(int index)
virtual ComponentState State
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
Rectangle ClampRect(Rectangle r)
virtual bool PlaySoundOnSelect
Action< GUIComponent > OnAddedToGUIUpdateList
virtual void ClearChildren()
int GetChildIndex(GUIComponent child)
virtual 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 ...
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
RectTransform RectTransform
IEnumerable< GUIComponent > Children
override void RemoveChild(GUIComponent child)
void Select(IEnumerable< GUIComponent > children)
void RecalculateChildren()
OnRearrangedHandler OnRearranged
List< GUIComponent > selected
override void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
GUIComponent DraggedElement
bool ClampScrollToElements
Whether to only allow scrolling from one element to the next when smooth scrolling is enabled
void SelectNext(Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus=TakeKeyBoardFocus.No, PlaySelectSound playSelectSound=PlaySelectSound.No)
bool CanTakeKeyBoardFocus
void ReceiveTextInput(string text)
override bool PlaySoundOnSelect
Vector2 CalculateTopOffset()
bool ResizeContentToMakeSpaceForScrollBar
void ScrollToEnd(float duration)
void ReceiveTextInput(char inputChar)
bool ScrollBarEnabled
Disables the scroll bar without hiding it.
bool SmoothScroll
Scrolls the list smoothly
override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
Draws all the children manually.
CheckSelectedHandler CheckSelected
GUIFrame ContentBackground
A frame drawn behind the content of the listbox
bool HideChildrenOutsideFrame
override void Draw(SpriteBatch spriteBatch)
GUIComponent SelectedComponent
void ReceiveCommandInput(char command)
override void Update(float deltaTime)
bool AllowMouseWheelScroll
bool KeepSpaceForScrollBar
bool HasDraggedElementIndexChanged
OnSelectedHandler OnSelected
delegate bool OnSelectedHandler(GUIComponent component, object obj)
bool CanInteractWhenUnfocusable
Setting this to true and CanBeFocused to false allows the list background to be unfocusable while the...
bool PadBottom
Adds enough extra padding to the bottom so the end of the scroll will only contain the last element
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
GUISoundType? SoundOnDragStop
IReadOnlyList< GUIComponent > AllSelected
void ScrollToElement(GUIComponent component, PlaySelectSound playSelectSound=PlaySelectSound.No)
Scrolls the list to the specific element.
void Select(object userData, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled)
void SelectPrevious(Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus=TakeKeyBoardFocus.No, PlaySelectSound playSelectSound=PlaySelectSound.No)
void ReceiveEditingInput(string text, int start, int length)
SelectMode CurrentSelectMode
void Select(int childIndex, Force force=Force.No, AutoScroll autoScroll=AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus=TakeKeyBoardFocus.No, PlaySelectSound playSelectSound=PlaySelectSound.No)
GUISoundType? SoundOnDragStart
bool AutoHideScrollBar
Automatically hides the scroll bar when the content fits in.
void UpdateScrollBarSize()
bool FadeElements
When set to true elements at the bottom of the list are gradually faded
bool SelectTop
When set to true always selects the topmost item on the list
override void ForceLayoutRecalculation()
GUIListBox(RectTransform rectT, bool isHorizontal=false, Color? color=null, string style="", bool isScrollBarOnDefaultSide=true, bool useMouseDownToSelect=false)
For horizontal listbox, default side is on the bottom. For vertical, it's on the right.
delegate object CheckSelectedHandler()
override Rectangle? MouseRect
delegate void OnRearrangedHandler(GUIListBox listBox, object obj)
override void ClearChildren()
void ReceiveSpecialInput(Keys key)
static RasterizerState ScissorTestEnable