38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -0,0 +1,357 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class ChatBox
{
private static Sprite radioIcon;
private GUIFrame guiFrame;
private GUIListBox chatBox;
private GUITextBox inputBox;
private GUIButton toggleButton;
private GUIButton radioButton;
private Point screenResolution;
private bool isSinglePlayer;
public bool IsSinglePlayer => isSinglePlayer;
private bool toggleOpen = true;
private float openState;
private float prevUIScale;
//individual message texts that pop up when the chatbox is hidden
const float PopupMessageDuration = 5.0f;
private float popupMessageTimer;
private Queue<GUIComponent> popupMessages = new Queue<GUIComponent>();
public GUITextBox.OnEnterHandler OnEnterMessage
{
get { return inputBox.OnEnterPressed; }
set { inputBox.OnEnterPressed = value; }
}
public GUIFrame GUIFrame
{
get { return guiFrame; }
}
public GUIButton RadioButton
{
get { return radioButton; }
}
public GUITextBox InputBox
{
get { return inputBox; }
}
public GUIButton ToggleButton
{
get { return toggleButton; }
}
public ChatBox(GUIComponent parent, bool isSinglePlayer)
{
this.isSinglePlayer = isSinglePlayer;
if (radioIcon == null)
{
radioIcon = new Sprite("Content/UI/inventoryAtlas.png", new Rectangle(527, 952, 38, 52), null);
radioIcon.Origin = radioIcon.size / 2;
}
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
int toggleButtonWidth = (int)(30 * GUI.Scale);
guiFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);
chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.9f), guiFrame.RectTransform), style: "ChatBox");
toggleButton = new GUIButton(new RectTransform(new Point(toggleButtonWidth, HUDLayoutSettings.ChatBoxArea.Height), parent.RectTransform),
style: "UIToggleButton");
toggleButton.OnClicked += (GUIButton btn, object userdata) =>
{
toggleOpen = !toggleOpen;
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = toggleOpen == (HUDLayoutSettings.ChatBoxAlignment == Alignment.Right) ?
SpriteEffects.FlipHorizontally : SpriteEffects.None;
}
return true;
};
inputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.1f), guiFrame.RectTransform, Anchor.BottomCenter),
style: "ChatTextBox")
{
Font = GUI.SmallFont,
MaxTextLength = ChatMessage.MaxLength
};
radioButton = new GUIButton(new RectTransform(new Vector2(0.1f, 2.0f), inputBox.RectTransform,
HUDLayoutSettings.ChatBoxAlignment == Alignment.Right ? Anchor.BottomRight : Anchor.BottomLeft,
HUDLayoutSettings.ChatBoxAlignment == Alignment.Right ? Pivot.TopRight : Pivot.TopLeft),
style: null);
new GUIImage(new RectTransform(Vector2.One, radioButton.RectTransform), radioIcon, scaleToFit: true);
radioButton.OnClicked = (GUIButton btn, object userData) =>
{
if (inputBox.Selected)
{
inputBox.Text = "";
inputBox.Deselect();
}
else
{
inputBox.Select();
var radioItem = Character.Controlled?.Inventory?.Items.FirstOrDefault(i => i?.GetComponent<WifiComponent>() != null);
if (radioItem != null && Character.Controlled.HasEquippedItem(radioItem) && radioItem.GetComponent<WifiComponent>().CanTransmit())
{
inputBox.Text = "r; ";
}
}
return true;
};
}
public bool TypingChatMessage(GUITextBox textBox, string text)
{
string command = ChatMessage.GetChatMessageCommand(text, out _);
if (IsSinglePlayer)
{
//radio is the only allowed special message type in single player
if (command != "r" && command != "radio")
{
command = "";
}
}
switch (command)
{
case "r":
case "radio":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
break;
case "d":
case "dead":
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
break;
default:
if (Character.Controlled != null && (Character.Controlled.IsDead || Character.Controlled.SpeechImpediment >= 100.0f))
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
}
else if (command != "") //PMing
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
}
else
{
textBox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
}
break;
}
return true;
}
public void AddMessage(ChatMessage message)
{
while (chatBox.Content.CountChildren > 20)
{
chatBox.RemoveChild(chatBox.Content.Children.First());
}
float prevSize = chatBox.BarSize;
string displayedText = message.Text;
string senderName = "";
if (!string.IsNullOrWhiteSpace(message.SenderName))
{
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
}
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);
GUITextBlock senderNameBlock = null;
if (!string.IsNullOrEmpty(senderName))
{
senderNameBlock = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform)
{ AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
senderName, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{
CanBeFocused = true
};
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameBlock == null ? 0 : senderNameBlock.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
{
UserData = message.SenderName,
CanBeFocused = true
};
if (message is OrderChatMessage orderChatMsg &&
Character.Controlled != null &&
orderChatMsg.TargetCharacter == Character.Controlled)
{
msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f);
}
else
{
msgHolder.Flash(Color.Yellow * 0.6f);
}
//resize the holder to match the size of the message and add some spacing
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));
chatBox.UpdateScrollBarSize();
if (!toggleOpen)
{
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, guiFrame.RectTransform), style: "GUIToolTip")
{
Visible = false
};
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), popupMsg.RectTransform, Anchor.TopRight),
senderName, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopRight)
{
CanBeFocused = false
};
var msgText = new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.0f), popupMsg.RectTransform, Anchor.TopRight)
{ AbsoluteOffset = new Point(0, senderText.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopRight, style: null, wrap: true)
{
CanBeFocused = false
};
int textWidth = (int)Math.Max(
msgText.Font.MeasureString(msgText.WrappedText).X,
senderText.Font.MeasureString(senderText.WrappedText).X);
popupMsg.RectTransform.Resize(new Point(textWidth + 20, msgText.Rect.Bottom - senderText.Rect.Y), resizeChildren: false);
popupMessages.Enqueue(popupMsg);
}
if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f;
GUISoundType soundType = GUISoundType.Message;
if (message.Type == ChatMessageType.Radio)
{
soundType = GUISoundType.RadioMessage;
}
else if (message.Type == ChatMessageType.Dead)
{
soundType = GUISoundType.DeadMessage;
}
GUI.PlayUISound(soundType);
}
private IEnumerable<object> UpdateMessageAnimation(GUIComponent message, float animDuration)
{
float timer = 0.0f;
while (timer < animDuration)
{
timer += CoroutineManager.DeltaTime;
float wavePhase = timer / animDuration * MathHelper.TwoPi;
message.RectTransform.ScreenSpaceOffset =
new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0);
yield return CoroutineStatus.Running;
}
message.RectTransform.ScreenSpaceOffset = Point.Zero;
yield return CoroutineStatus.Success;
}
private void SetUILayout()
{
guiFrame.RectTransform.AbsoluteOffset = Point.Zero;
guiFrame.RectTransform.RelativeOffset = new Vector2(
HUDLayoutSettings.ChatBoxArea.X / (float)GameMain.GraphicsWidth,
HUDLayoutSettings.ChatBoxArea.Y / (float)GameMain.GraphicsHeight);
guiFrame.RectTransform.NonScaledSize = HUDLayoutSettings.ChatBoxArea.Size;
int toggleButtonWidth = (int)(30 * GUI.Scale);
//make room for the toggle button
if (HUDLayoutSettings.ChatBoxAlignment == Alignment.Left)
{
guiFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
}
guiFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
toggleButton.RectTransform.NonScaledSize = new Point(toggleButtonWidth, HUDLayoutSettings.ChatBoxArea.Height);
toggleButton.RectTransform.AbsoluteOffset = HUDLayoutSettings.ChatBoxAlignment == Alignment.Left ?
new Point(HUDLayoutSettings.ChatBoxArea.X, HUDLayoutSettings.ChatBoxArea.Y) :
new Point(HUDLayoutSettings.ChatBoxArea.Right - toggleButtonWidth, HUDLayoutSettings.ChatBoxArea.Y);
}
public void Update(float deltaTime)
{
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
{
SetUILayout();
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = GUI.Scale;
}
if (toggleOpen || (inputBox != null && inputBox.Selected))
{
openState += deltaTime * 5.0f;
//delete all popup messages when the chatbox is open
while (popupMessages.Count > 0)
{
var popupMsg = popupMessages.Dequeue();
popupMsg.Parent.RemoveChild(popupMsg);
}
}
else
{
openState -= deltaTime * 5.0f;
//make the first popup message visible
var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
if (popupMsg != null)
{
popupMsg.Visible = true;
//popup messages appear and disappear faster when there's more pending messages
popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count;
if (popupMessageTimer > PopupMessageDuration)
{
//move the message out of the screen and delete it
popupMsg.RectTransform.ScreenSpaceOffset =
new Point((int)MathHelper.SmoothStep(-popupMsg.Rect.Width - toggleButton.Rect.Width * 2, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0);
if (popupMessageTimer > PopupMessageDuration + 1.0f)
{
popupMessageTimer = 0.0f;
popupMsg.Parent.RemoveChild(popupMsg);
popupMessages.Dequeue();
}
}
else
{
//move the message on the screen
popupMsg.RectTransform.ScreenSpaceOffset = new Point(
(int)MathHelper.SmoothStep(0, -popupMsg.Rect.Width - toggleButton.Rect.Width * 2 - (int)(35 * GUI.Scale), popupMessageTimer * 5.0f), 0);
}
}
}
openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
int hiddenBoxOffset = guiFrame.Rect.Width + toggleButton.Rect.Width;
if (radioButton != null) hiddenBoxOffset += (int)(radioButton.Rect.Width * 1.5f);
guiFrame.RectTransform.AbsoluteOffset =
new Point((int)MathHelper.SmoothStep(hiddenBoxOffset * (HUDLayoutSettings.ChatBoxAlignment == Alignment.Left ? -1 : 1), 0, openState), 0);
}
}
}
@@ -5,46 +5,6 @@ using System.Xml.Linq;
namespace Barotrauma
{
public class UISprite
{
public Sprite Sprite
{
get;
private set;
}
public bool Tile
{
get;
private set;
}
public bool Slice
{
get;
set;
}
public Rectangle[] Slices
{
get;
set;
}
public bool MaintainAspectRatio
{
get;
private set;
}
public UISprite(Sprite sprite, bool tile, bool maintainAspectRatio)
{
Sprite = sprite;
Tile = tile;
MaintainAspectRatio = maintainAspectRatio;
}
}
public class GUIComponentStyle
{
public readonly Vector4 Padding;
@@ -55,6 +15,7 @@ namespace Barotrauma
public readonly Color HoverColor;
public readonly Color SelectedColor;
public readonly Color PressedColor;
public readonly Color OutlineColor;
@@ -74,68 +35,34 @@ namespace Barotrauma
Padding = element.GetAttributeVector4("padding", Vector4.Zero);
Vector4 colorVector = element.GetAttributeVector4("color", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
Color = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
Color = element.GetAttributeColor("color", Color.Transparent);
textColor = element.GetAttributeColor("textcolor", Color.Black);
HoverColor = element.GetAttributeColor("hovercolor", Color.Transparent);
SelectedColor = element.GetAttributeColor("selectedcolor", Color.Transparent);
PressedColor = element.GetAttributeColor("pressedcolor", Color.Transparent);
OutlineColor = element.GetAttributeColor("outlinecolor", Color.Transparent);
colorVector = element.GetAttributeVector4("textcolor", new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
textColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
colorVector = element.GetAttributeVector4("hovercolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
HoverColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
colorVector = element.GetAttributeVector4("selectedcolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
SelectedColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
colorVector = element.GetAttributeVector4("outlinecolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
OutlineColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
Sprite sprite = new Sprite(subElement);
bool maintainAspect = subElement.GetAttributeBool("maintainaspectratio",false);
bool tile = subElement.GetAttributeBool("tile", true);
UISprite newSprite = new UISprite(subElement);
string stateStr = subElement.GetAttributeString("state", "None");
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
Enum.TryParse(stateStr, out spriteState);
UISprite newSprite = new UISprite(sprite, tile, maintainAspect);
Vector4 sliceVec = subElement.GetAttributeVector4("slice", Vector4.Zero);
if (sliceVec != Vector4.Zero)
if (subElement.Attribute("state") != null)
{
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
newSprite.Slice = true;
newSprite.Slices = new Rectangle[9];
//top-left
newSprite.Slices[0] = new Rectangle(newSprite.Sprite.SourceRect.Location, slice.Location - newSprite.Sprite.SourceRect.Location);
//top-mid
newSprite.Slices[1] = new Rectangle(slice.Location.X, newSprite.Slices[0].Y, slice.Width, newSprite.Slices[0].Height);
//top-right
newSprite.Slices[2] = new Rectangle(slice.Right, newSprite.Slices[0].Y, newSprite.Sprite.SourceRect.Right - slice.Right, newSprite.Slices[0].Height);
//mid-left
newSprite.Slices[3] = new Rectangle(newSprite.Slices[0].X, slice.Y, newSprite.Slices[0].Width, slice.Height);
//center
newSprite.Slices[4] = slice;
//mid-right
newSprite.Slices[5] = new Rectangle(newSprite.Slices[2].X, slice.Y, newSprite.Slices[2].Width, slice.Height);
//bottom-left
newSprite.Slices[6] = new Rectangle(newSprite.Slices[0].X, slice.Bottom, newSprite.Slices[0].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-mid
newSprite.Slices[7] = new Rectangle(newSprite.Slices[1].X, slice.Bottom, newSprite.Slices[1].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-right
newSprite.Slices[8] = new Rectangle(newSprite.Slices[2].X, slice.Bottom, newSprite.Slices[2].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
string stateStr = subElement.GetAttributeString("state", "None");
Enum.TryParse(stateStr, out spriteState);
Sprites[spriteState].Add(newSprite);
}
else
{
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
{
Sprites[state].Add(newSprite);
}
}
Sprites[spriteState].Add(newSprite);
break;
default:
ChildStyles.Add(subElement.Name.ToString().ToLowerInvariant(), new GUIComponentStyle(subElement));
File diff suppressed because it is too large Load Diff
@@ -6,7 +6,9 @@ namespace Barotrauma
public class GUIButton : GUIComponent
{
protected GUITextBlock textBlock;
public GUITextBlock TextBlock { get { return textBlock; } }
protected GUIFrame frame;
public GUIFrame Frame { get { return frame; } }
public delegate bool OnClickedHandler(GUIButton button, object obj);
public OnClickedHandler OnClicked;
@@ -14,11 +16,12 @@ namespace Barotrauma
public delegate bool OnPressedHandler();
public OnPressedHandler OnPressed;
public delegate bool OnButtonDownHandler();
public OnButtonDownHandler OnButtonDown;
public bool CanBeSelected = true;
private bool enabled;
public bool Enabled
public override bool Enabled
{
get
{
@@ -28,7 +31,6 @@ namespace Barotrauma
set
{
if (value == enabled) return;
enabled = value;
frame.Color = enabled ? color : Color.Gray * 0.7f;
}
@@ -67,6 +69,19 @@ namespace Barotrauma
}
}
public override Color PressedColor
{
get
{
return base.PressedColor;
}
set
{
base.PressedColor = value;
frame.PressedColor = value;
}
}
public override Color OutlineColor
{
get { return base.OutlineColor; }
@@ -114,62 +129,22 @@ namespace Barotrauma
base.ToolTip = value;
}
}
public override Rectangle Rect
{
get
{
return rect;
}
set
{
base.Rect = value;
frame.Rect = new Rectangle(value.X, value.Y, frame.Rect.Width, frame.Rect.Height);
textBlock.Rect = value;
}
}
public bool Selected { get; set; }
public GUIButton(Rectangle rect, string text, string style, GUIComponent parent = null)
: this(rect, text, null, Alignment.Left, style, parent)
public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
{
}
public GUIButton(Rectangle rect, string text, Alignment alignment, string style, GUIComponent parent = null)
: this(rect, text, null, alignment, style, parent)
{
}
public GUIButton(Rectangle rect, string text, Color? color, string style, GUIComponent parent = null)
: this(rect, text, color, (Alignment.Left | Alignment.Top), style, parent)
{
}
public GUIButton(Rectangle rect, string text, Color? color, Alignment alignment, string style = "", GUIComponent parent = null)
: this(rect, text, color, alignment, Alignment.Center, style, parent)
{
}
public GUIButton(Rectangle rect, string text, Color? color, Alignment alignment, Alignment textAlignment, string style = "", GUIComponent parent = null)
: base(style)
{
this.rect = rect;
if (color != null) this.color = (Color)color;
this.alignment = alignment;
if (parent != null) parent.AddChild(this);
frame = new GUIFrame(Rectangle.Empty, style, this);
GUI.Style.Apply(frame, style == "" ? "GUIButton" : style);
textBlock = new GUITextBlock(Rectangle.Empty, text,
Color.Transparent, (this.style == null) ? Color.Black : this.style.textColor,
textAlignment, null, this);
GUI.Style.Apply(textBlock, style, this);
if (color.HasValue)
{
this.color = color.Value;
}
frame = new GUIFrame(new RectTransform(Vector2.One, rectT), style);
if (style != null) GUI.Style.Apply(frame, style == "" ? "GUIButton" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT), text, textAlignment: textAlignment, style: null)
{
TextColor = this.style == null ? Color.Black : this.style.textColor
};
GUI.Style.Apply(textBlock, "", this);
Enabled = true;
}
@@ -178,28 +153,36 @@ namespace Barotrauma
base.ApplyStyle(style);
if (frame != null) frame.ApplyStyle(style);
if (textBlock != null) textBlock.ApplyStyle(style);
}
public override void Draw(SpriteBatch spriteBatch)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
DrawChildren(spriteBatch);
//do nothing
}
public override void Update(float deltaTime)
protected override void Update(float deltaTime)
{
if (!Visible) return;
base.Update(deltaTime);
if (rect.Contains(PlayerInput.MousePosition) && CanBeSelected && Enabled && (MouseOn == null || MouseOn == this || IsParentOf(MouseOn)))
if (Rect.Contains(PlayerInput.MousePosition) && CanBeSelected && Enabled && GUI.IsMouseOn(this))
{
state = ComponentState.Hover;
if (PlayerInput.LeftButtonDown())
{
OnButtonDown?.Invoke();
}
if (PlayerInput.LeftButtonHeld())
{
if (OnPressed != null)
{
if (OnPressed()) state = ComponentState.Pressed;
if (OnPressed())
{
state = ComponentState.Pressed;
}
}
else
{
state = ComponentState.Pressed;
}
}
else if (PlayerInput.LeftButtonClicked())
@@ -207,7 +190,10 @@ namespace Barotrauma
GUI.PlayUISound(GUISoundType.Click);
if (OnClicked != null)
{
if (OnClicked(this, UserData) && CanBeSelected) state = ComponentState.Selected;
if (OnClicked(this, UserData) && CanBeSelected)
{
state = ComponentState.Selected;
}
}
else
{
@@ -220,7 +206,12 @@ namespace Barotrauma
{
state = Selected ? ComponentState.Selected : ComponentState.None;
}
frame.State = state;
foreach (GUIComponent child in Children)
{
child.State = state;
}
//frame.State = state;
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
namespace Barotrauma
{
public class GUICanvas : RectTransform
{
protected GUICanvas() : base(Vector2.One, parent: null) { }
private static GUICanvas _instance;
public static GUICanvas Instance
{
get
{
if (_instance == null)
{
_instance = new GUICanvas();
if (GameMain.Instance != null)
{
GameMain.Instance.OnResolutionChanged += RecalculateSize;
}
}
return _instance;
}
}
// Turn public, if there is a need to call this manually.
private static void RecalculateSize()
{
Instance.Resize(Vector2.One, resizeChildren: true);
Instance.GetAllChildren().Select(c => c.GUIComponent as GUITextBlock).ForEach(t => t?.SetTextPos());
}
}
}
@@ -1,96 +1,134 @@
using EventInput;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using System;
namespace Barotrauma
{
public abstract class GUIComponent
{
const float FlashDuration = 1.5f;
#region Hierarchy
public GUIComponent Parent => RectTransform.Parent?.GUIComponent;
public static GUIComponent MouseOn
public IEnumerable<GUIComponent> Children => RectTransform.Children.Select(c => c.GUIComponent);
public T GetChild<T>() where T : GUIComponent
{
get;
private set;
}
public static void ForceMouseOn(GUIComponent c)
{
MouseOn = c;
return Children.FirstOrDefault(c => c is T) as T;
}
protected static List<GUIComponent> ComponentsToUpdate = new List<GUIComponent>();
public virtual void AddToGUIUpdateList()
public T GetAnyChild<T>() where T : GUIComponent
{
if (!Visible) return;
if (ComponentsToUpdate.Contains(this)) return;
ComponentsToUpdate.Add(this);
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
foreach (GUIComponent c in fixedChildren)
return GetAllChildren().FirstOrDefault(c => c is T) as T;
}
public GUIComponent GetChild(int index)
{
if (index < 0 || index >= CountChildren) return null;
return RectTransform.GetChild(index).GUIComponent;
}
public int GetChildIndex(GUIComponent child)
{
if (child == null) return -1;
return RectTransform.GetChildIndex(child.RectTransform);
}
public GUIComponent GetChildByUserData(object obj)
{
foreach (GUIComponent child in Children)
{
c.AddToGUIUpdateList();
if (child.UserData == obj || (child.userData != null && child.userData.Equals(obj))) return child;
}
return null;
}
public static void ClearUpdateList()
/// <summary>
/// Returns all child elements in the hierarchy.
/// If the component has RectTransform, it's more efficient to use RectTransform.GetChildren and access the GUIComponent property directly.
/// </summary>
public IEnumerable<GUIComponent> GetAllChildren()
{
if (keyboardDispatcher != null &&
KeyboardDispatcher.Subscriber is GUIComponent &&
!ComponentsToUpdate.Contains((GUIComponent)KeyboardDispatcher.Subscriber))
{
KeyboardDispatcher.Subscriber = null;
}
ComponentsToUpdate.Clear();
return RectTransform.GetAllChildren().Select(c => c.GUIComponent);
}
public static GUIComponent UpdateMouseOn()
public bool IsParentOf(GUIComponent component, bool recursive = true)
{
MouseOn = null;
for (int i = ComponentsToUpdate.Count - 1; i >= 0; i--)
if (component == null) { return false; }
return RectTransform.IsParentOf(component.RectTransform, recursive);
}
public virtual void RemoveChild(GUIComponent child)
{
if (child == null) return;
child.RectTransform.Parent = null;
}
// TODO: refactor?
public GUIComponent FindChild(object userData, bool recursive = false)
{
var matchingChild = Children.FirstOrDefault(c => c.userData == userData);
if (recursive && matchingChild == null)
{
GUIComponent c = ComponentsToUpdate[i];
if (c.MouseRect.Contains(PlayerInput.MousePosition))
foreach (GUIComponent child in Children)
{
MouseOn = c;
break;
matchingChild = child.FindChild(userData, recursive);
if (matchingChild != null) return matchingChild;
}
}
return MouseOn;
return matchingChild;
}
protected static KeyboardDispatcher keyboardDispatcher;
public IEnumerable<GUIComponent> FindChildren(object userData)
{
return Children.Where(c => c.userData == userData);
}
public virtual void ClearChildren()
{
RectTransform.ClearChildren();
}
public void SetAsLastChild()
{
RectTransform.SetAsLastChild();
}
#endregion
public bool AutoUpdate { get; set; } = true;
public bool AutoDraw { get; set; } = true;
public int UpdateOrder { get; set; }
public Action<GUIComponent> OnAddedToGUIUpdateList;
/// <summary>
/// Launched at the beginning of the Draw method. Note: if the method is overridden, the event might not be called!
public enum ComponentState { None, Hover, Pressed, Selected };
protected Alignment alignment;
protected GUIComponentStyle style;
protected object userData;
protected Rectangle rect;
public bool CanBeFocused;
protected Vector4 padding;
protected Color color;
protected Color hoverColor;
protected Color selectedColor;
protected GUIComponent parent;
public List<GUIComponent> children;
protected Color pressedColor;
protected ComponentState state;
protected Color flashColor;
protected float flashDuration = 1.5f;
protected float flashTimer;
public bool IgnoreLayoutGroups;
public virtual ScalableFont Font
{
get;
@@ -114,26 +152,26 @@ namespace Barotrauma
set;
}
protected bool enabled;
public virtual bool Enabled
{
get { return enabled; }
set { enabled = value; }
}
public bool TileSprites;
private static GUITextBlock toolTipBlock;
//protected float alpha;
public GUIComponent Parent
{
get { return parent; }
}
public Vector2 Center
{
get { return new Vector2(rect.Center.X, rect.Center.Y); }
get { return new Vector2(Rect.Center.X, Rect.Center.Y); }
}
protected Rectangle ClampRect(Rectangle r)
{
if (parent == null || !ClampMouseRectToParent) return r;
Rectangle parentRect = parent.ClampRect(parent.rect);
if (Parent == null || !ClampMouseRectToParent) return r;
Rectangle parentRect = Parent.ClampRect(Parent.Rect);
if (parentRect.Width <= 0 || parentRect.Height <= 0) return Rectangle.Empty;
if (parentRect.X > r.X)
{
@@ -163,46 +201,23 @@ namespace Barotrauma
public virtual Rectangle Rect
{
get { return rect; }
set
{
int prevX = rect.X, prevY = rect.Y;
int prevWidth = rect.Width, prevHeight = rect.Height;
rect = value;
if (prevX == rect.X && prevY == rect.Y && rect.Width == prevWidth && rect.Height == prevHeight) return;
//TODO: fix this (or replace with something better in the new GUI system)
//simply expanding the rects by the same amount as their parent only works correctly in some special cases
foreach (GUIComponent child in children)
{
child.Rect = new Rectangle(
child.rect.X + (rect.X - prevX),
child.rect.Y + (rect.Y - prevY),
Math.Max(child.rect.Width + (rect.Width - prevWidth),0),
Math.Max(child.rect.Height + (rect.Height - prevHeight),0));
}
if (parent != null && parent is GUIListBox)
{
((GUIListBox)parent).UpdateScrollBarSize();
}
}
get { return RectTransform.Rect; }
}
public bool ClampMouseRectToParent = true;
public bool ClampMouseRectToParent { get; set; } = false;
public virtual Rectangle MouseRect
{
get
{
if (!CanBeFocused) return Rectangle.Empty;
return ClampMouseRectToParent ? ClampRect(rect) : rect;
return ClampMouseRectToParent ? ClampRect(Rect) : Rect;
}
}
public Dictionary<ComponentState, List<UISprite>> sprites;
public SpriteEffects SpriteEffects;
public virtual Color OutlineColor { get; set; }
public ComponentState State
@@ -216,16 +231,10 @@ namespace Barotrauma
get { return userData; }
set { userData = value; }
}
public virtual Vector4 Padding
{
get { return padding; }
set { padding = value; }
}
public int CountChildren
{
get { return children.Count; }
get { return RectTransform.CountChildren; }
}
public virtual Color Color
@@ -246,9 +255,33 @@ namespace Barotrauma
set { selectedColor = value; }
}
public static KeyboardDispatcher KeyboardDispatcher
public virtual Color PressedColor
{
get { return keyboardDispatcher; }
get { return pressedColor; }
set { pressedColor = value; }
}
private RectTransform rectTransform;
public RectTransform RectTransform
{
get { return rectTransform; }
private set
{
rectTransform = value;
// This is the only place where the element should be assigned!
if (rectTransform != null)
{
rectTransform.GUIComponent = this;
}
}
}
/// <summary>
/// This is the new constructor.
/// </summary>
protected GUIComponent(string style, RectTransform rectT) : this(style)
{
RectTransform = rectT;
}
protected GUIComponent(string style)
@@ -260,8 +293,6 @@ namespace Barotrauma
OutlineColor = Color.Transparent;
Font = GUI.Font;
children = new List<GUIComponent>();
CanBeFocused = true;
@@ -269,50 +300,198 @@ namespace Barotrauma
GUI.Style.Apply(this, style);
}
public static void Init(GameWindow window)
#region Updating
public virtual void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
keyboardDispatcher = new KeyboardDispatcher(window);
if (!Visible) return;
UpdateOrder = order;
GUI.AddToUpdateList(this);
if (!ignoreChildren)
{
RectTransform.AddChildrenToGUIUpdateList(ignoreChildren, order);
}
OnAddedToGUIUpdateList?.Invoke(this);
}
public T GetChild<T>() where T : GUIComponent
public void RemoveFromGUIUpdateList(bool alsoChildren = true)
{
foreach (GUIComponent child in children)
GUI.RemoveFromUpdateList(this, alsoChildren);
}
/// <summary>
/// Only GUI should call this method. Auto updating follows the order of GUI update list. This order can be tweaked by changing the UpdateOrder property.
/// </summary>
public void UpdateAuto(float deltaTime)
{
if (AutoUpdate)
{
if (child is T) return (T)(object)child;
Update(deltaTime);
}
}
/// <summary>
/// By default, all the gui elements are updated automatically in the same order they appear on the update list.
/// </summary>
public void UpdateManually(float deltaTime, bool alsoChildren = false, bool recursive = true)
{
if (!Visible) return;
AutoUpdate = false;
Update(deltaTime);
if (alsoChildren)
{
UpdateChildren(deltaTime, recursive);
}
}
protected virtual void Update(float deltaTime)
{
if (!Visible) return;
if (flashTimer > 0.0f)
{
flashTimer -= deltaTime;
}
}
/// <summary>
/// Updates all the children manually.
/// </summary>
public void UpdateChildren(float deltaTime, bool recursive)
{
RectTransform.Children.ForEach(c => c.GUIComponent.UpdateManually(deltaTime, recursive, recursive));
}
#endregion
#region Drawing
/// <summary>
/// Only GUI should call this method. Auto drawing follows the order of GUI update list. This order can be tweaked by changing the UpdateOrder property.
/// </summary>
public void DrawAuto(SpriteBatch spriteBatch)
{
if (AutoDraw)
{
Draw(spriteBatch);
}
}
/// <summary>
/// By default, all the gui elements are drawn automatically in the same order they appear on the update list.
/// </summary>
public virtual void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true)
{
if (!Visible) return;
AutoDraw = false;
Draw(spriteBatch);
if (alsoChildren)
{
DrawChildren(spriteBatch, recursive);
}
}
/// <summary>
/// Draws all the children manually.
/// </summary>
public virtual void DrawChildren(SpriteBatch spriteBatch, bool recursive)
{
RectTransform.Children.ForEach(c => c.GUIComponent.DrawManually(spriteBatch, recursive, recursive));
}
protected virtual Color GetCurrentColor(ComponentState state)
{
switch (state)
{
case ComponentState.Hover:
return HoverColor;
case ComponentState.Pressed:
return PressedColor;
case ComponentState.Selected:
return SelectedColor;
default:
return Color;
}
}
protected virtual void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
var rect = Rect;
Color currColor = GetCurrentColor(state);
if (currColor.A > 0.0f && (sprites == null || !sprites.Any())) GUI.DrawRectangle(spriteBatch, rect, currColor * (currColor.A / 255.0f), true);
if (sprites != null && sprites[state] != null && currColor.A > 0.0f)
{
foreach (UISprite uiSprite in sprites[state])
{
uiSprite.Draw(spriteBatch, rect, currColor * (currColor.A / 255.0f), SpriteEffects);
}
}
return default(T);
}
public GUIComponent GetChild(object obj)
{
foreach (GUIComponent child in children)
if (flashTimer > 0.0f)
{
if (child.UserData == obj) return child;
//the number of flashes depends on the duration, 1 flash per 1 full second
int flashCycleCount = (int)Math.Max(flashDuration, 1);
float flashCycleDuration = flashDuration / flashCycleCount;
//MathHelper.Pi * 0.8f -> the curve goes from 144 deg to 0,
//i.e. quickly bumps up from almost full brightness to full and then fades out
GUI.UIGlow.Draw(spriteBatch,
rect,
flashColor * (float)Math.Sin(flashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f));
}
return null;
}
public bool IsParentOf(GUIComponent component)
/// <summary>
/// Creates and draws a tooltip.
/// </summary>
public void DrawToolTip(SpriteBatch spriteBatch)
{
for(int i = children.Count - 1; i >= 0; i--)
if (!Visible) return;
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect);
}
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement)
{
int width = 400;
if (toolTipBlock == null || (string)toolTipBlock.userData != toolTip)
{
if (children[i] == component) return true;
if (children[i].IsParentOf(component)) return true;
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, 18), null), toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip");
toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + 20),
toolTipBlock.WrappedText.Split('\n').Length * 18 + 7);
toolTipBlock.userData = toolTip;
}
return false;
toolTipBlock.RectTransform.AbsoluteOffset = new Point(targetElement.Center.X, targetElement.Bottom);
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
}
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(
(targetElement.Width / 2) * Math.Sign(targetElement.Center.X - toolTipBlock.Center.X),
toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10));
}
toolTipBlock.SetTextPos();
toolTipBlock.DrawManually(spriteBatch);
}
#endregion
protected virtual void SetAlpha(float a)
{
color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a);
}
public virtual void Flash(Color? color = null)
public virtual void Flash(Color? color = null, float flashDuration = 1.5f)
{
flashTimer = FlashDuration;
flashColor = (color == null) ? Color.Red * 0.8f : (Color)color;
flashTimer = flashDuration;
this.flashDuration = flashDuration;
flashColor = (color == null) ? Color.Red : (Color)color;
}
public void FadeOut(float duration, bool removeAfter)
@@ -336,206 +515,14 @@ namespace Barotrauma
SetAlpha(to);
if (removeAfter && parent != null)
if (removeAfter && Parent != null)
{
parent.RemoveChild(this);
Parent.RemoveChild(this);
}
yield return CoroutineStatus.Success;
}
public virtual void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Color currColor = color;
if (state == ComponentState.Selected) currColor = selectedColor;
if (state == ComponentState.Hover) currColor = hoverColor;
if (flashTimer > 0.0f)
{
GUI.DrawRectangle(spriteBatch,
new Rectangle(rect.X - 5, rect.Y - 5, rect.Width + 10, rect.Height + 10),
flashColor * (flashTimer / FlashDuration), true);
}
if (currColor.A > 0.0f && (sprites == null || !sprites.Any())) GUI.DrawRectangle(spriteBatch, rect, currColor * (currColor.A / 255.0f), true);
if (sprites != null && sprites[state] != null && currColor.A > 0.0f)
{
foreach (UISprite uiSprite in sprites[state])
{
if (uiSprite.Slice)
{
Vector2 pos = new Vector2(rect.X, rect.Y);
int centerWidth = Math.Max(rect.Width - uiSprite.Slices[0].Width - uiSprite.Slices[2].Width, 0);
int centerHeight = Math.Max(rect.Height - uiSprite.Slices[0].Height - uiSprite.Slices[8].Height, 0);
Vector2 scale = new Vector2(
MathHelper.Clamp((float)rect.Width / (uiSprite.Slices[0].Width + uiSprite.Slices[2].Width),0, 1),
MathHelper.Clamp((float)rect.Height / (uiSprite.Slices[0].Height + uiSprite.Slices[6].Height), 0, 1));
for (int x = 0; x < 3; x++)
{
float width = (x == 1 ? centerWidth : uiSprite.Slices[x].Width) * scale.X;
for (int y = 0; y < 3; y++)
{
float height = (y == 1 ? centerHeight : uiSprite.Slices[x + y * 3].Height) * scale.Y;
spriteBatch.Draw(uiSprite.Sprite.Texture,
new Rectangle((int)pos.X, (int)pos.Y, (int)width, (int)height),
uiSprite.Slices[x + y * 3],
currColor * (currColor.A / 255.0f));
pos.Y += height;
}
pos.X += width;
pos.Y = rect.Y;
}
}
else if (uiSprite.Tile)
{
Vector2 startPos = new Vector2(rect.X, rect.Y);
Vector2 size = new Vector2(Math.Min(uiSprite.Sprite.SourceRect.Width, rect.Width), Math.Min(uiSprite.Sprite.SourceRect.Height, rect.Height));
if (uiSprite.Sprite.size.X == 0.0f) size.X = rect.Width;
if (uiSprite.Sprite.size.Y == 0.0f) size.Y = rect.Height;
uiSprite.Sprite.DrawTiled(spriteBatch, startPos, size, color: currColor * (currColor.A / 255.0f));
}
else
{
if (uiSprite.MaintainAspectRatio)
{
float scale = (float)(rect.Width) / uiSprite.Sprite.SourceRect.Width;
spriteBatch.Draw(uiSprite.Sprite.Texture, rect,
new Rectangle(uiSprite.Sprite.SourceRect.X, uiSprite.Sprite.SourceRect.Y, (int)(uiSprite.Sprite.SourceRect.Width), (int)(rect.Height / scale)),
currColor * (currColor.A / 255.0f), 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
}
else
{
spriteBatch.Draw(uiSprite.Sprite.Texture, rect, uiSprite.Sprite.SourceRect, currColor * (currColor.A / 255.0f));
}
}
}
}
}
public void DrawToolTip(SpriteBatch spriteBatch)
{
if (!Visible) return;
int width = 400;
if (toolTipBlock == null || (string)toolTipBlock.userData != ToolTip)
{
toolTipBlock = new GUITextBlock(new Rectangle(0, 0, width, 18), ToolTip, "GUIToolTip", Alignment.TopLeft, Alignment.TopLeft, null, true, GUI.SmallFont);
toolTipBlock.padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
toolTipBlock.rect.Width = (int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + 20);
toolTipBlock.rect.Height = toolTipBlock.WrappedText.Split('\n').Length * 18 + 7;
toolTipBlock.userData = ToolTip;
}
toolTipBlock.rect = new Rectangle(MouseOn.Rect.Center.X, MouseOn.rect.Bottom, toolTipBlock.rect.Width, toolTipBlock.rect.Height);
if (toolTipBlock.rect.Right > GameMain.GraphicsWidth - 10)
{
toolTipBlock.rect.Location -= new Point(toolTipBlock.rect.Right - (GameMain.GraphicsWidth - 10), 0);
}
toolTipBlock.Draw(spriteBatch);
}
public virtual void Update(float deltaTime)
{
if (!Visible) return;
if (flashTimer>0.0f) flashTimer -= deltaTime;
/*if (CanBeFocused)
{
if (rect.Contains(PlayerInput.MousePosition))
{
MouseOn = this;
}
else
{
if (MouseOn == this) MouseOn = null;
}
}*/
//use a fixed list since children can change their order in the main children list
//TODO: maybe find a more efficient way of handling changes in list order
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
foreach (GUIComponent c in fixedChildren)
{
if (!c.Visible) continue;
c.Update(deltaTime);
}
}
public virtual void SetDimensions(Point size, bool expandChildren = false)
{
Point expandAmount = size - rect.Size;
rect = new Rectangle(rect.X, rect.Y, size.X, size.Y);
if (expandChildren)
{
//TODO: fix this (or replace with something better in the new GUI system)
//simply expanding the rects by the same amount as their parent only works correctly in some special cases
foreach (GUIComponent child in children)
{
child.Rect = new Rectangle(
child.rect.X,
child.rect.Y,
child.rect.Width + expandAmount.X,
child.rect.Height + expandAmount.Y);
}
}
}
protected virtual void UpdateDimensions(GUIComponent parent = null)
{
Rectangle parentRect = (parent == null) ? new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight) : parent.rect;
Vector4 padding = (parent == null) ? Vector4.Zero : parent.padding;
if (rect.Width == 0) rect.Width = parentRect.Width - rect.X
- (int)padding.X - (int)padding.Z;
if (rect.Height == 0) rect.Height = parentRect.Height - rect.Y
- (int)padding.Y - (int)padding.W;
if (alignment.HasFlag(Alignment.CenterX))
{
rect.X += parentRect.X + (int)parentRect.Width / 2 - (int)rect.Width / 2;
}
else if (alignment.HasFlag(Alignment.Right))
{
rect.X += parentRect.X + (int)parentRect.Width - (int)padding.Z - (int)rect.Width;
}
else
{
rect.X += parentRect.X + (int)padding.X;
}
if (alignment.HasFlag(Alignment.CenterY))
{
rect.Y += parentRect.Y + (int)parentRect.Height / 2 - (int)rect.Height / 2;
}
else if (alignment.HasFlag(Alignment.Bottom))
{
rect.Y += parentRect.Y + (int)parentRect.Height - (int)padding.W - (int)rect.Height;
}
else
{
rect.Y += parentRect.Y + (int)padding.Y;
}
}
public virtual void ApplyStyle(GUIComponentStyle style)
{
if (style == null) return;
@@ -543,77 +530,13 @@ namespace Barotrauma
color = style.Color;
hoverColor = style.HoverColor;
selectedColor = style.SelectedColor;
pressedColor = style.PressedColor;
padding = style.Padding;
sprites = style.Sprites;
OutlineColor = style.OutlineColor;
this.style = style;
}
public virtual void DrawChildren(SpriteBatch spriteBatch)
{
for (int i = 0; i < children.Count; i++ )
{
children[i].Draw(spriteBatch);
}
}
public virtual void AddChild(GUIComponent child)
{
if (child == null) return;
if (child.IsParentOf(this))
{
DebugConsole.ThrowError("Tried to add the parent of a GUIComponent as a child.\n" + Environment.StackTrace);
return;
}
if (child == this)
{
DebugConsole.ThrowError("Tried to add a GUIComponent as its own child\n" + Environment.StackTrace);
return;
}
if (children.Contains(child))
{
DebugConsole.ThrowError("Tried to add a the same child twice to a GUIComponent" + Environment.StackTrace);
return;
}
child.parent = this;
child.UpdateDimensions(this);
children.Add(child);
}
public virtual void RemoveChild(GUIComponent child)
{
if (child == null) return;
if (children.Contains(child)) children.Remove(child);
}
public GUIComponent FindChild(object userData, bool recursive = false)
{
var matchingChild = children.FirstOrDefault(c => c.userData == userData);
if (recursive && matchingChild == null)
{
foreach (GUIComponent child in children)
{
matchingChild = child.FindChild(userData, recursive);
if (matchingChild != null) return matchingChild;
}
}
return matchingChild;
}
public List<GUIComponent> FindChildren(object userData)
{
return children.FindAll(c => c.userData == userData);
}
public virtual void ClearChildren()
{
children.Clear();
}
}
}
@@ -0,0 +1,50 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
/// <summary>
/// GUIComponent that can be used to render custom content on the UI
/// </summary>
class GUICustomComponent : GUIComponent
{
public Action<SpriteBatch, GUICustomComponent> OnDraw;
public Action<float, GUICustomComponent> OnUpdate;
public bool HideElementsOutsideFrame;
public GUICustomComponent(RectTransform rectT, Action<SpriteBatch, GUICustomComponent> onDraw = null, Action<float, GUICustomComponent> onUpdate = null) : base(null, rectT)
{
OnDraw = onDraw;
OnUpdate = onUpdate;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (HideElementsOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
}
OnDraw?.Invoke(spriteBatch, this);
if (HideElementsOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
}
}
protected override void Update(float deltaTime)
{
if (Visible) OnUpdate?.Invoke(deltaTime, this);
}
}
}
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -13,18 +14,23 @@ namespace Barotrauma
private GUIButton button;
private GUIListBox listBox;
private RectTransform currentListBoxParent;
private List<RectTransform> parentHierarchy = new List<RectTransform>();
private bool selectMultiple;
public bool Dropped { get; set; }
public object SelectedItemData
{
get
{
if (listBox.Selected == null) return null;
return listBox.Selected.UserData;
if (listBox.SelectedComponent == null) return null;
return listBox.SelectedComponent.UserData;
}
}
public bool Enabled
public override bool Enabled
{
get { return listBox.Enabled; }
set { listBox.Enabled = value; }
@@ -32,7 +38,7 @@ namespace Barotrauma
public GUIComponent Selected
{
get { return listBox.Selected; }
get { return listBox.SelectedComponent; }
}
public GUIListBox ListBox
@@ -44,7 +50,7 @@ namespace Barotrauma
{
get
{
return (listBox.Selected == null) ? null : listBox.Selected.UserData;
return listBox.SelectedComponent?.UserData;
}
}
@@ -52,11 +58,29 @@ namespace Barotrauma
{
get
{
if (listBox.Selected == null) return -1;
return listBox.children.FindIndex(x => x == listBox.Selected);
if (listBox.SelectedComponent == null) return -1;
return listBox.Content.GetChildIndex(listBox.SelectedComponent);
}
}
private List<object> selectedDataMultiple = new List<object>();
public IEnumerable<object> SelectedDataMultiple
{
get { return selectedDataMultiple; }
}
private List<int> selectedIndexMultiple = new List<int>();
public IEnumerable<int> SelectedIndexMultiple
{
get { return selectedIndexMultiple; }
}
public string Text
{
get { return button.Text; }
set { button.Text = value; }
}
public override string ToolTip
{
get
@@ -70,56 +94,108 @@ namespace Barotrauma
listBox.ToolTip = value;
}
}
public override Rectangle Rect
public GUIDropDown(RectTransform rectT, string text = "", int elementCount = 4, string style = "", bool selectMultiple = false) : base(style, rectT)
{
get
this.selectMultiple = selectMultiple;
button = new GUIButton(new RectTransform(Vector2.One, rectT), text, Alignment.CenterLeft, style: "GUIDropDown")
{
return base.Rect;
}
set
OnClicked = OnClicked
};
GUI.Style.Apply(button, "", this);
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, Anchor.BottomLeft, Pivot.TopLeft)
{ IsFixedSize = false }, style: style)
{
Point moveAmount = value.Location - rect.Location;
base.Rect = value;
Enabled = !selectMultiple,
OnSelected = SelectItem
};
button.Rect = new Rectangle(button.Rect.Location + moveAmount, button.Rect.Size);
listBox.Rect = new Rectangle(listBox.Rect.Location + moveAmount, listBox.Rect.Size);
currentListBoxParent = FindListBoxParent();
currentListBoxParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
rectT.ParentChanged += (RectTransform newParent) =>
{
currentListBoxParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
if (newParent != null)
{
currentListBoxParent = FindListBoxParent();
currentListBoxParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
}
};
}
/// <summary>
/// Finds the component after which the listbox should be drawn. Usually the parent of the dropdown, but if the dropdown
/// is the child of another GUIListBox, we need to draw our listbox after that because listboxes clip everything outside their rect.
/// </summary>
private RectTransform FindListBoxParent()
{
parentHierarchy.Clear();
parentHierarchy = new List<RectTransform>() { RectTransform.Parent };
while (parentHierarchy.Last().Parent != null)
{
parentHierarchy.Add(parentHierarchy.Last().Parent);
}
//find the parent GUIListBox highest in the hierarchy
for (int i = parentHierarchy.Count - 1; i >= 0; i--)
{
if (parentHierarchy[i].GUIComponent is GUIListBox) return parentHierarchy[i];
}
//or just go with the direct parent if there are no listboxes in the hierarchy
parentHierarchy.Clear();
parentHierarchy.Add(RectTransform.Parent);
return RectTransform.Parent;
}
public GUIDropDown(Rectangle rect, string text, string style, GUIComponent parent = null)
: this(rect, text, style, Alignment.TopLeft, parent)
{
}
public GUIDropDown(Rectangle rect, string text, string style, Alignment alignment, GUIComponent parent = null)
: base(style)
{
this.rect = rect;
if (parent != null) parent.AddChild(this);
button = new GUIButton(this.rect, text, Color.White, alignment, Alignment.CenterLeft, "GUIDropDown", null);
GUI.Style.Apply(button, style, this);
button.OnClicked = OnClicked;
listBox = new GUIListBox(new Rectangle(this.rect.X, this.rect.Bottom, this.rect.Width, 200), style, null);
listBox.OnSelected = SelectItem;
}
public override void AddChild(GUIComponent child)
{
listBox.AddChild(child);
}
public void AddItem(string text, object userData = null, string toolTip = "")
{
GUITextBlock textBlock = new GUITextBlock(new Rectangle(0,0,0,20), text, "ListBoxElement", Alignment.TopLeft, Alignment.CenterLeft, listBox);
textBlock.UserData = userData;
textBlock.ToolTip = toolTip;
if (selectMultiple)
{
var frame = new GUIFrame(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
{ IsFixedSize = false }, style: "ListBoxElement")
{
UserData = userData,
ToolTip = toolTip
};
new GUITickBox(new RectTransform(new Point((int)(button.Rect.Height * 0.8f)), frame.RectTransform, anchor: Anchor.CenterLeft), text)
{
UserData = userData,
ToolTip = toolTip,
OnSelected = (GUITickBox tb) =>
{
List<string> texts = new List<string>();
selectedDataMultiple.Clear();
selectedIndexMultiple.Clear();
int i = 0;
foreach (GUIComponent child in ListBox.Content.Children)
{
var tickBox = child.GetChild<GUITickBox>();
if (tickBox.Selected)
{
selectedDataMultiple.Add(child.UserData);
selectedIndexMultiple.Add(i);
texts.Add(tickBox.Text);
}
i++;
}
button.Text = string.Join(", ", texts);
OnSelected?.Invoke(tb.Parent, tb.Parent.UserData);
return true;
}
};
}
else
{
new GUITextBlock(new RectTransform(new Point(button.Rect.Width, button.Rect.Height), listBox.Content.RectTransform)
{ IsFixedSize = false }, text, style: "ListBoxElement")
{
UserData = userData,
ToolTip = toolTip
};
}
}
public override void ClearChildren()
@@ -127,44 +203,63 @@ namespace Barotrauma
listBox.ClearChildren();
}
public List<GUIComponent> GetChildren()
public IEnumerable<GUIComponent> GetChildren()
{
return listBox.children;
return listBox.Content.Children;
}
private bool SelectItem(GUIComponent component, object obj)
{
GUITextBlock textBlock = component as GUITextBlock;
if (textBlock == null)
if (selectMultiple)
{
textBlock = component.GetChild<GUITextBlock>();
if (textBlock == null) return false;
foreach (GUIComponent child in ListBox.Content.Children)
{
var tickBox = child.GetChild<GUITickBox>();
if (obj == child.UserData) { tickBox.Selected = true; }
}
}
else
{
GUITextBlock textBlock = component as GUITextBlock;
if (textBlock == null)
{
textBlock = component.GetChild<GUITextBlock>();
if (textBlock == null) return false;
}
button.Text = textBlock.Text;
}
button.Text = textBlock.Text;
Dropped = false;
if (OnSelected != null) OnSelected(component, component.UserData);
OnSelected?.Invoke(component, component.UserData);
return true;
}
public void SelectItem(object userData)
{
//GUIComponent child = listBox.children.FirstOrDefault(c => c.UserData == userData);
//if (child == null) return;
listBox.Select(userData);
//SelectItem(child, userData);
if (selectMultiple)
{
SelectItem(listBox.Content.FindChild(userData), userData);
}
else
{
listBox.Select(userData);
}
}
public void Select(int index)
{
listBox.Select(index);
if (selectMultiple)
{
var child = listBox.Content.GetChild(index);
if (child != null)
{
SelectItem(null, child.UserData);
}
}
else
{
listBox.Select(index);
}
}
private bool wasOpened;
@@ -174,60 +269,68 @@ namespace Barotrauma
wasOpened = true;
Dropped = !Dropped;
if (Dropped)
if (Dropped && Enabled)
{
if (Enabled) OnDropped?.Invoke(this, userData);
if (parent.children[parent.children.Count - 1] != this)
{
parent.children.Remove(this);
parent.children.Add(this);
}
OnDropped?.Invoke(this, userData);
listBox.UpdateScrollBarSize();
}
return true;
}
public override void AddToGUIUpdateList()
private void AddListBoxToGUIUpdateList(GUIComponent parent)
{
base.AddToGUIUpdateList();
button.AddToGUIUpdateList();
if (Dropped) listBox.AddToGUIUpdateList();
//the parent is not our parent anymore :(
//can happen when subscribed to a parent higher in the hierarchy (instead of the direct parent),
//and somewhere between this component and the higher parent a component was removed
for (int i = 1; i < parentHierarchy.Count; i++)
{
if (!parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
{
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
return;
}
}
if (Dropped)
{
listBox.AddToGUIUpdateList(false, UpdateOrder);
}
}
public override void Update(float deltaTime)
public override void DrawManually(SpriteBatch spriteBatch, bool alsoChildren = false, bool recursive = true)
{
if (!Visible) return;
AutoDraw = false;
Draw(spriteBatch);
if (alsoChildren)
{
button.DrawManually(spriteBatch, alsoChildren, recursive);
}
}
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
base.AddToGUIUpdateList(true, order);
if (!ignoreChildren)
{
button.AddToGUIUpdateList(false, order);
}
}
protected override void Update(float deltaTime)
{
if (!Visible) return;
wasOpened = false;
base.Update(deltaTime);
if (Dropped && PlayerInput.LeftButtonClicked())
{
Rectangle listBoxRect = listBox.Rect;
listBoxRect.Width += 20;
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
{
Dropped = false;
}
}
button.Update(deltaTime);
if (Dropped) listBox.Update(deltaTime);
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
base.Draw(spriteBatch);
button.Draw(spriteBatch);
if (!Dropped) return;
listBox.Draw(spriteBatch);
}
}
}
@@ -1,61 +1,32 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace Barotrauma
{
public class GUIFrame : GUIComponent
{
public GUIFrame(Rectangle rect, string style = "", GUIComponent parent = null)
: this(rect, null, (Alignment.Left | Alignment.Top), style, parent)
{
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
{
}
public GUIFrame(Rectangle rect, Color color, string style = "", GUIComponent parent = null)
: this(rect, color, (Alignment.Left | Alignment.Top), style, parent)
{
}
public GUIFrame(Rectangle rect, Color? color, Alignment alignment, string style = "", GUIComponent parent = null)
: base(style)
{
this.rect = rect;
this.alignment = alignment;
if (color != null) this.color = (Color)color;
if (parent != null)
if (color.HasValue)
{
parent.AddChild(this);
this.color = color.Value;
}
else
{
UpdateDimensions();
}
//if (style != null) ApplyStyle(style);
}
public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Color currColor = color;
if (state == ComponentState.Selected) currColor = selectedColor;
if (state == ComponentState.Hover) currColor = hoverColor;
if (sprites == null || !sprites.Any()) GUI.DrawRectangle(spriteBatch, rect, currColor * (currColor.A/255.0f), true);
Color currColor = GetCurrentColor(state);
if (sprites == null || !sprites.Any()) GUI.DrawRectangle(spriteBatch, Rect, currColor * (currColor.A/255.0f), true);
base.Draw(spriteBatch);
if (OutlineColor != Color.Transparent)
{
GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (OutlineColor.A/255.0f), false);
GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false);
}
DrawChildren(spriteBatch);
}
}
}
@@ -12,7 +12,9 @@ namespace Barotrauma
private Rectangle sourceRect;
bool crop;
private bool crop;
private bool scaleToFit;
public bool Crop
{
@@ -43,55 +45,88 @@ namespace Barotrauma
set { sourceRect = value; }
}
public GUIImage(Rectangle rect, string spritePath, Alignment alignment, GUIComponent parent = null)
: this(rect, new Sprite(spritePath, Vector2.Zero), alignment, parent)
public Sprite Sprite
{
get { return sprite; }
set
{
if (sprite == value) return;
sprite = value;
sourceRect = sprite.SourceRect;
if (scaleToFit) RecalculateScale();
}
}
public GUIImage(RectTransform rectT, string style)
: this(rectT, null, null, false, style)
{
}
public GUIImage(Rectangle rect, Sprite sprite, Alignment alignment, GUIComponent parent = null)
: this(rect, sprite==null ? Rectangle.Empty : sprite.SourceRect, sprite, alignment, parent)
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
: this(rectT, sprite, sourceRect, scaleToFit, null)
{
}
public GUIImage(Rectangle rect, Rectangle sourceRect, Sprite sprite, Alignment alignment, GUIComponent parent = null)
: base(null)
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
{
this.rect = rect;
this.alignment = alignment;
color = Color.White;
//alpha = 1.0f;
Scale = 1.0f;
this.sprite = sprite;
if (rect.Width == 0) this.rect.Width = (int)sprite.size.X;
if (rect.Height == 0) this.rect.Height = (int)Math.Min(sprite.size.Y, sprite.size.Y * (this.rect.Width / sprite.size.X));
this.sourceRect = sourceRect;
if (parent != null) parent.AddChild(this);
this.parent = parent;
this.scaleToFit = scaleToFit;
Sprite = sprite;
if (sourceRect.HasValue)
{
this.sourceRect = sourceRect.Value;
}
else
{
this.sourceRect = sprite == null ? Rectangle.Empty : sprite.SourceRect;
}
if (style == null)
{
color = Color.White;
hoverColor = Color.White;
selectedColor = Color.White;
}
if (!scaleToFit)
{
Scale = 1.0f;
}
else
{
rectT.SizeChanged += RecalculateScale;
}
}
public override void Draw(SpriteBatch spriteBatch)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Color currColor = color;
if (state == ComponentState.Hover) currColor = hoverColor;
if (state == ComponentState.Selected) currColor = selectedColor;
if (sprite != null && sprite.Texture != null)
Color currColor = GetCurrentColor(state);
if (style != null)
{
spriteBatch.Draw(sprite.Texture, new Vector2(rect.X, rect.Y), sourceRect, currColor * (currColor.A / 255.0f), Rotation, Vector2.Zero,
foreach (UISprite uiSprite in style.Sprites[state])
{
if (Math.Abs(Rotation) > float.Epsilon)
{
float scale = Math.Min(Rect.Width / uiSprite.Sprite.size.X, Rect.Height / uiSprite.Sprite.size.Y);
spriteBatch.Draw(uiSprite.Sprite.Texture, Rect.Center.ToVector2(), uiSprite.Sprite.SourceRect, currColor * (currColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2,
Scale * scale, SpriteEffects.None, 0.0f);
}
else
{
uiSprite.Draw(spriteBatch, Rect, currColor * (currColor.A / 255.0f), SpriteEffects.None);
}
}
}
else if (sprite?.Texture != null)
{
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currColor * (currColor.A / 255.0f), Rotation, sprite.size / 2,
Scale, SpriteEffects.None, 0.0f);
}
DrawChildren(spriteBatch);
}
}
private void RecalculateScale()
{
Scale = sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
1.0f :
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
}
}
}
@@ -0,0 +1,137 @@
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma
{
public class GUILayoutGroup : GUIComponent
{
private bool isHorizontal;
public bool IsHorizontal
{
get { return isHorizontal; }
set
{
isHorizontal = value;
needsToRecalculate = true;
}
}
private bool stretch;
/// <summary>
/// Note that stretching cannot be undone, because the previous child sizes are not stored.
/// </summary>
public bool Stretch
{
get { return stretch; }
set
{
stretch = value;
needsToRecalculate = true;
}
}
private int absoluteSpacing;
public int AbsoluteSpacing
{
get { return absoluteSpacing; }
set
{
absoluteSpacing = MathHelper.Clamp(value, 0, int.MaxValue);
needsToRecalculate = true;
}
}
private float relativeSpacing;
public float RelativeSpacing
{
get { return relativeSpacing; }
set
{
relativeSpacing = MathHelper.Clamp(value, -1, 1);
needsToRecalculate = true;
}
}
private Anchor childAnchor;
public Anchor ChildAnchor
{
get { return childAnchor; }
set
{
childAnchor = value;
needsToRecalculate = true;
}
}
private bool needsToRecalculate;
public bool NeedsToRecalculate
{
get { return needsToRecalculate; }
}
public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
{
this.isHorizontal = isHorizontal;
this.childAnchor = childAnchor;
rectT.ChildrenChanged += (child) => needsToRecalculate = true;
rectT.ScaleChanged += () => needsToRecalculate = true;
rectT.SizeChanged += () => needsToRecalculate = true;
}
public void Recalculate()
{
float stretchFactor = 1.0f;
if (stretch && RectTransform.Children.Count() > 0)
{
float totalSize = RectTransform.Children
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
.Sum(c => isHorizontal ? c.Rect.Width : c.Rect.Height);
totalSize +=
(RectTransform.Children.Count() - 1) *
(absoluteSpacing + relativeSpacing * (isHorizontal ? Rect.Width : Rect.Height));
stretchFactor = totalSize <= 0.0f ? 1.0f : (isHorizontal ? Rect.Width: Rect.Height) / totalSize;
}
int absPos = 0;
float relPos = 0;
foreach (var child in RectTransform.Children)
{
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
child.SetPosition(childAnchor);
if (isHorizontal)
{
child.RelativeOffset = new Vector2(relPos, child.RelativeOffset.Y);
child.AbsoluteOffset = new Point(absPos, child.AbsoluteOffset.Y);
absPos += (int)((child.Rect.Width + absoluteSpacing) * stretchFactor);
if (stretch)
{
child.RelativeSize = new Vector2(child.RelativeSize.X * stretchFactor, child.RelativeSize.Y);
}
}
else
{
child.RelativeOffset = new Vector2(child.RelativeOffset.X, relPos);
child.AbsoluteOffset = new Point(child.AbsoluteOffset.X, absPos);
absPos += (int)((child.Rect.Height + absoluteSpacing) * stretchFactor);
if (stretch)
{
child.RelativeSize = new Vector2(child.RelativeSize.X, child.RelativeSize.Y * stretchFactor);
}
}
relPos += relativeSpacing * stretchFactor;
}
needsToRecalculate = false;
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
if (needsToRecalculate)
{
Recalculate();
}
}
}
}
@@ -1,12 +1,15 @@
using Microsoft.Xna.Framework;
using EventInput;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
public class GUIListBox : GUIComponent
public class GUIListBox : GUIComponent, IKeyboardSubscriber
{
protected List<GUIComponent> selected;
@@ -16,28 +19,44 @@ namespace Barotrauma
public delegate object CheckSelectedHandler();
public CheckSelectedHandler CheckSelected;
private GUIScrollBar scrollBar;
private GUIFrame frame;
public GUIScrollBar ScrollBar { get; private set; }
public GUIFrame Content { get; private set; }
private int totalSize;
private int spacing;
private bool scrollBarEnabled;
private bool scrollBarHidden;
private bool enabled;
private bool childrenNeedsRecalculation;
private bool scrollBarNeedsRecalculation;
public bool SelectMultiple;
public GUIComponent Selected
public bool HideChildrenOutsideFrame = true;
private bool useGridLayout;
public bool UseGridLayout
{
get { return useGridLayout; }
set
{
if (useGridLayout == value) return;
useGridLayout = value;
childrenNeedsRecalculation = true;
scrollBarNeedsRecalculation = true;
}
}
public GUIComponent SelectedComponent
{
get
{
return selected.Any() ? selected[0] : null;
return selected.FirstOrDefault();
}
}
public bool Selected { get; set; }
public List<GUIComponent> AllSelected
{
get { return selected; }
@@ -47,7 +66,7 @@ namespace Barotrauma
{
get
{
return (Selected == null) ? null : Selected.UserData;
return SelectedComponent?.UserData;
}
}
@@ -55,20 +74,25 @@ namespace Barotrauma
{
get
{
if (Selected == null) return -1;
return children.FindIndex(x => x == Selected);
if (SelectedComponent == null) return -1;
return Content.RectTransform.GetChildIndex(SelectedComponent.RectTransform);
}
}
public float BarScroll
{
get { return scrollBar.BarScroll; }
set { scrollBar.BarScroll = value; }
get { return ScrollBar.BarScroll; }
set { ScrollBar.BarScroll = value; }
}
public float BarSize
{
get { return scrollBar.BarSize; }
get { return ScrollBar.BarSize; }
}
public float TotalSize
{
get { return totalSize; }
}
public int Spacing
@@ -77,32 +101,6 @@ namespace Barotrauma
set { spacing = value; }
}
public bool Enabled
{
get { return enabled; }
set
{
enabled = value;
//scrollBar.Enabled = value;
}
}
public override Rectangle Rect
{
get
{
return rect;
}
set
{
base.Rect = value;
frame.Rect = value;
scrollBar.Rect = scrollBar.IsHorizontal ?
new Rectangle(rect.X, rect.Bottom - 20, rect.Width, 20) :
new Rectangle(rect.Right - 20, rect.Y, 20, rect.Height);
}
}
public override Color Color
{
get
@@ -113,145 +111,201 @@ namespace Barotrauma
{
base.Color = value;
frame.Color = value;
Content.Color = value;
}
}
/// <summary>
/// Disables the scroll bar without hiding it.
/// </summary>
public bool ScrollBarEnabled { get; set; } = true;
public bool ScrollBarEnabled
public bool ScrollBarVisible
{
get { return scrollBarEnabled; }
get
{
return ScrollBar.Visible;
}
set
{
scrollBarEnabled = value;
ScrollBar.Visible = value;
AutoHideScrollBar = false;
}
}
public GUIListBox(Rectangle rect, string style, GUIComponent parent = null)
: this(rect, style, Alignment.TopLeft, parent)
{
}
/// <summary>
/// Automatically hides the scroll bar when the content fits in.
/// </summary>
public bool AutoHideScrollBar { get; set; } = true;
public GUIListBox(Rectangle rect, string style, Alignment alignment, GUIComponent parent = null)
: this(rect, null, alignment, style, parent, false)
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "") : base(style, rectT)
{
}
public GUIListBox(Rectangle rect, Color? color, string style = null, GUIComponent parent = null)
: this(rect, color, (Alignment.Left | Alignment.Top), style, parent)
{
}
public GUIListBox(Rectangle rect, Color? color, Alignment alignment, string style = null, GUIComponent parent = null, bool isHorizontal = false)
: base(style)
{
this.rect = rect;
this.alignment = alignment;
selected = new List<GUIComponent>();
if (color != null) this.color = (Color)color;
Point frameSize = isHorizontal ?
new Point(rectT.NonScaledSize.X, rectT.NonScaledSize.Y - 20) :
new Point(rectT.NonScaledSize.X - 20, rectT.NonScaledSize.Y);
if (parent != null)
parent.AddChild(this);
Content = new GUIFrame(new RectTransform(frameSize, rectT), style)
{
CanBeFocused = false
};
Content.RectTransform.ChildrenChanged += (_) =>
{
scrollBarNeedsRecalculation = true;
childrenNeedsRecalculation = true;
};
scrollBarHidden = true;
if (style != null) GUI.Style.Apply(Content, "", this);
if (color.HasValue)
{
this.color = color.Value;
}
if (isHorizontal)
{
scrollBar = new GUIScrollBar(
new Rectangle(this.rect.X, this.rect.Bottom - 20, this.rect.Width, 20), null, 1.0f, "");
ScrollBar = new GUIScrollBar(new RectTransform(new Point(Rect.Width, 20), rectT, Anchor.BottomLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(0, 20) }, isHorizontal: isHorizontal);
}
else
{
scrollBar = new GUIScrollBar(
new Rectangle(this.rect.Right - 20, this.rect.Y, 20, this.rect.Height), null, 1.0f, "");
ScrollBar = new GUIScrollBar(new RectTransform(new Point(20, Rect.Height), rectT, Anchor.TopRight, Pivot.TopLeft) { AbsoluteOffset = new Point(20, 0) }, isHorizontal: isHorizontal);
}
scrollBar.IsHorizontal = isHorizontal;
frame = new GUIFrame(new Rectangle(0, 0, this.rect.Width, this.rect.Height), style, this);
if (style != null) GUI.Style.Apply(frame, style, this);
UpdateScrollBarSize();
Enabled = true;
ScrollBar.BarScroll = 0.0f;
RectTransform.ScaleChanged += UpdateDimensions;
RectTransform.SizeChanged += UpdateDimensions;
}
children.Clear();
enabled = true;
scrollBarEnabled = true;
scrollBar.BarScroll = 0.0f;
private void UpdateDimensions()
{
if (!ScrollBarEnabled)
{
Content.RectTransform.NonScaledSize = Rect.Size;
}
else
{
Point frameSize = ScrollBar.IsHorizontal ?
new Point(Rect.Width, Rect.Height - 20) :
new Point(Rect.Width - 20, Rect.Height);
Content.RectTransform.NonScaledSize = frameSize;
}
ScrollBar.RectTransform.NonScaledSize = ScrollBar.IsHorizontal ? new Point(Rect.Width, 20) : new Point(20, Rect.Height);
}
public void Select(object userData, bool force = false)
public void Select(object userData, bool force = false, bool autoScroll = true)
{
for (int i = 0; i < children.Count; i++)
var children = Content.Children;
int i = 0;
foreach (GUIComponent child in children)
{
if ((children[i].UserData != null && children[i].UserData.Equals(userData)) ||
(children[i].UserData == null && userData == null))
if ((child.UserData != null && child.UserData.Equals(userData)) ||
(child.UserData == null && userData == null))
{
Select(i, force);
Select(i, force, autoScroll);
if (!SelectMultiple) return;
}
i++;
}
}
public override void SetDimensions(Point size, bool expandChildren = false)
private void RepositionChildren()
{
base.SetDimensions(size, expandChildren);
frame.SetDimensions(size, expandChildren);
if (scrollBar.IsHorizontal)
var children = Content.Children;
int x = 0, y = 0;
if (ScrollBar.BarSize < 1.0f)
{
scrollBar.Rect = new Rectangle(this.rect.X, this.rect.Bottom - 20, this.rect.Width, 20);
}
else
{
scrollBar.Rect = new Rectangle(this.rect.Right - 20, this.rect.Y, 20, this.rect.Height);
if (ScrollBar.IsHorizontal)
{
x -= (int)((totalSize - Content.Rect.Width) * ScrollBar.BarScroll);
}
else
{
y -= (int)((totalSize - Content.Rect.Height) * ScrollBar.BarScroll);
}
}
UpdateScrollBarSize();
for (int i = 0; i < Content.CountChildren; i++)
{
GUIComponent child = Content.GetChild(i);
if (!child.Visible) { continue; }
if (RectTransform != null)
{
if (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
}
if (useGridLayout)
{
if (ScrollBar.IsHorizontal)
{
if (y + child.Rect.Height + spacing > Content.Rect.Height)
{
y = 0;
x += child.Rect.Width + spacing;
if (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
y += child.Rect.Height + spacing;
}
else
{
y += child.Rect.Height + spacing;
}
}
else
{
if (x + child.Rect.Width + spacing > Content.Rect.Width)
{
x = 0;
y += child.Rect.Height + spacing;
if (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
x += child.Rect.Width + spacing;
}
else
{
x += child.Rect.Width + spacing;
}
}
}
else
{
if (ScrollBar.IsHorizontal)
{
x += child.Rect.Width + spacing;
}
else
{
y += child.Rect.Height + spacing;
}
}
}
}
private void UpdateChildrenRect(float deltaTime)
private void UpdateChildrenRect()
{
int x = rect.X, y = rect.Y;
if (!scrollBarHidden)
for (int i = 0; i < Content.CountChildren; i++)
{
if (scrollBar.IsHorizontal)
{
x -= (int)((totalSize - rect.Width) * scrollBar.BarScroll);
}
else
{
y -= (int)((totalSize - rect.Height) * scrollBar.BarScroll);
}
}
var child = Content.RectTransform.GetChild(i)?.GUIComponent;
if (child == null) continue;
for (int i = 0; i < children.Count; i++)
{
GUIComponent child = children[i];
if (child == frame || !child.Visible) continue;
child.Rect = new Rectangle(x, y, child.Rect.Width, child.Rect.Height);
if (scrollBar.IsHorizontal)
{
x += child.Rect.Width + spacing;
}
else
{
y += child.Rect.Height + spacing;
}
if (deltaTime>0.0f) child.Update(deltaTime);
if (enabled && child.CanBeFocused &&
(MouseOn == this || (MouseOn != null && this.IsParentOf(MouseOn))) && child.Rect.Contains(PlayerInput.MousePosition))
// selecting
if (Enabled && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition))
{
child.State = ComponentState.Hover;
if (PlayerInput.LeftButtonClicked())
{
Select(i);
Select(i, autoScroll: false);
}
}
else if (selected.Contains(child))
@@ -270,186 +324,328 @@ namespace Barotrauma
}
}
public override void AddToGUIUpdateList()
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
if (!Visible) return;
if (ComponentsToUpdate.Contains(this)) return;
ComponentsToUpdate.Add(this);
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
int lastVisible = 0;
for (int i = 0; i < fixedChildren.Count; i++)
{
if (fixedChildren[i] == frame) continue;
if (!Visible) { return; }
if (!IsChildVisible(fixedChildren[i]))
if (childrenNeedsRecalculation)
{
foreach (GUIComponent child in Content.Children)
{
ClampChildMouseRects(child);
}
RepositionChildren();
childrenNeedsRecalculation = false;
}
UpdateOrder = order;
GUI.AddToUpdateList(this);
if (ignoreChildren)
{
OnAddedToGUIUpdateList?.Invoke(this);
return;
}
Content.AddToGUIUpdateList(true, order);
int lastVisible = 0;
for (int i = 0; i < Content.CountChildren; i++)
{
var child = Content.GetChild(i);
if (!child.Visible) continue;
if (!IsChildInsideFrame(child))
{
if (lastVisible > 0) break;
continue;
}
lastVisible = i;
fixedChildren[i].AddToGUIUpdateList();
child.AddToGUIUpdateList(false, order);
}
if (scrollBarEnabled && !scrollBarHidden) scrollBar.AddToGUIUpdateList();
}
public override Rectangle MouseRect
{
get
if (ScrollBar.Enabled)
{
return ClampMouseRectToParent ? ClampRect(rect) : rect;
ScrollBar.AddToGUIUpdateList(false, order);
}
OnAddedToGUIUpdateList?.Invoke(this);
}
private void ClampChildMouseRects(GUIComponent child)
{
child.ClampMouseRectToParent = true;
//no need to go through grandchildren if the child is a GUIListBox, it handles this by itself
if (child is GUIListBox) return;
foreach (GUIComponent grandChild in child.Children)
{
ClampChildMouseRects(grandChild);
}
}
public override void Update(float deltaTime)
protected override void Update(float deltaTime)
{
if (!Visible) return;
UpdateChildrenRect(deltaTime);
if (scrollBarEnabled && !scrollBarHidden) scrollBar.Update(deltaTime);
UpdateChildrenRect();
RepositionChildren();
if ((MouseOn == this || MouseOn == scrollBar || IsParentOf(MouseOn)) && PlayerInput.ScrollWheelSpeed != 0)
if (scrollBarNeedsRecalculation)
{
scrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
UpdateScrollBarSize();
scrollBarNeedsRecalculation = false;
}
ScrollBar.Enabled = ScrollBarEnabled && ScrollBar.BarSize < 1.0f;
if (AutoHideScrollBar)
{
ScrollBar.Visible = ScrollBar.BarSize < 1.0f;
}
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0)
{
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
}
}
public void Select(int childIndex, bool force = false)
public void SelectNext(bool force = false, bool autoScroll = true)
{
if (childIndex >= children.Count || childIndex < 0) return;
Select(Math.Min(Content.CountChildren - 1, SelectedIndex + 1), force, autoScroll);
}
public void SelectPrevious(bool force = false, bool autoScroll = true)
{
Select(Math.Max(0, SelectedIndex - 1), force, autoScroll);
}
public void Select(int childIndex, bool force = false, bool autoScroll = true)
{
if (childIndex >= Content.CountChildren || childIndex < 0) return;
GUIComponent child = Content.GetChild(childIndex);
bool wasSelected = true;
if (OnSelected != null) wasSelected = OnSelected(children[childIndex], children[childIndex].UserData) || force;
if (OnSelected != null) wasSelected = force || OnSelected(child, child.UserData);
if (!wasSelected) return;
if (SelectMultiple)
{
if (selected.Contains(children[childIndex]))
if (selected.Contains(child))
{
selected.Remove(children[childIndex]);
selected.Remove(child);
}
else
{
selected.Add(children[childIndex]);
selected.Add(child);
}
}
else
{
selected.Clear();
selected.Add(children[childIndex]);
selected.Add(child);
}
// Ensure that the selected element is visible. This may not be the case, if the selection is run from code. (e.g. if we have two list boxes that are synced)
// TODO: This method only works when moving one item up/down (e.g. when using the up and down arrows)
if (autoScroll)
{
if (ScrollBar.IsHorizontal)
{
if (child.Rect.X < MouseRect.X)
{
//child outside the left edge of the frame -> move left
ScrollBar.BarScroll -= (float)(MouseRect.X - child.Rect.X) / (totalSize - Content.Rect.Width);
}
else if (child.Rect.Right > MouseRect.Right)
{
//child outside the right edge of the frame -> move right
ScrollBar.BarScroll += (float)(child.Rect.Right - MouseRect.Right) / (totalSize - Content.Rect.Width);
}
}
else
{
if (child.Rect.Y < MouseRect.Y)
{
//child above the top of the frame -> move up
ScrollBar.BarScroll -= (float)(MouseRect.Y - child.Rect.Y) / (totalSize - Content.Rect.Height);
}
else if (child.Rect.Bottom > MouseRect.Bottom)
{
//child below the bottom of the frame -> move down
ScrollBar.BarScroll += (float)(child.Rect.Bottom - MouseRect.Bottom) / (totalSize - Content.Rect.Height);
}
}
}
// If one of the children is the subscriber, we don't want to register, because it will unregister the child.
if (RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
{
Selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
}
}
public void Deselect()
{
Selected = false;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
selected.Clear();
}
public void UpdateScrollBarSize()
{
totalSize = (int)(padding.Y + padding.W);
foreach (GUIComponent child in children)
if (Content == null) return;
totalSize = 0;
var children = Content.Children.Where(c => c.Visible);
if (useGridLayout)
{
if (child == frame || !child.Visible) continue;
totalSize += (scrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
int pos = 0;
foreach (GUIComponent child in children)
{
if (ScrollBar.IsHorizontal)
{
if (pos + child.Rect.Height + spacing > Content.Rect.Height || child == children.Last())
{
pos = 0;
totalSize += child.Rect.Width + spacing;
}
else
{
pos += child.Rect.Height + spacing;
}
}
else
{
if (pos + child.Rect.Width + spacing > Content.Rect.Width || child == children.Last())
{
pos = 0;
totalSize += child.Rect.Height + spacing;
}
else
{
pos += child.Rect.Width + spacing;
}
}
}
}
else
{
foreach (GUIComponent child in children)
{
totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
}
totalSize += Content.CountChildren * spacing;
}
totalSize += (children.Count - 1) * spacing;
scrollBar.BarSize = scrollBar.IsHorizontal ?
Math.Max(Math.Min((float)rect.Width / (float)totalSize, 1.0f), 5.0f / rect.Width) :
Math.Max(Math.Min((float)rect.Height / (float)totalSize, 1.0f), 5.0f / rect.Height);
scrollBarHidden = scrollBar.BarSize >= 1.0f;
ScrollBar.BarSize = ScrollBar.IsHorizontal ?
Math.Max(Math.Min(Content.Rect.Width / (float)totalSize, 1.0f), 5.0f / Content.Rect.Width) :
Math.Max(Math.Min(Content.Rect.Height / (float)totalSize, 1.0f), 5.0f / Content.Rect.Height);
}
public override void AddChild(GUIComponent child)
{
//temporarily reduce the size of the rect to prevent the child from expanding over the scrollbar
if (scrollBar.IsHorizontal)
rect.Height -= scrollBar.Rect.Height;
else
rect.Width -= scrollBar.Rect.Width;
base.AddChild(child);
if (scrollBar.IsHorizontal)
rect.Height += scrollBar.Rect.Height;
else
rect.Width += scrollBar.Rect.Width;
UpdateScrollBarSize();
UpdateChildrenRect(0.0f);
}
public override void ClearChildren()
{
base.ClearChildren();
Content.ClearChildren();
selected.Clear();
}
public override void RemoveChild(GUIComponent child)
{
if (child == null) return;
base.RemoveChild(child);
child.RectTransform.Parent = null;
if (selected.Contains(child)) selected.Remove(child);
UpdateScrollBarSize();
}
public override void Draw(SpriteBatch spriteBatch)
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
{
//do nothing (the children have to be drawn in the Draw method after the ScissorRectangle has been set)
return;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Content.DrawManually(spriteBatch, alsoChildren: false);
frame.Draw(spriteBatch);
if (!scrollBarHidden) scrollBar.Draw(spriteBatch);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, frame.Rect);
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
}
var children = Content.Children;
int lastVisible = 0;
for (int i = 0; i < children.Count; i++)
{
GUIComponent child = children[i];
if (child == frame || !child.Visible) continue;
if (!IsChildVisible(child))
int i = 0;
foreach (GUIComponent child in Content.Children)
{
if (!child.Visible) continue;
if (!IsChildInsideFrame(child))
{
if (lastVisible > 0) break;
continue;
}
lastVisible = i;
child.Draw(spriteBatch);
lastVisible = i;
child.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
i++;
}
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: prevRasterizerState);
}
if (ScrollBar.Visible) ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
}
private bool IsChildVisible(GUIComponent child)
private bool IsChildInsideFrame(GUIComponent child)
{
if (child == null) return false;
if (scrollBar.IsHorizontal)
if (ScrollBar.IsHorizontal)
{
if (child.Rect.Right < rect.X) return false;
if (child.Rect.X > rect.Right) return false;
if (child.Rect.Right < Content.Rect.X) return false;
if (child.Rect.X > Content.Rect.Right) return false;
}
else
{
if (child.Rect.Bottom < rect.Y) return false;
if (child.Rect.Y > rect.Bottom) return false;
if (child.Rect.Bottom < Content.Rect.Y) return false;
if (child.Rect.Y > Content.Rect.Bottom) return false;
}
return true;
}
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
public void ReceiveSpecialInput(Keys key)
{
switch (key)
{
case Keys.Down:
SelectNext();
break;
case Keys.Up:
SelectPrevious();
break;
default:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}
}
}
}
@@ -11,6 +11,8 @@ namespace Barotrauma
private Vector2 size;
public readonly bool WorldSpace;
public string Text
{
get { return coloredText.Text; }
@@ -27,57 +29,69 @@ namespace Barotrauma
set { pos = value; }
}
public Vector2 Size
{
get { return size; }
}
public Vector2 Origin;
public float LifeTime
{
get { return lifeTime; }
set { lifeTime = value; }
}
public Alignment Alignment
public Vector2 Velocity
{
get;
private set;
}
public Vector2 Size
{
get { return size; }
}
public Vector2 Origin;
/// <summary>
/// Autocentered messages are automatically placed at the center of the screen and prevented from overlapping with each other
/// </summary>
public bool AutoCenter;
public float Timer;
public GUIMessage(string text, Color color, Vector2 position, float lifeTime, Alignment textAlignment, bool autoCenter)
public float LifeTime
{
get { return lifeTime; }
}
public ScalableFont Font
{
get;
private set;
}
public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null)
{
coloredText = new ColoredText(text, color, false);
pos = position;
this.lifeTime = lifeTime;
this.Alignment = textAlignment;
this.AutoCenter = autoCenter;
Timer = lifeTime;
size = GUI.Font.MeasureString(text);
size = font.MeasureString(text);
Origin = new Vector2(0, size.Y * 0.5f);
Font = font;
}
public GUIMessage(string text, Color color, Vector2 worldPosition, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null)
{
coloredText = new ColoredText(text, color, false);
WorldSpace = true;
pos = worldPosition;
Timer = lifeTime;
Velocity = velocity;
this.lifeTime = lifeTime;
Font = font;
size = font.MeasureString(text);
Origin = new Vector2((int)(0.5f * size.X), (int)(0.5f * size.Y));
if (textAlignment.HasFlag(Alignment.Left))
Origin.X += size.X * 0.5f;
if (textAlignment.HasFlag(Alignment.Right))
Origin.X -= size.X * 0.5f;
if (textAlignment.HasFlag(Alignment.Top))
Origin.Y += size.Y * 0.5f;
if (textAlignment.HasFlag(Alignment.Right))
Origin.X += size.X * 0.5f;
if (textAlignment.HasFlag(Alignment.Bottom))
if (textAlignment.HasFlag(Alignment.Top))
Origin.Y -= size.Y * 0.5f;
if (autoCenter)
{
Origin = new Vector2((int)(0.5f * size.X), (int)(0.5f * size.Y));
}
if (textAlignment.HasFlag(Alignment.Bottom))
Origin.Y += size.Y * 0.5f;
}
}
}
@@ -1,5 +1,8 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -9,48 +12,41 @@ namespace Barotrauma
public const int DefaultWidth = 400, DefaultHeight = 250;
public GUIButton[] Buttons;
public static GUIComponent VisibleBox
{
get { return MessageBoxes.Count == 0 ? null : MessageBoxes[0]; }
}
public GUIFrame InnerFrame
{
get { return children[0] as GUIFrame; }
}
public string Text
{
get { return (children[0].children[1] as GUITextBlock).Text; }
set { (children[0].children[1] as GUITextBlock).Text = value; }
}
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
//public GUIFrame BackgroundFrame { get; private set; }
public GUILayoutGroup Content { get; private set; }
public GUIFrame InnerFrame { get; private set; }
public GUITextBlock Header { get; private set; }
public GUITextBlock Text { get; private set; }
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(string headerText, string text)
: this(headerText, text, new string[] {"OK"}, DefaultWidth, 0)
{
this.Buttons[0].OnClicked = Close;
}
public GUIMessageBox(string headerText, string text, int width, int height)
: this(headerText, text, new string[] { "OK" }, width, height)
{
this.Buttons[0].OnClicked = Close;
}
public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = 0, Alignment textAlignment = Alignment.TopLeft, GUIComponent parent = null)
: base(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
Color.Black * 0.5f, Alignment.TopLeft, null, parent)
// TODO: allow to use a relative size.
public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = 0, Alignment textAlignment = Alignment.TopLeft)
: base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "")
{
int headerHeight = 30;
var frame = new GUIFrame(new Rectangle(0, 0, width, height), null, Alignment.Center, "", this);
GUI.Style.Apply(frame, "", this);
InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, Anchor.Center) { IsFixedSize = false }, style: null);
GUI.Style.Apply(InnerFrame, "", this);
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
if (height == 0)
{
string wrappedText = ToolBox.WrapText(text, frame.Rect.Width - frame.Padding.X - frame.Padding.Z, GUI.Font);
string wrappedText = ToolBox.WrapText(text, Content.Rect.Width, GUI.Font);
string[] lines = wrappedText.Split('\n');
foreach (string line in lines)
{
@@ -58,33 +54,103 @@ namespace Barotrauma
}
height += string.IsNullOrWhiteSpace(headerText) ? 220 : 220 - headerHeight;
}
frame.Rect = new Rectangle(frame.Rect.X, GameMain.GraphicsHeight / 2 - height/2, frame.Rect.Width, height);
InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height);
var header = new GUITextBlock(new Rectangle(0, 0, 0, headerHeight), headerText, null, null, textAlignment, "", frame, true);
GUI.Style.Apply(header, "", this);
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
headerText, textAlignment: Alignment.Center, wrap: true);
GUI.Style.Apply(Header, "", this);
if (!string.IsNullOrWhiteSpace(text))
{
var textBlock = new GUITextBlock(new Rectangle(0, string.IsNullOrWhiteSpace(headerText) ? 0 : headerHeight, 0, height - 70), text,
null, null, textAlignment, "", frame, true);
GUI.Style.Apply(textBlock, "", this);
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
}
int x = 0;
this.Buttons = new GUIButton[buttons.Length];
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter),
isHorizontal: true, childAnchor: Anchor.BottomLeft)
{
AbsoluteSpacing = 5,
IgnoreLayoutGroups = true
};
Buttons = new List<GUIButton>(buttons.Length);
for (int i = 0; i < buttons.Length; i++)
{
this.Buttons[i] = new GUIButton(new Rectangle(x, 0, 150, 30), buttons[i], Alignment.Left | Alignment.Bottom, "", frame);
x += this.Buttons[i].Rect.Width + 20;
var button = new GUIButton(new RectTransform(new Vector2(Math.Min(0.9f / buttons.Length, 0.5f), 1.0f), buttonContainer.RectTransform, maxSize: new Point(300, 30)), buttons[i]);
Buttons.Add(button);
}
MessageBoxes.Add(this);
}
///// <summary>
///// This is the new constructor.
///// TODO: for some reason the background does not prohibit input on the elements that are behind the box
///// TODO: allow providing buttons in the constructor
///// </summary>
/*public GUIMessageBox(RectTransform rectT, string headerText, string text, Alignment textAlignment = Alignment.TopCenter)
: base(rectT, "")
{
//BackgroundFrame = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), rectT, Anchor.Center), null, Color.Black * 0.5f);
float headerHeight = 0.2f;
float margin = 0.05f;
InnerFrame = new GUIFrame(rectT);
GUI.Style.Apply(InnerFrame, "", this);
Header = null;
if (!string.IsNullOrWhiteSpace(headerText))
{
Header = new GUITextBlock(new RectTransform(new Vector2(1, headerHeight), InnerFrame.RectTransform, Anchor.TopCenter)
{
RelativeOffset = new Vector2(0, margin)
}, headerText, textAlignment: Alignment.Center);
GUI.Style.Apply(Header, "", this);
}
if (!string.IsNullOrWhiteSpace(text))
{
float offset = headerHeight + margin;
var size = Header == null ? Vector2.One : new Vector2(1 - margin * 2, 1 - offset + margin);
Text = new GUITextBlock(new RectTransform(size, InnerFrame.RectTransform, Anchor.TopCenter)
{
RelativeOffset = new Vector2(0, offset)
}, text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
}
MessageBoxes.Add(this);
}*/
//public override void AddToGUIUpdateList(bool ignoreChildren = false, bool updateLast = false)
//{
// base.AddToGUIUpdateList(ignoreChildren, updateLast);
//}
//public override void Draw(SpriteBatch spriteBatch, bool drawChildren = true)
//{
// if (RectTransform == null)
// {
// base.Draw(spriteBatch, drawChildren);
// }
// else
// {
// // Custom draw order so that the background is rendered behind the parent.
// if (drawChildren)
// {
// BackgroundFrame?.Draw(spriteBatch);
// }
// base.Draw(spriteBatch, false);
// if (drawChildren)
// {
// InnerFrame?.Draw(spriteBatch);
// Header?.Draw(spriteBatch);
// Text?.Draw(spriteBatch);
// Buttons.ForEach(b => b.Draw(spriteBatch));
// }
// }
//}
public void Close()
{
if (parent != null) parent.RemoveChild(this);
if (Parent != null) Parent.RemoveChild(this);
if (MessageBoxes.Contains(this)) MessageBoxes.Remove(this);
}
@@ -99,5 +165,14 @@ namespace Barotrauma
{
MessageBoxes.Clear();
}
/// <summary>
/// Parent does not matter. It's overridden.
/// </summary>
public void AddButton(RectTransform rectT, string text, GUIButton.OnClickedHandler onClick)
{
rectT.Parent = RectTransform;
Buttons.Add(new GUIButton(rectT, text) { OnClicked = onClick });
}
}
}
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Globalization;
using System.Linq;
@@ -16,8 +15,9 @@ namespace Barotrauma
public delegate void OnValueChangedHandler(GUINumberInput numberInput);
public OnValueChangedHandler OnValueChanged;
private GUITextBox textBox;
private GUIButton plusButton, minusButton;
public GUITextBox TextBox { get; private set; }
public GUIButton PlusButton { get; private set; }
public GUIButton MinusButton { get; private set; }
private NumberType inputType;
public NumberType InputType
@@ -26,12 +26,37 @@ namespace Barotrauma
set
{
inputType = value;
plusButton.Visible = inputType == NumberType.Int;
minusButton.Visible = inputType == NumberType.Int;
PlusButton.Visible = inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue);
MinusButton.Visible = PlusButton.Visible;
}
}
public float? MinValueFloat, MaxValueFloat;
private float? minValueFloat, maxValueFloat;
public float? MinValueFloat
{
get { return minValueFloat; }
set
{
minValueFloat = value;
ClampFloatValue();
PlusButton.Visible = inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue);
MinusButton.Visible = PlusButton.Visible;
}
}
public float? MaxValueFloat
{
get { return maxValueFloat; }
set
{
maxValueFloat = value;
ClampFloatValue();
PlusButton.Visible = inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue);
MinusButton.Visible = PlusButton.Visible;
}
}
private float floatValue;
public float FloatValue
@@ -40,24 +65,27 @@ namespace Barotrauma
set
{
if (value == floatValue) return;
floatValue = value;
if (MinValueFloat != null)
{
floatValue = Math.Max(floatValue, MinValueFloat.Value);
minusButton.Enabled = floatValue > MinValueFloat;
}
if (MaxValueFloat != null)
{
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
plusButton.Enabled = floatValue < MaxValueFloat;
}
textBox.Text = floatValue.ToString("G", CultureInfo.InvariantCulture);
ClampFloatValue();
float newValue = floatValue;
UpdateText();
//UpdateText may remove decimals from the value, force to full accuracy
floatValue = newValue;
OnValueChanged?.Invoke(this);
}
}
private int decimalsToDisplay = 1;
public int DecimalsToDisplay
{
get { return decimalsToDisplay; }
set
{
decimalsToDisplay = value;
UpdateText();
}
}
public int? MinValueInt, MaxValueInt;
private int intValue;
@@ -67,93 +95,145 @@ namespace Barotrauma
set
{
if (value == intValue) return;
intValue = value;
if (MinValueInt != null)
{
intValue = Math.Max(intValue, MinValueInt.Value);
minusButton.Enabled = intValue > MinValueInt;
}
if (MaxValueInt != null)
{
intValue = Math.Min(intValue, MaxValueInt.Value);
plusButton.Enabled = intValue < MaxValueInt;
}
textBox.Text = this.intValue.ToString();
UpdateText();
OnValueChanged?.Invoke(this);
}
}
public GUINumberInput(Rectangle rect, string style, NumberType inputType, GUIComponent parent = null)
: this(rect, style, inputType, Alignment.TopLeft, parent)
public float valueStep;
private float pressedTimer;
private float pressedDelay = 0.5f;
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center) : base(style, rectT)
{
}
int buttonHeight = Rect.Height / 2;
int margin = 2;
Point buttonSize = new Point(buttonHeight - margin, buttonHeight - margin);
TextBox = new GUITextBox(new RectTransform(new Point(Rect.Width, Rect.Height), rectT), textAlignment: textAlignment, style: style)
{
ClampText = false,
// For some reason the caret in the number inputs is dimmer than it should.
// It should not be rendered behind anything, as I first suspected.
// Therefore this hack.
CaretColor = Color.White
};
TextBox.OnTextChanged += TextChanged;
var buttonArea = new GUIFrame(new RectTransform(new Point(buttonSize.X, buttonSize.Y * 2), rectT, Anchor.CenterRight), style: null);
PlusButton = new GUIButton(new RectTransform(buttonSize, buttonArea.RectTransform), "+");
PlusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
PlusButton.OnClicked += (button, data) =>
{
IncreaseValue();
return true;
};
PlusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
IncreaseValue();
}
return true;
};
PlusButton.Visible = inputType == NumberType.Int;
public GUINumberInput(Rectangle rect, string style, NumberType inputType, Alignment alignment, GUIComponent parent = null)
: base(style)
{
this.rect = rect;
this.alignment = alignment;
if (parent != null)
parent.AddChild(this);
textBox = new GUITextBox(Rectangle.Empty, style, this);
textBox.OnTextChanged += TextChanged;
plusButton = new GUIButton(new Rectangle(0, 0, 15, rect.Height / 2), "+", null, Alignment.TopRight, Alignment.Center, style, this);
plusButton.OnClicked += ChangeIntValue;
plusButton.Visible = inputType == NumberType.Int;
minusButton = new GUIButton(new Rectangle(0, 0, 15, rect.Height / 2), "-", null, Alignment.BottomRight, Alignment.Center, style, this);
minusButton.OnClicked += ChangeIntValue;
minusButton.Visible = inputType == NumberType.Int;
MinusButton = new GUIButton(new RectTransform(buttonSize, buttonArea.RectTransform, Anchor.BottomRight), "-");
MinusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
MinusButton.OnClicked += (button, data) =>
{
ReduceValue();
return true;
};
MinusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
ReduceValue();
}
return true;
};
MinusButton.Visible = inputType == NumberType.Int;
if (inputType == NumberType.Int)
{
textBox.Text = "0";
textBox.OnEnterPressed += (txtBox, txt) =>
UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
textBox.Text = IntValue.ToString();
textBox.Deselect();
UpdateText();
TextBox.Deselect();
return true;
};
textBox.OnDeselected += (txtBox, key) =>
{
textBox.Text = IntValue.ToString();
};
TextBox.OnDeselected += (txtBox, key) => UpdateText();
}
else if (inputType == NumberType.Float)
{
textBox.Text = "0.0";
textBox.OnDeselected += (txtBox, key) =>
UpdateText();
TextBox.OnDeselected += (txtBox, key) => UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
textBox.Text = FloatValue.ToString("G", CultureInfo.InvariantCulture);
};
textBox.OnEnterPressed += (txtBox, txt) =>
{
textBox.Text = FloatValue.ToString("G", CultureInfo.InvariantCulture);
textBox.Deselect();
UpdateText();
TextBox.Deselect();
return true;
};
}
InputType = inputType;
switch (InputType)
{
case NumberType.Int:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c)).ToArray());
break;
case NumberType.Float:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());
break;
}
}
private bool ChangeIntValue(GUIButton button, object userData)
{
if (button == plusButton)
{
IntValue++;
}
else
{
IntValue--;
}
return false;
private void ReduceValue()
{
if (inputType == NumberType.Int)
{
IntValue -= valueStep > 0 ? (int)valueStep : 1;
}
else if (maxValueFloat.HasValue && minValueFloat.HasValue)
{
FloatValue -= valueStep > 0 ? valueStep : Round();
}
}
private void IncreaseValue()
{
if (inputType == NumberType.Int)
{
IntValue += valueStep > 0 ? (int)valueStep : 1;
}
else if (inputType == NumberType.Float)
{
FloatValue += valueStep > 0 ? valueStep : Round();
}
}
/// <summary>
/// Calculates one percent between the range as the increment/decrement.
/// This value is rounded so that the bigger it is, the less decimals are used (min 0, max 3).
/// Return value is clamped between 0.1f and 1000.
/// </summary>
private float Round()
{
if (!maxValueFloat.HasValue || !minValueFloat.HasValue) return 0;
float onePercent = MathHelper.Lerp(minValueFloat.Value, maxValueFloat.Value, 0.01f);
float diff = maxValueFloat.Value - minValueFloat.Value;
int decimals = (int)MathHelper.Lerp(3, 0, MathUtils.InverseLerp(10, 1000, diff));
return MathHelper.Clamp((float)Math.Round(onePercent, decimals), 0.1f, 1000);
}
private bool TextChanged(GUITextBox textBox, string text)
@@ -162,50 +242,81 @@ namespace Barotrauma
{
case NumberType.Int:
int newIntValue = IntValue;
if (text == "" || text == "-")
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
IntValue = 0;
textBox.Text = text;
intValue = 0;
}
else if (int.TryParse(text, out newIntValue))
{
IntValue = newIntValue;
}
else
{
textBox.Text = IntValue.ToString();
intValue = newIntValue;
}
ClampIntValue();
break;
case NumberType.Float:
float newFloatValue = FloatValue;
text = new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());
if (text == "" || text == "-")
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
FloatValue = 0;
textBox.Text = text;
floatValue = 0;
}
else if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out newFloatValue))
{
FloatValue = newFloatValue;
textBox.Text = text;
floatValue = newFloatValue;
}
/*else
{
textBox.Text = FloatValue.ToString("G", CultureInfo.InvariantCulture);
}*/
ClampFloatValue();
break;
}
OnValueChanged?.Invoke(this);
return true;
}
public override void Draw(SpriteBatch spriteBatch)
private void ClampFloatValue()
{
if (!Visible) return;
if (MinValueFloat != null)
{
floatValue = Math.Max(floatValue, MinValueFloat.Value);
MinusButton.Enabled = floatValue > MinValueFloat;
}
if (MaxValueFloat != null)
{
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
PlusButton.Enabled = floatValue < MaxValueFloat;
}
}
DrawChildren(spriteBatch);
private void ClampIntValue()
{
if (MinValueInt != null)
{
intValue = Math.Max(intValue, MinValueInt.Value);
MinusButton.Enabled = intValue > MinValueInt;
}
if (MaxValueInt != null)
{
intValue = Math.Min(intValue, MaxValueInt.Value);
PlusButton.Enabled = intValue < MaxValueInt;
}
}
private void UpdateText()
{
switch (InputType)
{
case NumberType.Float:
TextBox.Text = FloatValue.Format(decimalsToDisplay);
break;
case NumberType.Int:
TextBox.Text = IntValue.ToString();
break;
}
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
if (IsPressedTimerRunning)
{
pressedTimer -= deltaTime;
}
}
}
}
@@ -24,102 +24,76 @@ namespace Barotrauma
get { return barSize; }
set
{
float oldBarSize = barSize;
barSize = MathHelper.Clamp(value, 0.0f, 1.0f);
if (barSize != oldBarSize) UpdateRect();
//UpdateRect();
}
}
public GUIProgressBar(Rectangle rect, Color color, float barSize, GUIComponent parent = null)
: this(rect, color, barSize, (Alignment.Left | Alignment.Top), parent)
public GUIProgressBar(RectTransform rectT, float barSize, Color? color = null, string style = "") : base(style, rectT)
{
}
public GUIProgressBar(Rectangle rect, Color color, float barSize, Alignment alignment, GUIComponent parent = null)
: this(rect, color, null, barSize, alignment, parent)
{
}
public GUIProgressBar(Rectangle rect, Color color, string style, float barSize, Alignment alignment, GUIComponent parent = null)
: base(style)
{
this.rect = rect;
this.color = color;
isHorizontal = (rect.Width > rect.Height);
this.alignment = alignment;
if (parent != null)
parent.AddChild(this);
frame = new GUIFrame(new Rectangle(0, 0, 0, 0), null, this);
if (color.HasValue)
{
this.color = color.Value;
}
isHorizontal = (Rect.Width > Rect.Height);
frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(frame, "", this);
slider = new GUIFrame(new Rectangle(0, 0, 0, 0), null);
slider = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(slider, "Slider", this);
this.barSize = barSize;
UpdateRect();
}
/*public override void ApplyStyle(GUIComponentStyle style)
{
if (frame == null) return;
frame.Color = style.Color;
frame.HoverColor = style.HoverColor;
frame.SelectedColor = style.SelectedColor;
Padding = style.Padding;
frame.OutlineColor = style.OutlineColor;
this.style = style;
}*/
private void UpdateRect()
{
slider.Rect = new Rectangle(
(int)(frame.Rect.X + padding.X),
(int)(frame.Rect.Y + padding.Y),
isHorizontal ? (int)((frame.Rect.Width - padding.X - padding.Z) * barSize) : frame.Rect.Width,
isHorizontal ? (int)(frame.Rect.Height - padding.Y - padding.W) : (int)(frame.Rect.Height * barSize));
}
public override void Draw(SpriteBatch spriteBatch)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
if (ProgressGetter != null) BarSize = ProgressGetter();
if (ProgressGetter != null) BarSize = ProgressGetter();
DrawChildren(spriteBatch);
Color currColor = color;
if (state == ComponentState.Selected) currColor = selectedColor;
if (state == ComponentState.Hover) currColor = hoverColor;
if (slider.sprites != null && slider.sprites[state].Count > 0)
Rectangle sliderRect = new Rectangle(
frame.Rect.X,
(int)(frame.Rect.Y + (isHorizontal ? 0 : frame.Rect.Height * (1.0f - barSize))),
isHorizontal ? (int)((frame.Rect.Width) * barSize) : frame.Rect.Width,
isHorizontal ? (int)(frame.Rect.Height) : (int)(frame.Rect.Height * barSize));
frame.Visible = true;
slider.Visible = true;
if (AutoDraw)
{
foreach (UISprite uiSprite in slider.sprites[state])
{
if (uiSprite.Tile)
{
uiSprite.Sprite.DrawTiled(spriteBatch, slider.Rect.Location.ToVector2(), slider.Rect.Size.ToVector2(), color: currColor);
}
else
{
spriteBatch.Draw(uiSprite.Sprite.Texture,
slider.Rect, new Rectangle(
uiSprite.Sprite.SourceRect.X,
uiSprite.Sprite.SourceRect.Y,
(int)(uiSprite.Sprite.SourceRect.Width * (isHorizontal ? barSize : 1.0f)),
(int)(uiSprite.Sprite.SourceRect.Height * (isHorizontal ? 1.0f : barSize))),
currColor);
}
}
frame.DrawAuto(spriteBatch);
}
else
{
frame.DrawManually(spriteBatch);
}
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (BarSize <= 1.0f)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, sliderRect);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
}
Color currColor = GetCurrentColor(state);
slider.Color = currColor;
if (AutoDraw)
{
slider.DrawAuto(spriteBatch);
}
else
{
slider.DrawManually(spriteBatch);
}
//hide the slider, we've already drawn it manually
frame.Visible = false;
slider.Visible = false;
if (BarSize <= 1.0f)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
}
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -10,36 +11,79 @@ namespace Barotrauma
private bool isHorizontal;
private GUIFrame frame;
private GUIButton bar;
public GUIFrame Frame { get; private set; }
public GUIButton Bar { get; private set; }
private float barSize;
private float barScroll;
private float step;
private bool enabled;
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
public OnMovedHandler OnMoved;
public bool IsBooleanSwitch;
public override string ToolTip
{
get { return base.ToolTip; }
set
{
base.ToolTip = value;
Frame.ToolTip = value;
Bar.ToolTip = value;
}
}
private float minValue;
public float MinValue
{
get { return minValue; }
set
{
minValue = MathHelper.Clamp(value, 0.0f, 1.0f);
BarScroll = Math.Max(minValue, barScroll);
}
}
private float maxValue = 1.0f;
public float MaxValue
{
get { return maxValue; }
set
{
maxValue = MathHelper.Clamp(value, 0.0f, 1.0f);
BarScroll = Math.Min(maxValue, barScroll);
}
}
public bool IsHorizontal
{
get { return isHorizontal; }
set
/*set
{
if (isHorizontal == value) return;
isHorizontal = value;
UpdateRect();
}
}*/
}
public bool Enabled
public override bool Enabled
{
get { return enabled; }
set
{
enabled = value;
bar.Enabled = value;
Bar.Enabled = value;
if (!enabled) Bar.Selected = false;
}
}
public Vector4 Padding
{
get
{
if (Frame?.Style == null) return Vector4.Zero;
return Frame.Style.Padding;
}
}
@@ -48,25 +92,27 @@ namespace Barotrauma
get { return step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step); }
set
{
barScroll = MathHelper.Clamp(value, 0.0f, 1.0f);
int newX = bar.Rect.X - frame.Rect.X;
int newY = bar.Rect.Y - frame.Rect.Y;
if (float.IsNaN(value))
{
return;
}
barScroll = MathHelper.Clamp(value, minValue, maxValue);
int newX = Bar.RectTransform.AbsoluteOffset.X;
int newY = Bar.RectTransform.AbsoluteOffset.Y;
float newScroll = step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step);
if (isHorizontal)
{
newX = (int)(frame.Padding.X + newScroll * (frame.Rect.Width - bar.Rect.Width - frame.Padding.X - frame.Padding.Z));
newX = MathHelper.Clamp(newX, (int)frame.Padding.X, frame.Rect.Width - bar.Rect.Width - (int)frame.Padding.Z);
newX = (int)(Padding.X + newScroll * (Frame.Rect.Width - Bar.Rect.Width - Padding.X - Padding.Z));
newX = MathHelper.Clamp(newX, (int)Padding.X, Frame.Rect.Width - Bar.Rect.Width - (int)Padding.Z);
}
else
{
newY = (int)(frame.Padding.Y + newScroll * (frame.Rect.Height - bar.Rect.Height - frame.Padding.Y - frame.Padding.W));
newY = MathHelper.Clamp(newY, (int)frame.Padding.Y, frame.Rect.Height - bar.Rect.Height - (int)frame.Padding.W);
newY = (int)(Padding.Y + newScroll * (Frame.Rect.Height - Bar.Rect.Height - Padding.Y - Padding.W));
newY = MathHelper.Clamp(newY, (int)Padding.Y, Frame.Rect.Height - Bar.Rect.Height - (int)Padding.W);
}
bar.Rect = new Rectangle(newX + frame.Rect.X, newY + frame.Rect.Y, bar.Rect.Width, bar.Rect.Height);
Bar.RectTransform.AbsoluteOffset = new Point(newX, newY);
}
}
@@ -87,79 +133,39 @@ namespace Barotrauma
get { return barSize; }
set
{
float oldBarSize = barSize;
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
if (barSize != oldBarSize) UpdateRect();
UpdateRect();
}
}
public GUIScrollBar(Rectangle rect, string style, float barSize, GUIComponent parent = null)
: this(rect, null, barSize, style, parent)
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
{
}
public GUIScrollBar(Rectangle rect, Color? color, float barSize, string style = "", GUIComponent parent = null)
: this(rect, color, barSize, Alignment.TopLeft, style, parent)
{
}
public GUIScrollBar(Rectangle rect, Color? color, float barSize, Alignment alignment, string style = "", GUIComponent parent = null)
: base(style)
{
this.rect = rect;
//GetDimensions(parent);
this.alignment = alignment;
if (parent != null)
parent.AddChild(this);
isHorizontal = (rect.Width > rect.Height);
frame = new GUIFrame(new Rectangle(0,0,0,0), style, this);
GUI.Style.Apply(frame, isHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
this.isHorizontal = isHorizontal ?? (Rect.Width > Rect.Height);
Frame = new GUIFrame(new RectTransform(Vector2.One, rectT));
GUI.Style.Apply(Frame, IsHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
this.barSize = barSize;
bar = new GUIButton(new Rectangle(0, 0, 0, 0), "", color, "", this);
GUI.Style.Apply(bar, isHorizontal ? "GUIButtonHorizontal" : "GUIButtoneVertical", this);
bar.OnPressed = SelectBar;
Bar = new GUIButton(new RectTransform(Vector2.One, rectT, IsHorizontal ? Anchor.CenterLeft : Anchor.TopCenter), color: color);
GUI.Style.Apply(Bar, IsHorizontal ? "GUIButtonHorizontal" : "GUIButtonVertical", this);
Bar.OnPressed = SelectBar;
enabled = true;
UpdateRect();
BarScroll = 0.0f;
rectT.SizeChanged += UpdateRect;
rectT.ScaleChanged += UpdateRect;
Bar.RectTransform.SizeChanged += () => { BarScroll = barScroll; };
}
private void UpdateRect()
{
float width = frame.Rect.Width - frame.Padding.X - frame.Padding.Z;
float height = frame.Rect.Height - frame.Padding.Y - frame.Padding.W;
bar.Rect = new Rectangle(
bar.Rect.X,
bar.Rect.Y,
isHorizontal ? (int)(width * barSize) : (int)width,
isHorizontal ? (int)height : (int)(height * barSize));
ClampRect();
foreach (GUIComponent child in bar.children)
{
child.Rect = bar.Rect;
}
Vector4 padding = Frame.Style.Padding;
var newSize = new Point((int)(Rect.Size.X - padding.X - padding.Z), (int)(Rect.Size.Y - padding.Y - padding.W));
newSize = IsHorizontal ? newSize.Multiply(new Vector2(BarSize, 1)) : newSize.Multiply(new Vector2(1, BarSize));
Bar.RectTransform.Resize(newSize);
BarScroll = barScroll;
}
private void ClampRect()
{
bar.Rect = new Rectangle(
(int)MathHelper.Clamp(bar.Rect.X, frame.Rect.X + frame.Padding.X, frame.Rect.Right - bar.Rect.Width - frame.Padding.X - frame.Padding.Z),
(int)MathHelper.Clamp(bar.Rect.Y, frame.Rect.Y + frame.Padding.Y, frame.Rect.Bottom - bar.Rect.Height - frame.Padding.Y - frame.Padding.W),
bar.Rect.Width,
bar.Rect.Height);
}
public override void Update(float deltaTime)
protected override void Update(float deltaTime)
{
if (!Visible) return;
@@ -167,33 +173,42 @@ namespace Barotrauma
if (!enabled) return;
if (MouseOn == frame)
if (IsBooleanSwitch &&
(!PlayerInput.LeftButtonHeld() || (GUI.MouseOn != this && !IsParentOf(GUI.MouseOn))))
{
int dir = Math.Sign(barScroll - (minValue + maxValue) / 2.0f);
if (dir == 0) dir = 1;
if ((barScroll <= maxValue && dir > 0) ||
(barScroll > minValue && dir < 0))
{
BarScroll += dir * 0.1f;
}
}
if (draggingBar == this)
{
if (!PlayerInput.LeftButtonHeld()) draggingBar = null;
if ((isHorizontal && PlayerInput.MousePosition.X > Rect.X && PlayerInput.MousePosition.X < Rect.Right) ||
(!isHorizontal && PlayerInput.MousePosition.Y > Rect.Y && PlayerInput.MousePosition.Y < Rect.Bottom))
{
MoveButton(PlayerInput.MouseSpeed);
}
}
else if (GUI.MouseOn == Frame)
{
if (PlayerInput.LeftButtonClicked())
{
MoveButton(new Vector2(
Math.Sign(PlayerInput.MousePosition.X - bar.Rect.Center.X) * bar.Rect.Width,
Math.Sign(PlayerInput.MousePosition.Y - bar.Rect.Center.Y) * bar.Rect.Height));
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Bar.Rect.Width,
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Bar.Rect.Height));
}
}
if (draggingBar == this)
{
if (!PlayerInput.LeftButtonHeld()) draggingBar = null;
MoveButton(PlayerInput.MouseSpeed);
}
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
DrawChildren(spriteBatch);
}
private bool SelectBar()
{
if (!enabled) return false;
// This doesn't work
if (barSize == 1.0f) return false;
draggingBar = this;
@@ -202,23 +217,23 @@ namespace Barotrauma
}
private void MoveButton(Vector2 moveAmount)
public void MoveButton(Vector2 moveAmount)
{
float newScroll = barScroll;
if (isHorizontal)
{
moveAmount.Y = 0.0f;
barScroll += moveAmount.X / (frame.Rect.Width - bar.Rect.Width - frame.Padding.X - frame.Padding.Z);
newScroll += moveAmount.X / (Frame.Rect.Width - Bar.Rect.Width - Padding.X - Padding.Z);
}
else
{
moveAmount.X = 0.0f;
barScroll += moveAmount.Y / (frame.Rect.Height - bar.Rect.Height - frame.Padding.Y - frame.Padding.W);
newScroll += moveAmount.Y / (Frame.Rect.Height - Bar.Rect.Height - Padding.Y - Padding.W);
}
BarScroll = barScroll;
BarScroll = newScroll;
if (moveAmount != Vector2.Zero && OnMoved != null) OnMoved(this, BarScroll);
}
}
}
@@ -1,4 +1,5 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -7,8 +8,18 @@ namespace Barotrauma
public class GUIStyle
{
private Dictionary<string, GUIComponentStyle> componentStyles;
public GUIStyle(string file)
public ScalableFont Font { get; private set; }
public ScalableFont SmallFont { get; private set; }
public ScalableFont LargeFont { get; private set; }
public Sprite CursorSprite { get; private set; }
public UISprite UIGlow { get; private set; }
public SpriteSheet FocusIndicator { get; private set; }
public GUIStyle(string file, GraphicsDevice graphicsDevice)
{
componentStyles = new Dictionary<string, GUIComponentStyle>();
@@ -16,7 +27,7 @@ namespace Barotrauma
try
{
ToolBox.IsProperFilenameCase(file);
doc = XDocument.Load(file);
doc = XDocument.Load(file, LoadOptions.SetBaseUri);
}
catch (Exception e)
{
@@ -26,17 +37,45 @@ namespace Barotrauma
foreach (XElement subElement in doc.Root.Elements())
{
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement);
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "font":
Font = new ScalableFont(subElement, graphicsDevice);
break;
case "smallfont":
SmallFont = new ScalableFont(subElement, graphicsDevice);
break;
case "largefont":
LargeFont = new ScalableFont(subElement, graphicsDevice);
break;
case "cursor":
CursorSprite = new Sprite(subElement);
break;
case "uiglow":
UIGlow = new UISprite(subElement);
break;
case "focusindicator":
FocusIndicator = new SpriteSheet(subElement);
break;
default:
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement);
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
break;
}
}
}
public GUIComponentStyle GetComponentStyle(string name)
{
componentStyles.TryGetValue(name.ToLowerInvariant(), out GUIComponentStyle style);
return style;
}
public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
{
GUIComponentStyle componentStyle = null;
if (parent != null)
{
GUIComponentStyle parentStyle = parent.Style;
if (parent.Style == null)
@@ -49,8 +88,7 @@ namespace Barotrauma
return;
}
}
string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName;
parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle);
}
@@ -9,13 +9,11 @@ namespace Barotrauma
protected Alignment textAlignment;
private float textScale;
private float textScale = 1;
protected Vector2 textPos;
protected Vector2 origin;
protected Vector2 caretPos;
protected Color textColor;
private string wrappedText;
@@ -30,7 +28,10 @@ namespace Barotrauma
private float textDepth;
public override Vector4 Padding
public Vector2 TextOffset { get; set; }
private Vector4 padding;
public Vector4 Padding
{
get { return padding; }
set
@@ -40,6 +41,20 @@ namespace Barotrauma
}
}
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
if (base.Font == value) return;
base.Font = value;
SetTextPos();
}
}
public string Text
{
get { return text; }
@@ -47,6 +62,9 @@ namespace Barotrauma
{
if (Text == value) return;
//reset scale, it gets recalculated in SetTextPos
if (autoScale) textScale = 1.0f;
text = value;
wrappedText = value;
SetTextPos();
@@ -57,35 +75,7 @@ namespace Barotrauma
{
get { return wrappedText; }
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
if (base.Rect == value) return;
foreach (GUIComponent child in children)
{
child.Rect = new Rectangle(child.Rect.X + value.X - rect.X, child.Rect.Y + value.Y - rect.Y, child.Rect.Width, child.Rect.Height);
}
Point moveAmount = value.Location - rect.Location;
rect = value;
if (value.Width != rect.Width || value.Height != rect.Height)
{
SetTextPos();
}
else if (moveAmount != Point.Zero)
{
caretPos += moveAmount.ToVector2();
}
}
}
public float TextDepth
{
get { return textDepth; }
@@ -110,142 +100,146 @@ namespace Barotrauma
}
}
private bool autoScale;
/// <summary>
/// When enabled, the text is automatically scaled down to fit the textblock.
/// </summary>
public bool AutoScale
{
get { return autoScale; }
set
{
if (autoScale == value) return;
autoScale = value;
if (autoScale)
{
SetTextPos();
}
}
}
public Vector2 Origin
{
get { return origin; }
}
public Vector2 TextSize
{
get;
private set;
}
public Color TextColor
{
get { return textColor; }
set { textColor = value; }
}
public Vector2 CaretPos
public Alignment TextAlignment
{
get { return caretPos; }
get { return textAlignment; }
set
{
if (textAlignment == value) return;
textAlignment = value;
SetTextPos();
}
}
/// <summary>
/// This is the new constructor.
/// If the rectT height is set 0, the height is calculated from the text.
/// </summary>
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
: base(style, rectT)
{
if (color.HasValue)
{
this.color = color.Value;
}
if (textColor.HasValue)
{
this.textColor = textColor.Value;
}
this.Font = font ?? GUI.Font;
this.textAlignment = textAlignment;
this.Wrap = wrap;
this.Text = text ?? "";
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{
CalculateHeightFromText();
}
SetTextPos();
RectTransform.ScaleChanged += SetTextPos;
RectTransform.SizeChanged += SetTextPos;
}
public void CalculateHeightFromText()
{
if (wrappedText == null) { return; }
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y));
}
public GUITextBlock(Rectangle rect, string text, string style, GUIComponent parent, ScalableFont font)
: this(rect, text, style, Alignment.TopLeft, Alignment.TopLeft, parent, false, font)
{
}
public GUITextBlock(Rectangle rect, string text, string style, GUIComponent parent = null, bool wrap = false)
: this(rect, text, style, Alignment.TopLeft, Alignment.TopLeft, parent, wrap)
{
}
public GUITextBlock(Rectangle rect, string text, Color? color, Color? textColor, Alignment textAlignment = Alignment.Left, string style = null, GUIComponent parent = null, bool wrap = false)
: this(rect, text,color, textColor, Alignment.TopLeft, textAlignment, style, parent, wrap)
{
}
protected override void UpdateDimensions(GUIComponent parent = null)
{
base.UpdateDimensions(parent);
SetTextPos();
}
public override void ApplyStyle(GUIComponentStyle style)
{
if (style == null) return;
base.ApplyStyle(style);
padding = style.Padding;
textColor = style.textColor;
}
public GUITextBlock(Rectangle rect, string text, Color? color, Color? textColor, Alignment alignment, Alignment textAlignment = Alignment.Left, string style = null, GUIComponent parent = null, bool wrap = false, ScalableFont font = null)
: this (rect, text, style, alignment, textAlignment, parent, wrap, font)
{
if (color != null) this.color = (Color)color;
if (textColor != null) this.textColor = (Color)textColor;
}
public GUITextBlock(Rectangle rect, string text, string style, Alignment alignment = Alignment.TopLeft, Alignment textAlignment = Alignment.TopLeft, GUIComponent parent = null, bool wrap = false, ScalableFont font = null)
: base(style)
{
this.Font = font == null ? GUI.Font : font;
this.rect = rect;
this.text = text;
this.alignment = alignment;
this.padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
this.textAlignment = textAlignment;
if (parent != null)
parent.AddChild(this);
this.Wrap = wrap;
SetTextPos();
TextScale = 1.0f;
if (rect.Height == 0 && !string.IsNullOrEmpty(Text))
{
this.rect.Height = (int)Font.MeasureString(wrappedText).Y;
}
}
public void SetTextPos()
{
if (text == null) return;
var rect = Rect;
overflowClipActive = false;
wrappedText = text;
Vector2 size = MeasureText(text);
TextSize = MeasureText(text);
if (Wrap && rect.Width > 0)
{
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale);
size = MeasureText(wrappedText);
TextSize = MeasureText(wrappedText);
}
else if (OverflowClip)
{
overflowClipActive = size.X > rect.Width;
overflowClipActive = TextSize.X > rect.Width - padding.X - padding.Z;
}
if (autoScale && textScale > 0.1f &&
(TextSize.X * textScale > rect.Width - padding.X - padding.Z || TextSize.Y * textScale > rect.Height - padding.Y - padding.W))
{
TextScale -= 0.05f;
return;
}
textPos = new Vector2(rect.Width / 2.0f, rect.Height / 2.0f);
origin = size * 0.5f;
origin = TextSize / textScale * 0.5f;
if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive)
origin.X += (rect.Width / 2.0f - padding.X) - size.X / 2;
origin.X += (rect.Width / 2.0f - TextSize.X / 2) / textScale - padding.X;
if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive)
origin.X -= (rect.Width / 2.0f - padding.Z) - size.X / 2;
origin.X -= (rect.Width / 2.0f - TextSize.X / 2) / textScale - padding.Z;
if (textAlignment.HasFlag(Alignment.Top))
origin.Y += (rect.Height / 2.0f - padding.Y) - size.Y / 2;
origin.Y += (rect.Height / 2.0f - TextSize.Y / 2) / textScale - padding.Y;
if (textAlignment.HasFlag(Alignment.Bottom))
origin.Y -= (rect.Height / 2.0f - padding.W) - size.Y / 2;
origin.Y -= (rect.Height / 2.0f - TextSize.Y / 2) / textScale - padding.W;
origin.X = (int)origin.X;
origin.Y = (int)origin.Y;
origin.X = (int)(origin.X);
origin.Y = (int)(origin.Y);
textPos.X = (int)textPos.X;
textPos.Y = (int)textPos.Y;
if (wrappedText.Contains("\n"))
{
string[] lines = wrappedText.Split('\n');
Vector2 lastLineSize = MeasureText(lines[lines.Length-1]);
caretPos = new Vector2(rect.X + lastLineSize.X, rect.Y + size.Y - lastLineSize.Y) + textPos - origin;
}
else
{
caretPos = new Vector2(rect.X + size.X, rect.Y) + textPos - origin;
}
}
private Vector2 MeasureText(string text)
@@ -268,37 +262,32 @@ namespace Barotrauma
textColor = new Color(textColor.R, textColor.G, textColor.B, a);
}
public override void Draw(SpriteBatch spriteBatch)
{
Draw(spriteBatch, Vector2.Zero);
}
public void Draw(SpriteBatch spriteBatch, Vector2 offset)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Color currColor = color;
if (state == ComponentState.Hover) currColor = hoverColor;
if (state == ComponentState.Selected) currColor = selectedColor;
Color currColor = GetCurrentColor(state);
var rect = Rect;
Rectangle drawRect = rect;
if (offset != Vector2.Zero) drawRect.Location += offset.ToPoint();
base.Draw(spriteBatch);
if (TextGetter != null) Text = TextGetter();
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
spriteBatch.GraphicsDevice.ScissorRectangle = rect;
spriteBatch.End();
Rectangle scissorRect = new Rectangle(rect.X + (int)padding.X, rect.Y, rect.Width - (int)padding.X - (int)padding.Z, rect.Height);
spriteBatch.GraphicsDevice.ScissorRectangle = scissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
}
if (!string.IsNullOrEmpty(text))
{
Font.DrawString(spriteBatch,
Wrap ? wrappedText : text,
new Vector2(rect.X, rect.Y) + textPos + offset,
rect.Location.ToVector2() + textPos + TextOffset,
textColor * (textColor.A / 255.0f),
0.0f, origin, TextScale,
SpriteEffects.None, textDepth);
@@ -306,11 +295,11 @@ namespace Barotrauma
if (overflowClipActive)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
}
DrawChildren(spriteBatch);
if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
}
}
@@ -2,6 +2,9 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -15,8 +18,11 @@ namespace Barotrauma
bool caretVisible;
float caretTimer;
GUITextBlock textBlock;
private GUIFrame frame;
private GUITextBlock textBlock;
public Func<string, string> textFilterFunction;
public delegate bool OnEnterHandler(GUITextBox textBox, string text);
public OnEnterHandler OnEnterPressed;
@@ -24,22 +30,73 @@ namespace Barotrauma
public event TextBoxEvent OnKeyHit;
public delegate bool OnTextChangedHandler(GUITextBox textBox, string text);
public OnTextChangedHandler OnTextChanged;
/// <summary>
/// Don't set the Text property on delegates that register to this event, because modifying the Text will launch this event -> stack overflow.
/// If the event launches, the text should already be up to date!
/// </summary>
public event OnTextChangedHandler OnTextChanged;
public bool CaretEnabled;
public bool CaretEnabled { get; set; }
public Color? CaretColor { get; set; }
private int? maxTextLength;
private int _caretIndex;
private int CaretIndex
{
get { return _caretIndex; }
set
{
previousCaretIndex = _caretIndex;
_caretIndex = value;
caretPosDirty = true;
}
}
private bool caretPosDirty;
protected Vector2 caretPos;
public Vector2 CaretScreenPos => Rect.Location.ToVector2() + caretPos;
private bool isSelecting;
private string selectedText = string.Empty;
private string clipboard = string.Empty;
private int selectedCharacters;
private int selectionStartIndex;
private int selectionEndIndex;
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
private int previousCaretIndex;
private Vector2 selectionStartPos;
private Vector2 selectionEndPos;
private Vector2 selectionRectSize;
private readonly Memento<string> memento = new Memento<string>();
public GUITextBlock.TextGetterHandler TextGetter
{
get { return textBlock.TextGetter; }
set { textBlock.TextGetter = value; }
}
public bool Selected
{
get;
set;
}
public bool Wrap
{
get { return textBlock.Wrap; }
set { textBlock.Wrap = value; }
set
{
textBlock.Wrap = value;
}
}
//should the text be limited to the size of the box
//ignored when MaxTextLength is set or text wrapping is enabled
public bool ClampText
{
get;
set;
}
public int? MaxTextLength
@@ -51,11 +108,18 @@ namespace Barotrauma
maxTextLength = value;
}
}
public bool Enabled
public override bool Enabled
{
get;
set;
get { return enabled; }
set
{
enabled = value;
if (!enabled && Selected)
{
Deselect();
}
}
}
public override string ToolTip
@@ -110,23 +174,9 @@ namespace Barotrauma
}
}
public override Rectangle Rect
{
get
{
return base.Rect;
}
set
{
base.Rect = value;
// TODO: should this be defined in the stylesheet?
public Color SelectionColor { get; set; } = Color.White * 0.25f;
if (textBlock != null)
{
textBlock.Rect = value;
}
}
}
public string Text
{
get
@@ -135,132 +185,229 @@ namespace Barotrauma
}
set
{
if (textBlock.Text == value) return;
textBlock.Text = value;
if (textBlock.Text == null) textBlock.Text = "";
if (textBlock.Text != "")
{
if (!Wrap)
{
if (maxTextLength != null)
{
if (Text.Length > maxTextLength)
{
Text = textBlock.Text.Substring(0, (int)maxTextLength);
}
}
else if (Font.MeasureString(textBlock.Text).X > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
{
Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1);
}
}
}
SetText(value, store: false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
}
public GUITextBox(Rectangle rect, string style = null, GUIComponent parent = null)
: this(rect, null, null, Alignment.Left, Alignment.Left, style, parent)
public string WrappedText
{
get { return textBlock.WrappedText; }
}
public GUITextBox(Rectangle rect, Alignment alignment = Alignment.Left, string style = null, GUIComponent parent = null)
: this(rect, null, null, alignment, Alignment.Left, style, parent)
{
}
public GUITextBox(Rectangle rect, Color? color, Color? textColor, Alignment alignment, Alignment textAlignment = Alignment.CenterLeft, string style = null, GUIComponent parent = null)
: base(style)
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
: base(style, rectT)
{
Enabled = true;
this.rect = rect;
if (color != null) this.color = (Color)color;
this.alignment = alignment;
if (parent != null)
parent.AddChild(this);
textBlock = new GUITextBlock(new Rectangle(0,0,0,0), "", color, textColor, textAlignment, style, this);
Font = GUI.Font;
GUI.Style.Apply(textBlock, style == "" ? "GUITextBox" : style);
textBlock.Padding = new Vector4(3.0f, 0.0f, 3.0f, 0.0f);
this.color = color ?? Color.White;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.Center), text, textColor, font, textAlignment, wrap);
GUI.Style.Apply(textBlock, "", this);
CaretEnabled = true;
caretPosDirty = true;
Font = textBlock.Font;
rectT.SizeChanged += () => { caretPosDirty = true; };
rectT.ScaleChanged += () => { caretPosDirty = true; };
}
private bool SetText(string text, bool store = true)
{
if (textFilterFunction != null)
{
text = textFilterFunction(text);
}
if (textBlock.Text == text) { return false; }
textBlock.Text = text;
if (textBlock.Text == null) textBlock.Text = "";
if (textBlock.Text != "" && !Wrap)
{
if (maxTextLength != null)
{
if (textBlock.Text.Length > maxTextLength)
{
textBlock.Text = textBlock.Text.Substring(0, (int)maxTextLength);
}
}
else if (ClampText && Font.MeasureString(textBlock.Text).X > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
{
textBlock.Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1);
}
}
if (store)
{
memento.Store(textBlock.Text);
}
return true;
}
private void CalculateCaretPos()
{
if (textBlock.WrappedText.Contains("\n"))
{
string[] lines = textBlock.WrappedText.Split('\n');
int totalIndex = 0;
for (int i = 0; i < lines.Length; i++)
{
int currentLineLength = lines[i].Length;
totalIndex += currentLineLength;
// The caret is on this line
if (CaretIndex < totalIndex || totalIndex == textBlock.Text.Length)
{
int diff = totalIndex - CaretIndex;
int index = currentLineLength - diff;
Vector2 lineTextSize = Font.MeasureString(lines[i].Substring(0, index));
Vector2 lastLineSize = Font.MeasureString(lines[i]);
float totalTextHeight = Font.MeasureString(textBlock.WrappedText.Substring(0, totalIndex)).Y;
caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin;
break;
}
}
}
else
{
Vector2 textSize = Font.MeasureString(textBlock.Text.Substring(0, CaretIndex));
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin;
}
caretPosDirty = false;
}
protected List<Tuple<Vector2, int>> GetAllPositions()
{
var positions = new List<Tuple<Vector2, int>>();
if (textBlock.WrappedText.Contains("\n"))
{
string[] lines = textBlock.WrappedText.Split('\n');
int index = 0;
int totalIndex = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
totalIndex += line.Length;
float totalTextHeight = Font.MeasureString(textBlock.WrappedText.Substring(0, totalIndex)).Y;
for (int j = 0; j <= line.Length; j++)
{
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j));
Vector2 indexPos = new Vector2(lineTextSize.X + textBlock.Padding.X, totalTextHeight + textBlock.Padding.Y);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(textBlock.Rect.Location.ToVector2() + indexPos, index + j));
}
index = totalIndex;
}
}
else
{
for (int i = 0; i <= textBlock.Text.Length; i++)
{
Vector2 textSize = Font.MeasureString(textBlock.Text.Substring(0, i));
Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y);
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(textBlock.Rect.Location.ToVector2() + indexPos, i));
}
}
return positions;
}
public int GetCaretIndexFromScreenPos(Vector2 pos)
{
var positions = GetAllPositions().OrderBy(p => Vector2.DistanceSquared(p.Item1, pos));
var posIndex = positions.FirstOrDefault();
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return posIndex != null ? posIndex.Item2 : textBlock.Text.Length;
}
public void Select()
{
if (memento.Current == null)
{
memento.Store(Text);
}
Selected = true;
keyboardDispatcher.Subscriber = this;
//if (Clicked != null) Clicked(this);
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
ClearSelection();
GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None);
}
public void Deselect()
{
memento.Clear();
Selected = false;
if (keyboardDispatcher.Subscriber == this) keyboardDispatcher.Subscriber = null;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
OnDeselected?.Invoke(this, Keys.None);
}
public override void Flash(Color? color = null)
public override void Flash(Color? color = null, float flashDuration = 1.5f)
{
textBlock.Flash(color);
textBlock.Flash(color, flashDuration);
}
public override void Update(float deltaTime)
protected override void Update(float deltaTime)
{
if (!Visible) return;
if (flashTimer > 0.0f) flashTimer -= deltaTime;
if (!Enabled) return;
if (MouseRect.Contains(PlayerInput.MousePosition) && Enabled &&
(MouseOn == null || MouseOn == this || IsParentOf(MouseOn) || MouseOn.IsParentOf(this)))
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || GUI.IsMouseOn(this)))
{
state = ComponentState.Hover;
if (PlayerInput.LeftButtonClicked())
if (PlayerInput.LeftButtonDown())
{
Select();
OnSelected?.Invoke(this, Keys.None);
}
else
{
isSelecting = PlayerInput.LeftButtonHeld();
}
if (PlayerInput.DoubleClicked())
{
SelectAll();
}
if (isSelecting)
{
if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0))
{
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
CalculateCaretPos();
CalculateSelection();
}
}
}
else
{
isSelecting = false;
state = ComponentState.None;
}
if (!isSelecting)
{
isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
}
if (CaretEnabled)
{
caretTimer += deltaTime;
caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
if (caretVisible && caretPosDirty)
{
CalculateCaretPos();
}
}
if (keyboardDispatcher.Subscriber == this)
if (GUI.KeyboardDispatcher.Subscriber == this)
{
state = ComponentState.Selected;
Character.DisableControls = true;
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
{
string input = Text;
Text = "";
OnEnterPressed(this, input);
OnEnterPressed(this, Text);
}
#if LINUX
else if (PlayerInput.KeyHit(Keys.Back) && Text.Length>0)
{
Text = Text.Substring(0, Text.Length-1);
}
#endif
}
else if (Selected)
{
@@ -268,40 +415,133 @@ namespace Barotrauma
}
textBlock.State = state;
textBlock.Update(deltaTime);
}
public override void Draw(SpriteBatch spriteBatch)
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
DrawChildren(spriteBatch);
if (!CaretEnabled) return;
Vector2 caretPos = textBlock.CaretPos;
if (caretVisible && Selected)
base.Draw(spriteBatch);
// Frame is not used in the old system.
frame?.DrawManually(spriteBatch);
textBlock.DrawManually(spriteBatch);
if (Selected)
{
GUI.DrawLine(spriteBatch,
new Vector2((int)caretPos.X + 2, caretPos.Y + 3),
new Vector2((int)caretPos.X + 2, caretPos.Y + Font.MeasureString("I").Y - 3),
textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
if (caretVisible )
{
GUI.DrawLine(spriteBatch,
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + 3),
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + Font.MeasureString("I").Y - 3),
CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
}
if (selectedCharacters > 0)
{
DrawSelectionRect(spriteBatch);
}
//GUI.DrawString(spriteBatch, new Vector2(100, 0), selectedCharacters.ToString(), Color.LightBlue, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 20), selectionStartIndex.ToString(), Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(140, 20), selectionEndIndex.ToString(), Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 40), selectedText.ToString(), Color.Yellow, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 60), $"caret index: {CaretIndex.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 100), $"caret screen pos: {CaretScreenPos.ToString()}", Color.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 120), $"text start pos: {(textBlock.TextPos - textBlock.Origin).ToString()}", Color.White, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 140), $"cursor pos: {PlayerInput.MousePosition.ToString()}", Color.White, Color.Black);
}
}
private void DrawSelectionRect(SpriteBatch spriteBatch)
{
if (textBlock.WrappedText.Contains("\n"))
{
// Multiline selection
string[] lines = textBlock.WrappedText.Split('\n');
int totalIndex = 0;
int previousCharacters = 0;
Vector2 offset = textBlock.TextPos - textBlock.Origin;
for (int i = 0; i < lines.Length; i++)
{
string currentLine = lines[i];
int currentLineLength = currentLine.Length;
totalIndex += currentLineLength;
bool containsSelection = IsLeftToRight
? selectionStartIndex < totalIndex && selectionEndIndex > previousCharacters
: selectionEndIndex < totalIndex && selectionStartIndex > previousCharacters;
if (containsSelection)
{
Vector2 currentLineSize = Font.MeasureString(currentLine);
if ((IsLeftToRight && selectionStartIndex < previousCharacters && selectionEndIndex > totalIndex)
|| !IsLeftToRight && selectionEndIndex < previousCharacters && selectionStartIndex > totalIndex)
{
// select the whole line
Vector2 topLeft = offset + new Vector2(0, currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, currentLineSize, SelectionColor, isFilled: true);
}
else
{
if (IsLeftToRight)
{
bool selectFromTheBeginning = selectionStartIndex <= previousCharacters;
int startIndex = selectFromTheBeginning ? 0 : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(endIndex - startIndex, currentLineLength - startIndex);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(startIndex, characters));
Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X, offset.Y + currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
}
else
{
bool selectFromTheBeginning = selectionStartIndex >= totalIndex;
bool selectFromTheStart = selectionEndIndex <= previousCharacters;
int startIndex = selectFromTheBeginning ? currentLineLength : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = selectFromTheStart ? 0 : Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(Math.Abs(endIndex - startIndex), currentLineLength);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(endIndex, characters));
Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X + currentLineSize.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
}
}
}
previousCharacters = totalIndex;
}
}
else
{
// Single line selection
Vector2 topLeft = IsLeftToRight ? selectionStartPos : selectionEndPos;
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectionRectSize, SelectionColor, isFilled: true);
}
}
public void ReceiveTextInput(char inputChar)
{
Text = Text + inputChar;
if (OnTextChanged!=null) OnTextChanged(this, Text);
if (selectedCharacters > 0)
{
RemoveSelectedText();
}
if (SetText(Text.Insert(CaretIndex, inputChar.ToString())))
{
CaretIndex = Math.Min(Text.Length, CaretIndex + 1);
OnTextChanged?.Invoke(this, Text);
}
}
public void ReceiveTextInput(string text)
public void ReceiveTextInput(string input)
{
Text = Text + text;
if (OnTextChanged != null) OnTextChanged(this, Text);
if (selectedCharacters > 0)
{
RemoveSelectedText();
}
if (SetText(Text.Insert(CaretIndex, input)))
{
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
OnTextChanged?.Invoke(this, Text);
}
}
public void ReceiveCommandInput(char command)
{
if (Text == null) Text = "";
@@ -309,24 +549,260 @@ namespace Barotrauma
switch (command)
{
case '\b': //backspace
if (Text.Length > 0) Text = Text.Substring(0, Text.Length - 1);
if (OnTextChanged != null) OnTextChanged(this, Text);
if (selectedCharacters > 0)
{
RemoveSelectedText();
}
else if (Text.Length > 0 && CaretIndex > 0)
{
CaretIndex--;
SetText(Text.Remove(CaretIndex, 1));
CalculateCaretPos();
ClearSelection();
}
OnTextChanged?.Invoke(this, Text);
break;
case (char)0x3: // ctrl-c
CopySelectedText();
break;
case (char)0x16: // ctrl-v
string text = GetCopiedText();
RemoveSelectedText();
if (SetText(Text.Insert(CaretIndex, text)))
{
CaretIndex = Math.Min(Text.Length, CaretIndex + text.Length);
OnTextChanged?.Invoke(this, Text);
}
break;
case (char)0x18: // ctrl-x
CopySelectedText();
RemoveSelectedText();
break;
case (char)0x1: // ctrl-a
SelectAll();
break;
case (char)0x1A: // ctrl-z
text = memento.Undo();
if (text != Text)
{
SetText(text, false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
break;
case (char)0x12: // ctrl-r
text = memento.Redo();
if (text != Text)
{
SetText(text, false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
break;
}
}
public void ReceiveSpecialInput(Keys key)
{
if (OnKeyHit != null) OnKeyHit(this, key);
switch (key)
{
case Keys.Left:
if (isSelecting)
{
InitSelectionStart();
}
CaretIndex = Math.Max(CaretIndex - 1, 0);
caretTimer = 0;
HandleSelection();
break;
case Keys.Right:
if (isSelecting)
{
InitSelectionStart();
}
CaretIndex = Math.Min(CaretIndex + 1, Text.Length);
caretTimer = 0;
HandleSelection();
break;
case Keys.Up:
if (isSelecting)
{
InitSelectionStart();
}
float lineHeight = Font.MeasureString(Text).Y;
int newIndex = GetCaretIndexFromScreenPos(new Vector2(CaretScreenPos.X, CaretScreenPos.Y - lineHeight / 2));
CaretIndex = newIndex != CaretIndex ? newIndex : 0;
caretTimer = 0;
HandleSelection();
break;
case Keys.Down:
if (isSelecting)
{
InitSelectionStart();
}
lineHeight = Font.MeasureString(Text).Y;
newIndex = GetCaretIndexFromScreenPos(new Vector2(CaretScreenPos.X, CaretScreenPos.Y + lineHeight * 2));
CaretIndex = newIndex != CaretIndex ? newIndex : Text.Length;
caretTimer = 0;
HandleSelection();
break;
case Keys.Delete:
if (selectedCharacters > 0)
{
RemoveSelectedText();
}
else if (Text.Length > 0 && CaretIndex < Text.Length)
{
SetText(Text.Remove(CaretIndex, 1));
OnTextChanged?.Invoke(this, Text);
caretPosDirty = true;
}
break;
case Keys.Tab:
// Select the next text box.
var editor = RectTransform.GetParents().Select(p => p.GUIComponent as SerializableEntityEditor).FirstOrDefault(e => e != null);
if (editor == null) { break; }
var allTextBoxes = GetAndSortTextBoxes(editor).ToList();
if (allTextBoxes.Any())
{
int currentIndex = allTextBoxes.IndexOf(this);
int nextIndex = Math.Min(allTextBoxes.Count - 1, currentIndex + 1);
var next = allTextBoxes[nextIndex];
if (next != this)
{
next.Select();
next.Flash(Color.White * 0.5f, 0.5f);
}
else
{
// Select the first text box in the next editor that has text boxes.
var listBox = RectTransform.GetParents().Select(p => p.GUIComponent as GUIListBox).FirstOrDefault(lb => lb != null);
if (listBox == null) { break; }
// TODO: The get's out of focus if the selection is out of view.
// Not sure how's that possible, but it seems to work when the auto scroll is disabled and you handle the scrolling manually.
listBox.SelectNext();
while (SelectNextTextBox(listBox) == null)
{
var previous = listBox.SelectedComponent;
listBox.SelectNext();
if (listBox.SelectedComponent == previous) { break; }
}
}
}
IEnumerable<GUITextBox> GetAndSortTextBoxes(GUIComponent parent) => parent.GetAllChildren().Select(c => c as GUITextBox).Where(t => t != null).OrderBy(t => t.Rect.Y).ThenBy(t => t.Rect.X);
GUITextBox SelectNextTextBox(GUIListBox listBox)
{
var textBoxes = GetAndSortTextBoxes(listBox.SelectedComponent);
if (textBoxes.Any())
{
var next = textBoxes.First();
next.Select();
next.Flash(Color.White * 0.5f, 0.5f);
return next;
}
return null;
}
break;
}
OnKeyHit?.Invoke(this, key);
void HandleSelection()
{
if (isSelecting)
{
InitSelectionStart();
CalculateSelection();
}
else
{
ClearSelection();
}
}
}
//public event TextBoxEvent OnTabPressed;
public bool Selected
public void SelectAll()
{
get;
set;
CaretIndex = 0;
CalculateCaretPos();
selectionStartPos = caretPos;
selectionStartIndex = 0;
CaretIndex = Text.Length;
CalculateSelection();
}
private void CopySelectedText()
{
#if WINDOWS
System.Windows.Clipboard.SetText(selectedText);
#else
clipboard = selectedText;
#endif
}
private void ClearSelection()
{
selectedCharacters = 0;
selectionStartIndex = -1;
selectionEndIndex = -1;
selectedText = string.Empty;
}
private string GetCopiedText()
{
string t;
#if WINDOWS
t = System.Windows.Clipboard.GetText();
#else
t = clipboard;
#endif
return t;
}
private void RemoveSelectedText()
{
if (selectedText.Length == 0) { return; }
if (IsLeftToRight)
{
SetText(Text.Remove(selectionStartIndex, selectedText.Length));
CaretIndex = Math.Min(Text.Length, selectionStartIndex);
}
else
{
SetText(Text.Remove(selectionEndIndex, selectedText.Length));
CaretIndex = Math.Min(Text.Length, selectionEndIndex);
}
ClearSelection();
OnTextChanged?.Invoke(this, Text);
}
private void InitSelectionStart()
{
if (caretPosDirty)
{
CalculateCaretPos();
}
if (selectionStartIndex == -1)
{
selectionStartIndex = CaretIndex;
selectionStartPos = caretPos;
}
}
private void CalculateSelection()
{
InitSelectionStart();
selectionEndIndex = CaretIndex;
selectionEndPos = caretPos;
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
if (IsLeftToRight)
{
selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textBlock.WrappedText.Substring(selectionStartIndex, selectedCharacters));
}
else
{
selectedText = Text.Substring(selectionEndIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textBlock.WrappedText.Substring(selectionEndIndex, selectedCharacters));
}
}
}
}
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma
{
@@ -11,6 +12,10 @@ namespace Barotrauma
public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected;
public static int size = 20;
private List<GUITickBox> radioButtonGroup;
private bool selected;
public bool Selected
@@ -19,42 +24,33 @@ namespace Barotrauma
set
{
if (value == selected) return;
if (radioButtonGroup != null && !value) return;
selected = value;
state = (selected) ? ComponentState.Selected : ComponentState.None;
box.State = state;
if (radioButtonGroup != null)
{
foreach (GUITickBox tickBox in radioButtonGroup)
{
if (tickBox == this) continue;
tickBox.selected = false;
tickBox.state = tickBox.box.State = ComponentState.None;
}
}
OnSelected?.Invoke(this);
if (radioButtonGroup != null)
{
foreach (GUITickBox tickBox in radioButtonGroup)
{
if (tickBox == this) continue;
tickBox.OnSelected?.Invoke(tickBox);
}
}
}
}
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
enabled = value;
}
}
public override Rectangle Rect
{
get
{
return rect;
}
set
{
base.Rect = value;
if (box != null) box.Rect = new Rectangle(value.X,value.Y,box.Rect.Width,box.Rect.Height);
if (text != null) text.Rect = new Rectangle(box.Rect.Right, box.Rect.Y + 2, 20, box.Rect.Height);
}
}
public Color TextColor
{
get { return text.TextColor; }
@@ -63,7 +59,11 @@ namespace Barotrauma
public override Rectangle MouseRect
{
get { return ClampMouseRectToParent ? ClampRect(box.Rect) : box.Rect; }
get
{
if (!CanBeFocused) return Rectangle.Empty;
return ClampMouseRectToParent ? ClampRect(box.Rect) : box.Rect;
}
}
public override ScalableFont Font
@@ -80,37 +80,70 @@ namespace Barotrauma
}
}
public GUITickBox(Rectangle rect, string label, Alignment alignment, GUIComponent parent)
: this(rect, label, alignment, GUI.Font, parent)
public GUIFrame Box
{
get { return box; }
}
public GUITickBox(Rectangle rect, string label, Alignment alignment, ScalableFont font, GUIComponent parent)
: base(null)
public override string ToolTip
{
if (parent != null)
parent.AddChild(this);
get { return base.ToolTip; }
set
{
base.ToolTip = value;
box.ToolTip = value;
text.ToolTip = value;
}
}
box = new GUIFrame(rect, Color.DarkGray, "", this);
box.HoverColor = Color.Gray;
box.SelectedColor = Color.DarkGray;
box.CanBeFocused = false;
public string Text
{
get { return text.Text; }
set { text.Text = value; }
}
GUI.Style.Apply(box, "GUITickBox");
text = new GUITextBlock(new Rectangle(rect.Right, rect.Y, 20, rect.Height), label, "", Alignment.TopLeft, Alignment.Left | Alignment.CenterY, this, false, font);
public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT)
{
box = new GUIFrame(new RectTransform(new Point(rectT.Rect.Height, rectT.Rect.Height), rectT, Anchor.CenterLeft)
{
IsFixedSize = false
}, string.Empty, Color.DarkGray)
{
HoverColor = Color.Gray,
SelectedColor = Color.DarkGray,
CanBeFocused = false
};
GUI.Style.Apply(box, style == "" ? "GUITickBox" : style);
text = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.CenterLeft) { AbsoluteOffset = new Point(box.Rect.Width, 0) }, label, font: font, textAlignment: Alignment.CenterLeft);
GUI.Style.Apply(text, "GUIButtonHorizontal", this);
this.rect = new Rectangle(box.Rect.X, box.Rect.Y, 240, rect.Height);
Enabled = true;
ResizeBox();
rectT.ScaleChanged += ResizeBox;
rectT.SizeChanged += ResizeBox;
}
public static void CreateRadioButtonGroup(IEnumerable<GUITickBox> tickBoxes)
{
var group = new List<GUITickBox>(tickBoxes);
foreach (GUITickBox tickBox in tickBoxes)
{
tickBox.radioButtonGroup = group;
}
}
private void ResizeBox()
{
box.RectTransform.NonScaledSize = new Point(RectTransform.NonScaledSize.Y);
text.RectTransform.AbsoluteOffset = new Point(box.Rect.Width, 0);
}
public override void Update(float deltaTime)
protected override void Update(float deltaTime)
{
if (!Visible) return;
if (MouseOn == this && Enabled)
if (GUI.MouseOn == this && Enabled)
{
box.State = ComponentState.Hover;
@@ -121,8 +154,14 @@ namespace Barotrauma
if (PlayerInput.LeftButtonClicked())
{
Selected = !Selected;
if (OnSelected != null) OnSelected(this);
if (radioButtonGroup == null)
{
Selected = !Selected;
}
else if (!selected)
{
Selected = true;
}
}
}
else
@@ -135,12 +174,5 @@ namespace Barotrauma
box.State = ComponentState.Selected;
}
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
DrawChildren(spriteBatch);
}
}
}
@@ -0,0 +1,75 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace Barotrauma
{
class Graph
{
private float[] values;
public Graph(int arraySize = 100)
{
values = new float[arraySize];
}
public float LargestValue()
{
float maxValue = 0.0f;
for (int i = 0; i < values.Length; i++)
{
if (values[i] > maxValue) maxValue = values[i];
}
return maxValue;
}
public float Average()
{
return values.Length == 0 ? 0.0f : values.Average();
}
public void Update(float newValue)
{
for (int i = values.Length - 1; i > 0; i--)
{
values[i] = values[i - 1];
}
values[0] = newValue;
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float? maxVal, float xOffset, Color color)
{
float graphMaxVal = 1.0f;
if (maxVal == null)
{
graphMaxVal = LargestValue();
}
else if (maxVal > 0.0f)
{
graphMaxVal = (float)maxVal;
}
GUI.DrawRectangle(spriteBatch, rect, Color.White);
if (values.Length == 0) return;
float lineWidth = (float)rect.Width / (float)(values.Length - 2);
float yScale = (float)rect.Height / graphMaxVal;
Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (values[1] + (values[0] - values[1]) * xOffset) * yScale);
float currX = rect.Right - ((xOffset - 1.0f) * lineWidth);
for (int i = 1; i < values.Length - 1; i++)
{
currX -= lineWidth;
Vector2 newPoint = new Vector2(currX, rect.Bottom - values[i] * yScale);
GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color);
prevPoint = newPoint;
}
Vector2 lastPoint = new Vector2(rect.X,
rect.Bottom - (values[values.Length - 1] + (values[values.Length - 2] - values[values.Length - 1]) * xOffset) * yScale);
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color);
}
}
}
@@ -0,0 +1,204 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
static class HUDLayoutSettings
{
public static bool DebugDraw;
private static int inventoryTopY;
public static int InventoryTopY
{
get { return inventoryTopY; }
set
{
if (value == inventoryTopY) return;
inventoryTopY = value;
CreateAreas();
}
}
public static Rectangle ButtonAreaTop
{
get; private set;
}
public static Rectangle MessageAreaTop
{
get; private set;
}
public static Rectangle InventoryAreaUpper
{
get; private set;
}
public static Rectangle CrewArea
{
get; private set;
}
public static Rectangle ChatBoxArea
{
get; private set;
}
public static Alignment ChatBoxAlignment
{
get; private set;
}
public static Rectangle InventoryAreaLower
{
get; private set;
}
public static Rectangle HealthBarAreaLeft
{
get; private set;
}
public static Rectangle AfflictionAreaLeft
{
get; private set;
}
public static Rectangle HealthBarAreaRight
{
get; private set;
}
public static Rectangle AfflictionAreaRight
{
get; private set;
}
public static Rectangle HealthWindowAreaLeft
{
get; private set;
}
public static Rectangle HealthWindowAreaRight
{
get; private set;
}
public static Rectangle PortraitArea
{
get; private set;
}
public static int Padding
{
get; private set;
}
static HUDLayoutSettings()
{
if (GameMain.Instance != null)
{
GameMain.Instance.OnResolutionChanged += CreateAreas;
GameMain.Config.OnHUDScaleChanged += CreateAreas;
CreateAreas();
}
}
public static RectTransform ToRectTransform(Rectangle rect, RectTransform parent)
{
return new RectTransform(new Vector2(rect.Width / (float)GameMain.GraphicsWidth, rect.Height / (float)GameMain.GraphicsHeight), parent)
{
RelativeOffset = new Vector2(rect.X / (float)GameMain.GraphicsWidth, rect.Y / (float)GameMain.GraphicsHeight)
};
}
public static void CreateAreas()
{
Padding = (int)(10 * GUI.Scale);
if (inventoryTopY == 0) inventoryTopY = GameMain.GraphicsHeight;
//slice from the top of the screen for misc buttons (info, end round, server controls)
ButtonAreaTop = new Rectangle(Padding, Padding, GameMain.GraphicsWidth - Padding * 2, (int)(50 * GUI.Scale));
int crewAreaHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.22f, 150);
CrewArea = new Rectangle(Padding, ButtonAreaTop.Bottom + Padding, GameMain.GraphicsWidth - InventoryAreaUpper.Width - Padding * 3, crewAreaHeight);
int portraitSize = (int)(120 * GUI.Scale);
PortraitArea = new Rectangle(GameMain.GraphicsWidth - portraitSize - Padding, GameMain.GraphicsHeight - portraitSize - Padding, portraitSize, portraitSize);
//horizontal slices at the corners of the screen for health bar and affliction icons
int healthBarWidth = (int)Math.Max(250 * GUI.Scale, 150);
int healthBarHeight = (int)Math.Max(20 * GUI.Scale, 15);
int afflictionAreaHeight = (int)(60 * GUI.Scale);
HealthBarAreaLeft = new Rectangle(Padding, GameMain.GraphicsHeight - healthBarHeight - Padding, healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(Padding, HealthBarAreaLeft.Y - afflictionAreaHeight - Padding, healthBarWidth, afflictionAreaHeight);
HealthBarAreaRight = new Rectangle(PortraitArea.X - Padding - healthBarWidth, Math.Min(PortraitArea.Y + Padding * 3, inventoryTopY - healthBarHeight), healthBarWidth, HealthBarAreaLeft.Height);
if (HealthBarAreaRight.Y + healthBarHeight * 0.75f < PortraitArea.Y)
{
HealthBarAreaRight = new Rectangle(GameMain.GraphicsWidth - Padding - healthBarWidth, HealthBarAreaRight.Y, HealthBarAreaRight.Width, HealthBarAreaRight.Height);
}
AfflictionAreaRight = new Rectangle(HealthBarAreaRight.X, HealthBarAreaRight.Y - Padding - afflictionAreaHeight, healthBarWidth, afflictionAreaHeight);
int messageAreaPos = GameMain.GraphicsWidth - HealthBarAreaRight.X;
MessageAreaTop = new Rectangle(messageAreaPos + Padding, ButtonAreaTop.Bottom, GameMain.GraphicsWidth - (messageAreaPos + Padding) * 2, ButtonAreaTop.Height);
//slice for the upper slots of the inventory (clothes, id card, headset)
int inventoryAreaUpperWidth = (int)(GameMain.GraphicsWidth * 0.2f);
int inventoryAreaUpperHeight = (int)(GameMain.GraphicsHeight * 0.2f);
InventoryAreaUpper = new Rectangle(GameMain.GraphicsWidth - inventoryAreaUpperWidth - Padding, CrewArea.Y, inventoryAreaUpperWidth, inventoryAreaUpperHeight);
//chatbox between upper and lower inventory areas, can be on either side depending on the alignment
ChatBoxAlignment = Alignment.Right;
int chatBoxWidth = (int)(500 * GUI.Scale);
int chatBoxHeight = crewAreaHeight;
ChatBoxArea = ChatBoxAlignment == Alignment.Left ?
new Rectangle(Padding, CrewArea.Y, chatBoxWidth, chatBoxHeight) :
new Rectangle(GameMain.GraphicsWidth - Padding - chatBoxWidth, CrewArea.Y, chatBoxWidth, chatBoxHeight);
int lowerAreaHeight = (int)Math.Min(GameMain.GraphicsHeight * 0.25f, 280);
InventoryAreaLower = new Rectangle(Padding, GameMain.GraphicsHeight - lowerAreaHeight, GameMain.GraphicsWidth - Padding * 2, lowerAreaHeight);
int healthWindowY = CrewArea.Bottom + Padding;
Rectangle healthWindowArea = ChatBoxAlignment == Alignment.Left ?
new Rectangle(ChatBoxArea.Right + Padding, healthWindowY, GameMain.GraphicsWidth - ChatBoxArea.Width - inventoryAreaUpperWidth, GameMain.GraphicsHeight - healthWindowY - lowerAreaHeight / 2) :
new Rectangle(Padding - ChatBoxArea.Width, healthWindowY, GameMain.GraphicsWidth - ChatBoxArea.Width - inventoryAreaUpperWidth, GameMain.GraphicsHeight - healthWindowY - lowerAreaHeight / 2);
int healthWindowPadding = Padding * 3;
HealthWindowAreaLeft = new Rectangle(healthWindowPadding, healthWindowY, GameMain.GraphicsWidth / 2 - healthWindowPadding, GameMain.GraphicsHeight - healthWindowY - lowerAreaHeight);
HealthWindowAreaRight = new Rectangle(GameMain.GraphicsWidth / 2, healthWindowY, GameMain.GraphicsWidth / 2 - healthWindowPadding, GameMain.GraphicsHeight - healthWindowY - lowerAreaHeight);
}
public static void Draw(SpriteBatch spriteBatch)
{
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f);
GUI.DrawRectangle(spriteBatch, MessageAreaTop, Color.Orange * 0.5f);
GUI.DrawRectangle(spriteBatch, InventoryAreaUpper, Color.Yellow * 0.5f);
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f);
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthBarAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthBarAreaRight, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, AfflictionAreaRight, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthWindowAreaRight, Color.Red * 0.5f);
}
}
public static class HUD
{
public static bool CloseHUD(Rectangle rect)
{
//don't close when the cursor is on a UI element
if (GUI.MouseOn != null) return false;
//don't close when hovering over an inventory element
if (Inventory.IsMouseOnInventory()) return false;
bool input = PlayerInput.LeftButtonDown() || PlayerInput.RightButtonClicked();
return input && !rect.Contains(PlayerInput.MousePosition);
}
}
}
@@ -4,23 +4,26 @@ using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class LoadingScreen
{
private Texture2D backgroundTexture,monsterTexture,titleTexture;
private Texture2D backgroundTexture, monsterTexture, titleTexture;
readonly RenderTarget2D renderTarget;
private RenderTarget2D renderTarget;
float state;
private float state;
private string selectedTip;
public Vector2 CenterPosition;
public Vector2 TitlePosition;
private float? loadState;
#if !LINUX
#if !(LINUX || OSX)
Video splashScreenVideo;
VideoPlayer videoPlayer;
#endif
@@ -57,13 +60,13 @@ namespace Barotrauma
public LoadingScreen(GraphicsDevice graphics)
{
#if !LINUX
#if !(LINUX || OSX)
if (GameMain.Config.EnableSplashScreen)
{
try
{
splashScreenVideo = GameMain.Instance.Content.Load<Video>("utg_4");
splashScreenVideo = GameMain.Instance.Content.Load<Video>("splashscreen");
}
catch (Exception e)
@@ -80,14 +83,20 @@ namespace Barotrauma
titleTexture = TextureLoader.FromFile("Content/UI/titleText.png");
renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
GameMain.Instance.OnResolutionChanged += () =>
{
renderTarget?.Dispose();
renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
};
DrawLoadingText = true;
selectedTip = TextManager.Get("LoadingScreenTip", true);
}
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
{
#if !LINUX
#if !(LINUX || OSX)
if (GameMain.Config.EnableSplashScreen && splashScreenVideo != null)
{
try
@@ -103,18 +112,18 @@ namespace Barotrauma
}
}
#endif
drawn = true;
graphics.SetRenderTarget(renderTarget);
Scale = GameMain.GraphicsHeight/1500.0f;
Scale = GameMain.GraphicsHeight / 1500.0f;
state += deltaTime;
if (DrawLoadingText)
{
CenterPosition = new Vector2(GameMain.GraphicsWidth*0.3f, GameMain.GraphicsHeight/2.0f);
CenterPosition = new Vector2(GameMain.GraphicsWidth * 0.3f, GameMain.GraphicsHeight / 2.0f);
TitlePosition = CenterPosition + new Vector2(-0.0f + (float)Math.Sqrt(state) * 220.0f, 0.0f) * Scale;
TitlePosition.X = Math.Min(TitlePosition.X, (float)GameMain.GraphicsWidth / 2.0f);
}
@@ -124,7 +133,7 @@ namespace Barotrauma
spriteBatch.Draw(backgroundTexture, CenterPosition, null, Color.White * Math.Min(state / 5.0f, 1.0f), 0.0f,
new Vector2(backgroundTexture.Width / 2.0f, backgroundTexture.Height / 2.0f),
Scale*1.5f, SpriteEffects.None, 0.2f);
Scale * 1.5f, SpriteEffects.None, 0.2f);
spriteBatch.Draw(monsterTexture,
CenterPosition + new Vector2((state % 40) * 100.0f - 1800.0f, (state % 40) * 30.0f - 200.0f) * Scale, null,
@@ -133,19 +142,19 @@ namespace Barotrauma
spriteBatch.Draw(titleTexture,
TitlePosition, null,
Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
spriteBatch.End();
graphics.SetRenderTarget(null);
if (Hull.renderer != null)
if (WaterRenderer.Instance != null)
{
Hull.renderer.ScrollWater(deltaTime);
Hull.renderer.RenderBack(spriteBatch, renderTarget, 0.0f);
WaterRenderer.Instance.ScrollWater(Vector2.One * 10.0f, deltaTime);
WaterRenderer.Instance.RenderWater(spriteBatch, renderTarget, null);
}
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
spriteBatch.Draw(titleTexture,
TitlePosition, null,
Color.White * Math.Min((state - 3.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
@@ -169,16 +178,27 @@ namespace Barotrauma
if (GUI.LargeFont != null)
{
GUI.LargeFont.DrawString(spriteBatch, loadText,
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.8f),
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText).X / 2.0f, GameMain.GraphicsHeight * 0.7f),
Color.White);
}
if (GUI.Font != null && selectedTip != null)
{
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
string[] lines = wrappedTip.Split('\n');
for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawString(spriteBatch, lines[i],
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f, GameMain.GraphicsHeight * 0.78f + i * 15), Color.White);
}
}
}
spriteBatch.End();
}
#if !LINUX
#if !(LINUX || OSX)
private void DrawSplashScreen(SpriteBatch spriteBatch)
{
if (videoPlayer == null)
@@ -222,7 +242,8 @@ namespace Barotrauma
{
drawn = false;
LoadState = null;
selectedTip = TextManager.Get("LoadingScreenTip", true);
while (!drawn)
{
yield return CoroutineStatus.Running;
@@ -0,0 +1,53 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class ParamsEditor
{
private static ParamsEditor _instance;
public static ParamsEditor Instance
{
get
{
if (_instance == null)
{
_instance = new ParamsEditor();
}
return _instance;
}
}
public GUIListBox EditorBox { get; private set; }
/// <summary>
/// Uses Linq queries. Don't use too frequently or reimplement.
/// </summary>
public IEnumerable<SerializableEntityEditor> FindEntityEditors() => EditorBox.Content.RectTransform.Children
.Select(c => c.GUIComponent as SerializableEntityEditor)
.Where(c => c != null);
public GUIListBox CreateEditorBox(RectTransform rectT = null)
{
rectT = rectT ?? new RectTransform(new Vector2(0.25f, 0.95f), GUI.Canvas) { MinSize = new Point(340, GameMain.GraphicsHeight) };
rectT.SetPosition(Anchor.TopRight);
rectT.RelativeOffset = new Vector2(0.16f, 0);
EditorBox = new GUIListBox(rectT)
{
Spacing = 10,
Color = Color.Black
};
return EditorBox;
}
public void Clear()
{
EditorBox.ClearChildren();
}
public ParamsEditor(RectTransform rectT = null)
{
EditorBox = CreateEditorBox();
}
}
}
@@ -0,0 +1,738 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using System.Xml.Linq;
namespace Barotrauma
{
public enum Anchor
{
TopLeft, TopCenter, TopRight,
CenterLeft, Center, CenterRight,
BottomLeft, BottomCenter, BottomRight
}
public enum Pivot
{
TopLeft, TopCenter, TopRight,
CenterLeft, Center, CenterRight,
BottomLeft, BottomCenter, BottomRight
}
public class RectTransform
{
#region Fields and Properties
/// <summary>
/// Should be assigned only by GUIComponent.
/// Note that RectTransform is created first and the GUIComponent after that.
/// This means the GUIComponent is not set before the GUIComponent is initialized.
/// </summary>
public GUIComponent GUIComponent { get; set; }
private RectTransform parent;
public RectTransform Parent
{
get { return parent; }
set
{
if (parent == value || value == this) { return; }
// Remove the child from the old parent
RemoveFromHierarchy(displayErrors: false);
parent = value;
if (parent != null && !parent.children.Contains(this))
{
parent.children.Add(this);
RecalculateAll(false, true, true);
ParentChanged?.Invoke(parent);
Parent.ChildrenChanged?.Invoke(this);
}
}
}
private List<RectTransform> children = new List<RectTransform>();
public IEnumerable<RectTransform> Children => children;
public int CountChildren => children.Count;
private Vector2 relativeSize = Vector2.One;
/// <summary>
/// Relative to the parent rect.
/// </summary>
public Vector2 RelativeSize
{
get { return relativeSize; }
set
{
if (relativeSize.NearlyEquals(value)) { return; }
relativeSize = value;
RecalculateAll(resize: true, scale: false, withChildren: true);
}
}
private Point? minSize;
/// <summary>
/// Min size in pixels.
/// Does not affect scaling.
/// </summary>
public Point MinSize
{
get { return minSize ?? Point.Zero; }
set
{
if (minSize == value) { return; }
minSize = value;
RecalculateAll(true, false, true);
}
}
private static Point maxPoint = new Point(int.MaxValue, int.MaxValue);
private Point? maxSize;
/// <summary>
/// Max size in pixels.
/// Does not affect scaling.
/// </summary>
public Point MaxSize
{
get { return maxSize ?? maxPoint; }
set
{
if (maxSize == value) { return; }
maxSize = value;
RecalculateAll(true, false, true);
}
}
private Point nonScaledSize;
/// <summary>
/// Size before scale multiplications.
/// </summary>
public Point NonScaledSize
{
get { return nonScaledSize; }
set
{
if (nonScaledSize == value) { return; }
nonScaledSize = value.Clamp(MinSize, MaxSize);
RecalculateRelativeSize();
RecalculateAnchorPoint();
RecalculatePivotOffset();
RecalculateChildren(resize: true, scale: false);
}
}
/// <summary>
/// Size after scale multiplications.
/// </summary>
public Point ScaledSize => NonScaledSize.Multiply(Scale);
/// <summary>
/// Applied to all RectTransforms.
/// The elements are not automatically resized, if the global scale changes.
/// You have to manually call RecalculateScale() for all elements after changing the global scale.
/// This is because there is currently no easy way to inform all the elements without having a reference to them.
/// Having a reference (static list, or event) is problematic, because deconstructing the elements is not handled manually.
/// This means that the uncleared references would bloat the memory.
/// We could recalculate the scale each time it's needed,
/// but in that case the calculation would need to be very lightweight and garbage free, which it currently is not.
/// </summary>
public static Vector2 globalScale = Vector2.One;
private Vector2 localScale = Vector2.One;
public Vector2 LocalScale
{
get { return localScale; }
set
{
if (localScale.NearlyEquals(value)) { return; }
localScale = value;
RecalculateAll(resize: false, scale: true, withChildren: true);
ScaleChanged?.Invoke();
}
}
public Vector2 Scale { get; private set; }
private Vector2 relativeOffset = Vector2.Zero;
private Point absoluteOffset = Point.Zero;
private Point screenSpaceOffset = Point.Zero;
/// <summary>
/// Defined as portions of the parent size.
/// Also the direction of the offset is relative, calculated away from the anchor point.
/// </summary>
public Vector2 RelativeOffset
{
get { return relativeOffset; }
set
{
if (relativeOffset.NearlyEquals(value)) { return; }
relativeOffset = value;
RecalculateChildren(false, false);
}
}
/// <summary>
/// Absolute in pixels but relative to the anchor point.
/// Calculated away from the anchor point, like a padding.
/// Use RelativeOffset to set an amount relative to the parent size.
/// </summary>
public Point AbsoluteOffset
{
get { return absoluteOffset; }
set
{
if (absoluteOffset == value) { return; }
absoluteOffset = value;
recalculateRect = true;
RecalculateChildren(false, false);
}
}
/// <summary>
/// Screen space offset. From top left corner. In pixels.
/// </summary>
public Point ScreenSpaceOffset
{
get { return screenSpaceOffset; }
set
{
if (screenSpaceOffset == value) { return; }
screenSpaceOffset = value;
recalculateRect = true;
RecalculateChildren(false, false);
}
}
/// <summary>
/// Calculated from the selected pivot. In pixels.
/// </summary>
public Point PivotOffset { get; private set; }
/// <summary>
/// Screen space point in pixels.
/// </summary>
public Point AnchorPoint { get; private set; }
public Point TopLeft
{
get
{
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
Point relativeOffset = ParentRect.MultiplySize(RelativeOffset);
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
}
}
protected Point NonScaledTopLeft
{
get
{
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
Point relativeOffset = NonScaledParentRect.MultiplySize(RelativeOffset);
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
}
}
private bool recalculateRect = true;
private Rectangle _rect;
public Rectangle Rect
{
get
{
if (recalculateRect)
{
_rect = new Rectangle(TopLeft, ScaledSize);
recalculateRect = false;
}
return _rect;
}
}
public Rectangle ParentRect => Parent != null ? Parent.Rect : ScreenRect;
protected Rectangle NonScaledRect => new Rectangle(NonScaledTopLeft, NonScaledSize);
protected Rectangle NonScaledParentRect => parent != null ? Parent.NonScaledRect : ScreenRect;
protected Rectangle ScreenRect => new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
private Pivot pivot;
/// <summary>
/// Does not automatically calculate children.
/// Note also that if you change the pivot point with this property, the pivot does not automatically match the anchor.
/// You can use SetPosition to change everything automatcally or MatchPivotToAnchor to match the pivot to anchor.
/// </summary>
public Pivot Pivot
{
get { return pivot; }
set
{
if (pivot == value) { return; }
pivot = value;
RecalculatePivotOffset();
}
}
private Anchor anchor;
/// <summary>
/// Does not automatically calculate children.
/// Note also that if you change the anchor point with this property, the pivot does not automatically match the anchor.
/// You can use SetPosition to change everything automatically or MatchPivotToAnchor to match the pivot to anchor.
/// </summary>
public Anchor Anchor
{
get { return anchor; }
set
{
if (anchor == value) { return; }
anchor = value;
RecalculateAnchorPoint();
}
}
public bool IsLastChild
{
get
{
if (Parent == null) { return false; }
var last = Parent.Children.LastOrDefault();
if (last == null) { return false; }
return last == this;
}
}
public bool IsFirstChild
{
get
{
if (Parent == null) { return false; }
var first = Parent.Children.FirstOrDefault();
if (first == null) { return false; }
return first == this;
}
}
#endregion
#region Events
public event Action<RectTransform> ParentChanged;
/// <summary>
/// The element provided as the argument is the changed child. It may be new in the hierarchy or just repositioned.
/// </summary>
public event Action<RectTransform> ChildrenChanged;
public event Action ScaleChanged;
public event Action SizeChanged;
#endregion
#region Initialization
public RectTransform(Vector2 relativeSize, RectTransform parent, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null)
{
Init(parent, anchor, pivot);
this.relativeSize = relativeSize;
this.minSize = minSize;
this.maxSize = maxSize;
RecalculateScale();
RecalculateAbsoluteSize();
RecalculateAnchorPoint();
RecalculatePivotOffset();
parent?.ChildrenChanged?.Invoke(this);
}
/// <summary>
/// By default, elements defined with an absolute size (in pixels), will be treated as fixed sized.
/// This can be changed by setting IsFixedSize to false.
/// </summary>
public RectTransform(Point absoluteSize, RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null)
{
Init(parent, anchor, pivot);
this.nonScaledSize = absoluteSize;
RecalculateScale();
RecalculateRelativeSize();
RecalculateAnchorPoint();
RecalculatePivotOffset();
IsFixedSize = true;
parent?.ChildrenChanged?.Invoke(this);
}
public static RectTransform Load(XElement element, RectTransform parent)
{
Enum.TryParse(element.GetAttributeString("anchor", "Center"), out Anchor anchor);
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
Point? minSize = null, maxSize = null;
if (element.Attribute("minsize") != null) minSize = element.GetAttributePoint("minsize", Point.Zero);
if (element.Attribute("maxsize") != null) maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
RectTransform rectTransform;
if (element.Attribute("relativesize") != null)
{
rectTransform = new RectTransform(element.GetAttributeVector2("relativesize", Vector2.One), parent, anchor, pivot, minSize, maxSize);
}
else
{
rectTransform = new RectTransform(element.GetAttributePoint("absolutesize", new Point(1000, 1000)), parent, anchor, pivot)
{
minSize = minSize,
maxSize = maxSize
};
}
rectTransform.RelativeOffset = element.GetAttributeVector2("relativeoffset", Vector2.Zero);
rectTransform.AbsoluteOffset = element.GetAttributePoint("absoluteoffset", Point.Zero);
return rectTransform;
}
private void Init(RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null)
{
this.parent = parent;
parent?.children.Add(this);
Anchor = anchor;
Pivot = pivot ?? MatchPivotToAnchor(Anchor);
}
#endregion
#region Protected methods
protected void RecalculateScale()
{
var scale = LocalScale * globalScale;
var parents = GetParents();
Scale = parents.Any() ? parents.Select(rt => rt.LocalScale).Aggregate((parent, child) => parent * child) * scale : scale;
recalculateRect = true;
ScaleChanged?.Invoke();
}
protected void RecalculatePivotOffset()
{
PivotOffset = CalculatePivotOffset(Pivot, ScaledSize);
recalculateRect = true;
}
protected void RecalculateAnchorPoint()
{
AnchorPoint = CalculateAnchorPoint(Anchor, ParentRect);
recalculateRect = true;
}
protected void RecalculateRelativeSize()
{
relativeSize = new Vector2(NonScaledSize.X, NonScaledSize.Y) / new Vector2(NonScaledParentRect.Width, NonScaledParentRect.Height);
recalculateRect = true;
SizeChanged?.Invoke();
}
protected void RecalculateAbsoluteSize()
{
nonScaledSize = NonScaledParentRect.Size.Multiply(RelativeSize).Clamp(MinSize, MaxSize);
recalculateRect = true;
SizeChanged?.Invoke();
}
/// <summary>
/// If false, the element will resize if the parent is resized (with the children).
/// If true, the element will resize only when explicitly resized.
/// Note that scaling always affects the elements.
/// </summary>
public bool IsFixedSize { get; set; }
protected void RecalculateAll(bool resize, bool scale = true, bool withChildren = true)
{
if (scale)
{
RecalculateScale();
}
if (resize && !IsFixedSize)
{
RecalculateAbsoluteSize();
}
RecalculateAnchorPoint();
RecalculatePivotOffset();
if (withChildren)
{
RecalculateChildren(resize, scale);
}
}
private bool RemoveFromHierarchy(bool displayErrors = true, bool recalculate = true)
{
if (Parent == null)
{
if (displayErrors)
{
DebugConsole.ThrowError("Parent null" + Environment.StackTrace);
}
return false;
}
if (!Parent.children.Contains(this))
{
if (displayErrors)
{
DebugConsole.ThrowError("The children of the parent does not contain this child. This should not be possible! " + Environment.StackTrace);
}
return false;
}
if (!Parent.children.Remove(this))
{
if (displayErrors)
{
DebugConsole.ThrowError("Unable to remove the child from the parent. " + Environment.StackTrace);
}
return false;
}
return true;
}
#endregion
#region Public instance methods
public void SetPosition(Anchor anchor, Pivot? pivot = null)
{
Anchor = anchor;
Pivot = pivot ?? MatchPivotToAnchor(anchor);
ScreenSpaceOffset = Point.Zero;
recalculateRect = true;
RecalculateChildren(false, false);
}
public void Resize(Point absoluteSize, bool resizeChildren = true)
{
nonScaledSize = absoluteSize;
RecalculateRelativeSize();
RecalculateAll(resize: false, scale: false, withChildren: false);
RecalculateChildren(resizeChildren, false);
}
public void Resize(Vector2 relativeSize, bool resizeChildren = true)
{
this.relativeSize = relativeSize;
RecalculateAll(resize: true, scale: false, withChildren: false);
RecalculateChildren(resizeChildren, false);
}
public void ChangeScale(Vector2 newScale)
{
LocalScale = newScale;
}
public void ResetScale()
{
ChangeScale(Vector2.One);
}
/// <summary>
/// Currently this needs to be manually called only when the global scale changes.
/// If the local scale changes, the scale is automatically recalculated.
/// </summary>
public void RecalculateScale(bool withChildren)
{
RecalculateScale();
if (withChildren)
{
RecalculateChildren(resize: false, scale: true);
}
}
/// <summary>
/// Manipulates ScreenSpaceOffset.
/// If you want to manipulate some other offset, access the property setters directly.
/// </summary>
public void Translate(Point translation)
{
ScreenSpaceOffset += translation;
}
/// <summary>
/// Returns all parent elements in the hierarchy.
/// </summary>
public IEnumerable<RectTransform> GetParents()
{
var parents = new List<RectTransform>();
if (Parent != null)
{
parents.Add(Parent);
return parents.Concat(Parent.GetParents());
}
else
{
return parents;
}
}
/// <summary>
/// Returns all child elements in the hierarchy.
/// </summary>
public IEnumerable<RectTransform> GetAllChildren()
{
return children.SelectManyRecursive(c => c.children);
}
public int GetChildIndex(RectTransform rectT)
{
return children.IndexOf(rectT);
}
public RectTransform GetChild(int index)
{
return children[index];
}
public bool IsParentOf(RectTransform rectT, bool recursive = true)
{
return children.Contains(rectT) || (recursive && children.Any(c => c.IsParentOf(rectT)));
}
public void ClearChildren()
{
children.ForEachMod(c => c.Parent = null);
}
public void SortChildren(Comparison<RectTransform> comparison)
{
children.Sort(comparison);
RecalculateAll(false, true, true);
Parent.ChildrenChanged?.Invoke(this);
}
public void SetAsLastChild()
{
if (IsLastChild) { return; }
if (!RemoveFromHierarchy(displayErrors: true)) { return; }
parent.children.Add(this);
RecalculateAll(false, true, true);
parent.ChildrenChanged?.Invoke(this);
}
public void SetAsFirstChild()
{
if (IsFirstChild) { return; }
RepositionChildInHierarchy(0);
}
public bool RepositionChildInHierarchy(int index)
{
if (!RemoveFromHierarchy(displayErrors: true)) { return false; }
try
{
Parent.children.Insert(index, this);
}
catch (ArgumentOutOfRangeException e)
{
DebugConsole.ThrowError(e.ToString());
return false;
}
RecalculateAll(false, true, true);
Parent.ChildrenChanged?.Invoke(this);
return true;
}
public void RecalculateChildren(bool resize, bool scale = true)
{
for (int i = 0; i < children.Count; i++)
{
children[i].RecalculateAll(resize, scale, withChildren: true);
}
}
public void AddChildrenToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
for (int i = 0; i < children.Count; i++)
{
children[i].GUIComponent.AddToGUIUpdateList(ignoreChildren, order);
}
}
#endregion
#region Static methods
public static Pivot MatchPivotToAnchor(Anchor anchor)
{
if (!Enum.TryParse(anchor.ToString(), out Pivot pivot))
{
throw new Exception($"[RectTransform] Cannot match pivot to anchor {anchor}");
}
return pivot;
}
/// <summary>
/// Converts the offset so that the direction is always away from the anchor point.
/// </summary>
public static Point ConvertOffsetRelativeToAnchor(Point offset, Anchor anchor)
{
switch (anchor)
{
case Anchor.BottomRight:
return offset.Inverse();
case Anchor.BottomLeft:
case Anchor.BottomCenter:
return new Point(offset.X, -offset.Y);
case Anchor.TopRight:
case Anchor.CenterRight:
return new Point(-offset.X, offset.Y);
default:
return offset;
}
}
public static Point CalculatePivotOffset(Pivot pivot, Point size)
{
int width = size.X;
int height = size.Y;
switch (pivot)
{
case Pivot.TopLeft:
return Point.Zero;
case Pivot.TopCenter:
return new Point(-width / 2, 0);
case Pivot.TopRight:
return new Point(-width, 0);
case Pivot.CenterLeft:
return new Point(0, -height / 2);
case Pivot.Center:
return size.Divide(2).Inverse();
case Pivot.CenterRight:
return new Point(-width, -height / 2);
case Pivot.BottomLeft:
return new Point(0, -height);
case Pivot.BottomCenter:
return new Point(-width / 2, -height);
case Pivot.BottomRight:
return new Point(-width, -height);
default:
throw new NotImplementedException(pivot.ToString());
}
}
public static Point CalculateAnchorPoint(Anchor anchor, Rectangle parent)
{
switch (anchor)
{
case Anchor.TopLeft:
return parent.Location;
case Anchor.TopCenter:
return new Point(parent.Center.X, parent.Top);
case Anchor.TopRight:
return new Point(parent.Right, parent.Top);
case Anchor.CenterLeft:
return new Point(parent.Left, parent.Center.Y);
case Anchor.Center:
return parent.Center;
case Anchor.CenterRight:
return new Point(parent.Right, parent.Center.Y);
case Anchor.BottomLeft:
return new Point(parent.Left, parent.Bottom);
case Anchor.BottomCenter:
return new Point(parent.Center.X, parent.Bottom);
case Anchor.BottomRight:
return new Point(parent.Right, parent.Bottom);
default:
throw new NotImplementedException(anchor.ToString());
}
}
/// <summary>
/// The elements are not automatically resized, if the global scale changes.
/// You have to manually call RecalculateScale() for all elements after changing the global scale.
/// This is because there is currently no easy way to inform all the elements without having a reference to them.
/// Having a reference (static list, or event) is problematic, because deconstructing the elements is not handled manually.
/// This means that the uncleared references would bloat the memory.
/// We could recalculate the scale each time it's needed,
/// but in that case the calculation would need to be very lightweight and garbage free, which it currently is not.
/// </summary>
public static void ResetGlobalScale()
{
globalScale = Vector2.One;
}
#endregion
}
}
@@ -0,0 +1,324 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Sprite batch extensions for drawing primitive shapes
/// Modified from: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/ShapeExtensions.cs
/// </summary>
public static class ShapeExtensions
{
private static Texture2D _whitePixelTexture;
private static Texture2D GetTexture(SpriteBatch spriteBatch)
{
if (_whitePixelTexture == null)
{
_whitePixelTexture = new Texture2D(spriteBatch.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
_whitePixelTexture.SetData(new[] { Color.White });
}
return _whitePixelTexture;
}
/// <summary>
/// Draws a closed polygon from a <see cref="Polygon" /> shape
/// </summary>
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 position, Polygon polygon, Color color,
float thickness = 1f)
{
DrawPolygon(spriteBatch, position, polygon.Vertices, color, thickness);
}
/// <summary>
/// Draws a closed polygon from an array of points
/// </summary>
public static void DrawPolygon(this SpriteBatch spriteBatch, Vector2 offset, IReadOnlyList<Vector2> points, Color color,
float thickness = 1f)
{
if (points.Count == 0)
return;
if (points.Count == 1)
{
DrawPoint(spriteBatch, points[0], color, (int)thickness);
return;
}
var texture = GetTexture(spriteBatch);
for (var i = 0; i < points.Count - 1; i++)
DrawPolygonEdge(spriteBatch, texture, points[i] + offset, points[i + 1] + offset, color, thickness);
DrawPolygonEdge(spriteBatch, texture, points[points.Count - 1] + offset, points[0] + offset, color,
thickness);
}
private static void DrawPolygonEdge(SpriteBatch spriteBatch, Texture2D texture, Vector2 point1, Vector2 point2,
Color color, float thickness)
{
var length = Vector2.Distance(point1, point2);
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
var scale = new Vector2(length, thickness);
spriteBatch.Draw(texture, point1, color: color, rotation: angle, scale: scale);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
public static void DrawLine(this SpriteBatch spriteBatch, float x1, float y1, float x2, float y2, Color color,
float thickness = 1f)
{
DrawLine(spriteBatch, new Vector2(x1, y1), new Vector2(x2, y2), color, thickness);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point1, Vector2 point2, Color color,
float thickness = 1f)
{
// calculate the distance between the two vectors
var distance = Vector2.Distance(point1, point2);
// calculate the angle between the two vectors
var angle = (float)Math.Atan2(point2.Y - point1.Y, point2.X - point1.X);
DrawLine(spriteBatch, point1, distance, angle, color, thickness);
}
/// <summary>
/// Draws a line from point1 to point2 with an offset
/// </summary>
public static void DrawLine(this SpriteBatch spriteBatch, Vector2 point, float length, float angle, Color color,
float thickness = 1f)
{
var origin = new Vector2(0f, 0.5f);
var scale = new Vector2(length, thickness);
spriteBatch.Draw(GetTexture(spriteBatch), point, null, color, angle, origin, scale, SpriteEffects.None, 0);
}
/// <summary>
/// Draws a point at the specified x, y position. The center of the point will be at the position.
/// </summary>
public static void DrawPoint(this SpriteBatch spriteBatch, float x, float y, Color color, float size = 1f)
{
DrawPoint(spriteBatch, new Vector2(x, y), color, size);
}
/// <summary>
/// Draws a point at the specified position. The center of the point will be at the position.
/// </summary>
public static void DrawPoint(this SpriteBatch spriteBatch, Vector2 position, Color color, float size = 1f)
{
var scale = Vector2.One * size;
var offset = new Vector2(0.5f) - new Vector2(size * 0.5f);
spriteBatch.Draw(GetTexture(spriteBatch), position + offset, color: color, scale: scale);
}
public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 center, float radius, int sides, Color color,
float thickness = 1f)
{
DrawPolygon(spriteBatch, center, CreateCircle(radius, sides), color, thickness);
}
public static void DrawCircle(this SpriteBatch spriteBatch, float x, float y, float radius, int sides,
Color color, float thickness = 1f)
{
DrawPolygon(spriteBatch, new Vector2(x, y), CreateCircle(radius, sides), color, thickness);
}
public static void DrawSector(this SpriteBatch spriteBatch, Vector2 center, float radius, float radians, int sides, Color color, float offset = 0, float thickness = 1)
{
DrawPolygon(spriteBatch, center, CreateSector(radius, sides, radians, offset), color, thickness);
}
private static Vector2[] CreateSector(double radius, int sides, float radians, float offset = 0)
{
//circle sectors need one extra point at the center
var points = new Vector2[radians < MathHelper.TwoPi ? sides + 1 : sides];
var step = radians / sides;
double theta = offset;
for (var i = 0; i < sides; i++)
{
points[i] = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * (float)radius;
theta += step;
}
return points;
}
private static Vector2[] CreateCircle(double radius, int sides)
{
return CreateSector(radius, sides, MathHelper.TwoPi);
}
}
/// <summary>
/// Original source: https://github.com/craftworkgames/MonoGame.Extended/blob/develop/Source/MonoGame.Extended/Shapes/Polygon.cs
/// </summary>
public class Polygon : IEquatable<Polygon>
{
public Polygon(IEnumerable<Vector2> vertices)
{
_localVertices = vertices.ToArray();
_transformedVertices = _localVertices;
_offset = Vector2.Zero;
_rotation = 0;
_scale = Vector2.One;
_isDirty = false;
}
private readonly Vector2[] _localVertices;
private Vector2[] _transformedVertices;
private Vector2 _offset;
private float _rotation;
private Vector2 _scale;
private bool _isDirty;
public Vector2[] Vertices
{
get
{
if (_isDirty)
{
_transformedVertices = GetTransformedVertices();
_isDirty = false;
}
return _transformedVertices;
}
}
public float Left
{
get { return Vertices.Min(v => v.X); }
}
public float Right
{
get { return Vertices.Max(v => v.X); }
}
public float Top
{
get { return Vertices.Min(v => v.Y); }
}
public float Bottom
{
get { return Vertices.Max(v => v.Y); }
}
public void Offset(Vector2 amount)
{
_offset += amount;
_isDirty = true;
}
public void Rotate(float amount)
{
_rotation += amount;
_isDirty = true;
}
public void Scale(Vector2 amount)
{
_scale += amount;
_isDirty = true;
}
private Vector2[] GetTransformedVertices()
{
var newVertices = new Vector2[_localVertices.Length];
var isScaled = _scale != Vector2.One;
for (var i = 0; i < _localVertices.Length; i++)
{
var p = _localVertices[i];
if (isScaled)
p *= _scale;
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (_rotation != 0)
{
var cos = (float)Math.Cos(_rotation);
var sin = (float)Math.Sin(_rotation);
p = new Vector2(cos * p.X - sin * p.Y, sin * p.X + cos * p.Y);
}
newVertices[i] = p + _offset;
}
return newVertices;
}
public Polygon TransformedCopy(Vector2 offset, float rotation, Vector2 scale)
{
var polygon = new Polygon(_localVertices);
polygon.Offset(offset);
polygon.Rotate(rotation);
polygon.Scale(scale - Vector2.One);
return new Polygon(polygon.Vertices);
}
public bool Contains(Vector2 point)
{
return Contains(point.X, point.Y);
}
public bool Contains(float x, float y)
{
var intersects = 0;
var vertices = Vertices;
for (var i = 0; i < vertices.Length; i++)
{
var x1 = vertices[i].X;
var y1 = vertices[i].Y;
var x2 = vertices[(i + 1) % vertices.Length].X;
var y2 = vertices[(i + 1) % vertices.Length].Y;
if ((((y1 <= y) && (y < y2)) || ((y2 <= y) && (y < y1))) && (x < (x2 - x1) / (y2 - y1) * (y - y1) + x1))
intersects++;
}
return (intersects & 1) == 1;
}
public static bool operator ==(Polygon a, Polygon b)
{
return a.Equals(b);
}
public static bool operator !=(Polygon a, Polygon b)
{
return !(a == b);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is Polygon && Equals((Polygon)obj);
}
public bool Equals(Polygon other)
{
return Vertices.SequenceEqual(other.Vertices);
}
public override int GetHashCode()
{
unchecked
{
return Vertices.Aggregate(27, (current, v) => current + 13 * current + v.GetHashCode());
}
}
}
}
@@ -0,0 +1,255 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
namespace Barotrauma
{
class SpriteSheetPlayer
{
private SpriteSheet[] playingSheets;
private SpriteSheet currentSheet;
private List<PreloadedContent> preloadedSheets;
private GUIFrame background, videoFrame;
private GUITextBlock title;
private GUICustomComponent sheetView;
private float totalElapsed = 0;
private float animationSpeed = 0.1f;
private float loopTimer = 0.0f;
private float loopDelay = 0.0f;
private int currentSheetIndex = 0;
private int currentFrameIndex = 0;
private Color backgroundColor = new Color(0f, 0f, 0f, 1f);
private bool isPlaying;
public bool IsPlaying
{
get { return isPlaying; }
private set
{
if (isPlaying == value) return;
isPlaying = value;
}
}
private readonly Vector2 defaultResolution = new Vector2(520, 300);
private readonly int borderSize = 20;
private class PreloadedContent
{
public string ContentName;
public string ContentTag;
public SpriteSheet[] Sheets;
public PreloadedContent(string name, string tag, SpriteSheet[] sheets)
{
ContentName = name;
ContentTag = tag;
Sheets = sheets;
}
}
public SpriteSheetPlayer()
{
int width = (int)defaultResolution.X;
int height = (int)defaultResolution.Y;
background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor);
videoFrame = new GUIFrame(new RectTransform(new Point(width + borderSize, height + borderSize), background.RectTransform, Anchor.Center), "SonarFrame");
sheetView = new GUICustomComponent(new RectTransform(new Point(width, height), videoFrame.RectTransform, Anchor.Center),
(spriteBatch, guiCustomComponent) => { DrawSheetView(spriteBatch, guiCustomComponent.Rect); }, UpdateSheetView);
title = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), videoFrame.RectTransform, Anchor.TopCenter, Pivot.BottomCenter), string.Empty, font: GUI.LargeFont, textAlignment: Alignment.Center);
preloadedSheets = new List<PreloadedContent>();
}
public void PreloadContent(string contentPath, string contentTag, string contentId, XElement contentElement)
{
if (preloadedSheets.Find(s => s.ContentName == contentId) != null) return; // Already loaded
preloadedSheets.Add(new PreloadedContent(contentId, contentTag, CreateSpriteSheets(contentPath, contentElement)));
}
public void RemoveAllPreloaded()
{
if (preloadedSheets == null || preloadedSheets.Count == 0) return;
for (int i = 0; i < preloadedSheets.Count; i++)
{
for (int j = 0; j < preloadedSheets[i].Sheets.Length; j++)
{
preloadedSheets[i].Sheets[j].Remove();
}
}
preloadedSheets.Clear();
}
public void RemovePreloadedByTag(string tag)
{
if (preloadedSheets == null || preloadedSheets.Count == 0) return;
for (int i = 0; i < preloadedSheets.Count; i++)
{
if (preloadedSheets[i].ContentTag != tag) continue;
for (int j = 0; j < preloadedSheets[i].Sheets.Length; j++)
{
preloadedSheets[i].Sheets[j].Remove();
}
preloadedSheets[i] = null;
preloadedSheets.RemoveAt(i);
i--;
}
}
public void Play()
{
isPlaying = true;
}
public void Stop()
{
isPlaying = false;
}
public void AddToGUIUpdateList()
{
if (!isPlaying) return;
background.AddToGUIUpdateList();
}
public void LoadContent(string contentPath, XElement videoElement, string contentId, bool startPlayback)
{
totalElapsed = loopTimer = 0.0f;
animationSpeed = videoElement.GetAttributeFloat("animationspeed", 0.1f);
loopDelay = videoElement.GetAttributeFloat("loopdelay", 0.0f); ;
if (playingSheets != null)
{
foreach (SpriteSheet existingSheet in playingSheets)
{
existingSheet.Remove();
}
playingSheets = null;
}
playingSheets = preloadedSheets.Find(s => s.ContentName == contentId).Sheets;
if (playingSheets == null) // No preloaded sheets found, create sheets
{
playingSheets = CreateSpriteSheets(contentPath, videoElement);
}
currentSheet = playingSheets[0];
Point resolution = currentSheet.FrameSize;
videoFrame.RectTransform.NonScaledSize = resolution + new Point(borderSize, borderSize);
sheetView.RectTransform.NonScaledSize = resolution;
title.Text = TextManager.Get(contentId);
title.RectTransform.NonScaledSize = new Point(resolution.X, 30);
if (startPlayback) Play();
}
private SpriteSheet[] CreateSpriteSheets(string contentPath, XElement videoElement)
{
SpriteSheet[] sheets = null;
try
{
List<XElement> sheetElements = new List<XElement>();
foreach (var sheetElement in videoElement.Elements("Sheet"))
{
sheetElements.Add(sheetElement);
}
sheets = new SpriteSheet[sheetElements.Count];
for (int i = 0; i < sheetElements.Count; i++)
{
sheets[i] = new SpriteSheet(sheetElements[i], contentPath, sheetElements[i].GetAttributeString("path", ""), sheetElements[i].GetAttributeInt("empty", 0));
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading sprite sheet content " + contentPath + "!", e);
}
return sheets;
}
private void UpdateSheetView(float deltaTime, GUICustomComponent viewContainer)
{
if (!isPlaying) return;
if (loopTimer > 0.0f)
{
loopTimer -= deltaTime;
if (loopTimer <= 0.0f)
{
currentSheetIndex = 0;
currentFrameIndex = 0;
currentSheet = playingSheets[currentSheetIndex];
}
else
{
return;
}
}
totalElapsed += deltaTime;
if (totalElapsed > animationSpeed)
{
totalElapsed -= animationSpeed;
currentFrameIndex++;
if (currentFrameIndex > currentSheet.FrameCount - 1)
{
currentSheetIndex++;
if (currentSheetIndex > playingSheets.Length - 1)
{
if (loopDelay > 0.0f)
{
loopTimer = loopDelay;
return;
}
currentSheetIndex = 0;
}
currentFrameIndex = 0;
currentSheet = playingSheets[currentSheetIndex];
}
}
}
private void DrawSheetView(SpriteBatch spriteBatch, Rectangle rect)
{
if (!isPlaying) return;
currentSheet.Draw(spriteBatch, currentFrameIndex, rect.Center.ToVector2(), Color.White, currentSheet.Origin, 0f, Vector2.One);
}
public void Remove()
{
if (playingSheets != null)
{
foreach (SpriteSheet existingSheet in playingSheets)
{
existingSheet.Remove();
}
playingSheets = null;
}
RemoveAllPreloaded();
}
}
}
@@ -0,0 +1,158 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
namespace Barotrauma
{
public class UISprite
{
public Sprite Sprite
{
get;
private set;
}
public bool Tile
{
get;
private set;
}
public bool Slice
{
get;
set;
}
public Rectangle[] Slices
{
get;
set;
}
public bool MaintainAspectRatio
{
get;
private set;
}
public UISprite(XElement element)
{
Sprite = new Sprite(element);
MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false);
Tile = element.GetAttributeBool("tile", true);
Vector4 sliceVec = element.GetAttributeVector4("slice", Vector4.Zero);
if (sliceVec != Vector4.Zero)
{
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
Slice = true;
Slices = new Rectangle[9];
//top-left
Slices[0] = new Rectangle(Sprite.SourceRect.Location, slice.Location - Sprite.SourceRect.Location);
//top-mid
Slices[1] = new Rectangle(slice.Location.X, Slices[0].Y, slice.Width, Slices[0].Height);
//top-right
Slices[2] = new Rectangle(slice.Right, Slices[0].Y, Sprite.SourceRect.Right - slice.Right, Slices[0].Height);
//mid-left
Slices[3] = new Rectangle(Slices[0].X, slice.Y, Slices[0].Width, slice.Height);
//center
Slices[4] = slice;
//mid-right
Slices[5] = new Rectangle(Slices[2].X, slice.Y, Slices[2].Width, slice.Height);
//bottom-left
Slices[6] = new Rectangle(Slices[0].X, slice.Bottom, Slices[0].Width, Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-mid
Slices[7] = new Rectangle(Slices[1].X, slice.Bottom, Slices[1].Width, Sprite.SourceRect.Bottom - slice.Bottom);
//bottom-right
Slices[8] = new Rectangle(Slices[2].X, slice.Bottom, Slices[2].Width, Sprite.SourceRect.Bottom - slice.Bottom);
}
}
public void Draw(SpriteBatch spriteBatch, Rectangle rect, Color color, SpriteEffects spriteEffects = SpriteEffects.None)
{
if (Sprite.Texture == null)
{
GUI.DrawRectangle(spriteBatch, rect, Color.Magenta);
return;
}
if (Slice)
{
Vector2 pos = new Vector2(rect.X, rect.Y);
int centerWidth = Math.Max(rect.Width - Slices[0].Width - Slices[2].Width, 0);
int centerHeight = Math.Max(rect.Height - Slices[0].Height - Slices[8].Height, 0);
Vector2 scale = Vector2.One;
if (centerHeight == 0)
{
scale.Y = MathHelper.Clamp((float)rect.Height / (Slices[0].Height + Slices[3].Height + Slices[6].Height), 0, 1);
centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale.Y);
}
else
{
scale.Y = MathHelper.Clamp((float)rect.Height / (Slices[0].Height + Slices[6].Height), 0, 1);
centerHeight = (int)(centerHeight * scale.Y);
}
if (centerWidth == 0)
{
scale.X = MathHelper.Clamp((float)rect.Height / (Slices[0].Width + Slices[1].Width + Slices[2].Width), 0, 1);
centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale.X);
}
else
{
scale.X = MathHelper.Clamp((float)rect.Width / (Slices[0].Width + Slices[2].Width), 0, 1);
centerWidth = (int)(centerWidth * scale.X);
}
for (int x = 0; x < 3; x++)
{
float width = (x == 1 ? centerWidth : Slices[x].Width * scale.X);
for (int y = 0; y < 3; y++)
{
float height = (y == 1 ? centerHeight : Slices[x + y * 3].Height * scale.Y);
spriteBatch.Draw(Sprite.Texture,
new Rectangle((int)pos.X, (int)pos.Y, (int)width, (int)height),
Slices[x + y * 3],
color);
pos.Y += height;
}
pos.X += width;
pos.Y = rect.Y;
}
}
else if (Tile)
{
Vector2 startPos = new Vector2(rect.X, rect.Y);
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), null, color);
}
else
{
if (MaintainAspectRatio)
{
float scale = Math.Min((float)rect.Width / Sprite.SourceRect.Width, (float)rect.Height / Sprite.SourceRect.Height);
spriteBatch.Draw(Sprite.Texture, rect.Center.ToVector2(),
Sprite.SourceRect,
color,
rotation: 0.0f,
origin: Sprite.size / 2.0f,
scale: scale,
effects: spriteEffects, layerDepth: 0.0f);
}
else
{
spriteBatch.Draw(Sprite.Texture, rect, Sprite.SourceRect, color, 0, Vector2.Zero, spriteEffects, 0);
}
}
}
}
}
@@ -0,0 +1,187 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Extensions;
namespace Barotrauma
{
class Widget
{
public enum Shape
{
Rectangle,
Circle,
Cross
}
public Shape shape;
public string tooltip;
public bool showTooltip = true;
public Rectangle DrawRect => new Rectangle((int)(DrawPos.X - (float)size / 2), (int)(DrawPos.Y - (float)size / 2), size, size);
public Rectangle InputRect
{
get
{
var inputRect = DrawRect;
inputRect.Inflate(inputAreaMargin, inputAreaMargin);
return inputRect;
}
}
public Vector2 DrawPos { get; set; }
public int size = 10;
/// <summary>
/// Used only for circles.
/// </summary>
public int sides = 40;
/// <summary>
/// Currently used only for rectangles.
/// </summary>
public bool isFilled;
public int inputAreaMargin;
public Color color = Color.Red;
public Color? secondaryColor;
public Color textColor = Color.White;
public Color textBackgroundColor = Color.Black * 0.5f;
public readonly string id;
public event Action Selected;
public event Action Deselected;
public event Action Hovered;
public event Action MouseUp;
public event Action MouseDown;
public event Action<float> MouseHeld;
public event Action<float> PreUpdate;
public event Action<float> PostUpdate;
public event Action<SpriteBatch, float> PreDraw;
public event Action<SpriteBatch, float> PostDraw;
public Action refresh;
public object data;
public bool IsSelected => enabled && selectedWidgets.Contains(this);
public bool IsControlled => IsSelected && PlayerInput.LeftButtonHeld();
public bool IsMouseOver => GUI.MouseOn == null && InputRect.Contains(PlayerInput.MousePosition);
private bool enabled = true;
public bool Enabled
{
get { return enabled; }
set
{
enabled = value;
if (!enabled && selectedWidgets.Contains(this))
{
selectedWidgets.Remove(this);
}
}
}
private static bool multiselect;
public static bool EnableMultiSelect
{
get { return multiselect; }
set
{
multiselect = value;
if (!multiselect && selectedWidgets.Multiple())
{
selectedWidgets = selectedWidgets.Take(1).ToList();
}
}
}
public Vector2? tooltipOffset;
public Widget linkedWidget;
public static List<Widget> selectedWidgets = new List<Widget>();
public Widget(string id, int size, Shape shape)
{
this.id = id;
this.size = size;
this.shape = shape;
}
public virtual void Update(float deltaTime)
{
PreUpdate?.Invoke(deltaTime);
if (!enabled) { return; }
if (IsMouseOver)
{
Hovered?.Invoke();
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
{
selectedWidgets.Add(this);
Selected?.Invoke();
}
}
else if (selectedWidgets.Contains(this))
{
selectedWidgets.Remove(this);
Deselected?.Invoke();
}
if (IsSelected)
{
if (PlayerInput.LeftButtonDown())
{
MouseDown?.Invoke();
}
if (PlayerInput.LeftButtonHeld())
{
MouseHeld?.Invoke(deltaTime);
}
if (PlayerInput.LeftButtonClicked())
{
MouseUp?.Invoke();
}
}
PostUpdate?.Invoke(deltaTime);
}
public virtual void Draw(SpriteBatch spriteBatch, float deltaTime)
{
PreDraw?.Invoke(spriteBatch, deltaTime);
var drawRect = DrawRect;
switch (shape)
{
case Shape.Rectangle:
if (secondaryColor.HasValue)
{
GUI.DrawRectangle(spriteBatch, drawRect, secondaryColor.Value, isFilled, thickness: 2);
}
GUI.DrawRectangle(spriteBatch, drawRect, color, isFilled, thickness: IsSelected ? 3 : 1);
break;
case Shape.Circle:
if (secondaryColor.HasValue)
{
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, secondaryColor.Value, thickness: 2);
}
ShapeExtensions.DrawCircle(spriteBatch, DrawPos, size / 2, sides, color, thickness: IsSelected ? 3 : 1);
break;
case Shape.Cross:
float halfSize = size / 2;
if (secondaryColor.HasValue)
{
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, secondaryColor.Value, width: 2);
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, secondaryColor.Value, width: 2);
}
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitY * halfSize, DrawPos - Vector2.UnitY * halfSize, color, width: IsSelected ? 3 : 1);
GUI.DrawLine(spriteBatch, DrawPos + Vector2.UnitX * halfSize, DrawPos - Vector2.UnitX * halfSize, color, width: IsSelected ? 3 : 1);
break;
default: throw new NotImplementedException(shape.ToString());
}
if (IsSelected)
{
if (showTooltip && !string.IsNullOrEmpty(tooltip))
{
var offset = tooltipOffset ?? new Vector2(size, -size / 2);
GUI.DrawString(spriteBatch, DrawPos + offset, tooltip, textColor, textBackgroundColor);
}
}
PostDraw?.Invoke(spriteBatch, deltaTime);
}
}
}