(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,409 @@
using Barotrauma.Extensions;
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;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
class ChatBox
{
public const string RadioChatString = "r; ";
private GUIListBox chatBox;
private Point screenResolution;
public readonly ChatManager ChatManager = new ChatManager();
public bool IsSinglePlayer { get; private set; }
private bool _toggleOpen = true;
public bool ToggleOpen
{
get { return _toggleOpen; }
set
{
_toggleOpen = GameMain.Config.ChatOpen = value;
if (value) hideableElements.Visible = true;
}
}
private float openState;
public bool CloseAfterMessageSent;
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; private set; }
public GUITextBox InputBox { get; private set; }
public GUIButton ToggleButton;
private GUIButton showNewMessagesButton;
private GUIFrame hideableElements;
public const int ToggleButtonWidthRaw = 30;
private int popupMessageOffset;
public ChatBox(GUIComponent parent, bool isSinglePlayer)
{
this.IsSinglePlayer = isSinglePlayer;
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
GUIFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);
hideableElements = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: null);
var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);
InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft),
style: "ChatTextBox")
{
OverflowClip = true,
Font = GUI.SmallFont,
MaxTextLength = ChatMessage.MaxLength
};
ChatManager.RegisterKeys(InputBox, ChatManager);
InputBox.OnDeselected += (gui, Keys) =>
{
ChatManager.Clear();
ChatMessage.GetChatMessageCommand(InputBox.Text, out var message);
if (string.IsNullOrEmpty(message))
{
if (CloseAfterMessageSent)
{
_toggleOpen = false;
CloseAfterMessageSent = false;
}
}
//gui.Text = "";
};
var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
chatSendButton.OnClicked += (GUIButton btn, object userdata) =>
{
InputBox.OnEnterPressed(InputBox, InputBox.Text);
return true;
};
chatSendButton.RectTransform.AbsoluteOffset = new Point((int)(InputBox.Rect.Height * 0.15f), 0);
InputBox.TextBlock.RectTransform.MaxSize
= new Point((int)(InputBox.Rect.Width - chatSendButton.Rect.Width * 1.25f - InputBox.TextBlock.Padding.Z), int.MaxValue);
showNewMessagesButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), GUIFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.125f) }, TextManager.Get("chat.shownewmessages"));
showNewMessagesButton.OnClicked += (GUIButton btn, object userdata) =>
{
chatBox.ScrollBar.BarScrollValue = 1f;
showNewMessagesButton.Visible = false;
return true;
};
showNewMessagesButton.Visible = false;
ToggleOpen = GameMain.Config.ChatOpen;
}
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 > 60)
{
chatBox.RemoveChild(chatBox.Content.Children.First());
}
float prevSize = chatBox.BarSize;
string displayedText = message.TranslatedText;
string senderName = "";
Color senderColor = Color.White;
if (!string.IsNullOrWhiteSpace(message.SenderName))
{
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
}
if (message.Sender?.Info?.Job != null)
{
senderColor = Color.Lerp(message.Sender.Info.Job.Prefab.UIColor, Color.White, 0.25f);
}
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 = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{
CanBeFocused = true
};
if (!string.IsNullOrEmpty(senderName))
{
new GUITextBlock(new RectTransform(new Vector2(0.8f, 1.0f), senderNameBlock.RectTransform) { AbsoluteOffset = new Point((int)(senderNameBlock.TextSize.X), 0) },
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{
CanBeFocused = true
};
}
var msgText =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);
}
msgHolder.RectTransform.SizeChanged += Recalculate;
Recalculate();
void Recalculate()
{
msgHolder.RectTransform.SizeChanged -= Recalculate;
//resize the holder to match the size of the message and add some spacing
msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
senderNameBlock.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameBlock.RectTransform.AbsoluteOffset.X, int.MaxValue);
msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
msgHolder.RectTransform.SizeChanged += Recalculate;
chatBox.RecalculateChildren();
chatBox.UpdateScrollBarSize();
}
CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));
chatBox.UpdateScrollBarSize();
if (chatBox.ScrollBar.Visible && chatBox.ScrollBar.BarScroll < 1f)
{
showNewMessagesButton.Visible = true;
}
if (!ToggleOpen)
{
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
{
Visible = false,
CanBeFocused = false
};
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
Vector2 senderTextSize = Vector2.Zero;
if (!string.IsNullOrEmpty(senderName))
{
var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
senderName, textColor: senderColor, style: null, font: GUI.SmallFont)
{
CanBeFocused = false
};
senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
}
var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
{
CanBeFocused = false
};
msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
popupMsg.RectTransform.IsFixedSize = true;
content.Recalculate();
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.ChatMessage;
if (message.Type == ChatMessageType.Radio)
{
soundType = GUISoundType.RadioMessage;
}
else if (message.Type == ChatMessageType.Dead)
{
soundType = GUISoundType.DeadMessage;
}
GUI.PlayUISound(soundType);
}
public void SetVisibility(bool visible)
{
GUIFrame.Parent.Visible = visible;
}
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)(ToggleButtonWidthRaw * GUI.Scale);
GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(20 * GUI.Scale);
}
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;
}
//hide chatbox when accessing the inventory of another character to prevent overlaps
if (Character.Controlled?.SelectedCharacter?.Inventory != null &&
Character.Controlled.SelectedCharacter.CanInventoryBeAccessed)
{
SetVisibility(false);
}
else
{
SetVisibility(true);
}
if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f)
{
showNewMessagesButton.Visible = false;
}
if (ToggleButton != null)
{
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
}
if (ToggleOpen)
{
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(popupMessageOffset, 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, popupMessageOffset, popupMessageTimer * 5.0f), 0);
}
}
}
openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
int hiddenBoxOffset = -(GUIFrame.Rect.Width);
GUIFrame.RectTransform.AbsoluteOffset =
new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
hideableElements.Visible = openState > 0.0f;
}
}
}
@@ -0,0 +1,176 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
public enum TransitionMode
{
Linear,
Smooth,
Smoother,
EaseIn,
EaseOut,
Exponential
}
public enum SpriteFallBackState
{
None,
Hover,
Pressed,
Selected,
HoverSelected,
Toggle
}
public class GUIComponentStyle
{
public readonly Vector4 Padding;
public readonly Color Color;
public readonly Color HoverColor;
public readonly Color SelectedColor;
public readonly Color PressedColor;
public readonly Color DisabledColor;
public readonly Color TextColor;
public readonly Color HoverTextColor;
public readonly Color SelectedTextColor;
public readonly Color DisabledTextColor;
public readonly float SpriteCrossFadeTime;
public readonly float ColorCrossFadeTime;
public readonly TransitionMode TransitionMode;
public readonly string Font;
public readonly bool ForceUpperCase;
public readonly Color OutlineColor;
public readonly XElement Element;
public readonly Dictionary<GUIComponent.ComponentState, List<UISprite>> Sprites;
public SpriteFallBackState FallBackState;
public Dictionary<string, GUIComponentStyle> ChildStyles;
public readonly GUIStyle Style;
public readonly string Name;
public int? Width { get; private set; }
public int? Height { get; private set; }
public GUIComponentStyle(XElement element, GUIStyle style)
{
Name = element.Name.LocalName;
Style = style;
Element = element;
Sprites = new Dictionary<GUIComponent.ComponentState, List<UISprite>>();
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
{
Sprites[state] = new List<UISprite>();
}
ChildStyles = new Dictionary<string, GUIComponentStyle>();
Padding = element.GetAttributeVector4("padding", Vector4.Zero);
Color = element.GetAttributeColor("color", Color.Transparent);
HoverColor = element.GetAttributeColor("hovercolor", Color);
SelectedColor = element.GetAttributeColor("selectedcolor", Color);
DisabledColor = element.GetAttributeColor("disabledcolor", Color);
PressedColor = element.GetAttributeColor("pressedcolor", Color);
OutlineColor = element.GetAttributeColor("outlinecolor", Color.Transparent);
TextColor = element.GetAttributeColor("textcolor", Color.Black);
HoverTextColor = element.GetAttributeColor("hovertextcolor", TextColor);
DisabledTextColor = element.GetAttributeColor("disabledtextcolor", TextColor);
SelectedTextColor = element.GetAttributeColor("selectedtextcolor", TextColor);
SpriteCrossFadeTime = element.GetAttributeFloat("spritefadetime", SpriteCrossFadeTime);
ColorCrossFadeTime = element.GetAttributeFloat("colorfadetime", ColorCrossFadeTime);
if (Enum.TryParse(element.GetAttributeString("colortransition", string.Empty), ignoreCase: true, out TransitionMode transition))
{
TransitionMode = transition;
}
if (Enum.TryParse(element.GetAttributeString("fallbackstate", GUIComponent.ComponentState.None.ToString()), ignoreCase: true, out SpriteFallBackState s))
{
FallBackState = s;
}
Font = element.GetAttributeString("font", "");
ForceUpperCase = element.GetAttributeBool("forceuppercase", false);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
UISprite newSprite = new UISprite(subElement);
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
if (subElement.Attribute("state") != null)
{
string stateStr = subElement.GetAttributeString("state", "None");
Enum.TryParse(stateStr, out spriteState);
Sprites[spriteState].Add(newSprite);
//use the same sprite for Hover and HoverSelected if latter is not specified
if (spriteState == GUIComponent.ComponentState.HoverSelected && !Sprites.ContainsKey(GUIComponent.ComponentState.HoverSelected))
{
Sprites[GUIComponent.ComponentState.HoverSelected].Add(newSprite);
}
}
else
{
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
{
Sprites[state].Add(newSprite);
}
}
break;
case "size":
break;
default:
string styleName = subElement.Name.ToString().ToLowerInvariant();
if (ChildStyles.ContainsKey(styleName))
{
DebugConsole.ThrowError("UI style \"" + element.Name.ToString() + "\" contains multiple child styles with the same name (\"" + styleName + "\")!");
ChildStyles[styleName] = new GUIComponentStyle(subElement, style);
}
else
{
ChildStyles.Add(styleName, new GUIComponentStyle(subElement, style));
}
break;
}
}
GetSize(element);
}
public void GetSize(XElement element)
{
Point size = new Point(0, 0);
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
size = new Point(
subElement.GetAttributeInt("width", 0),
subElement.GetAttributeInt("height", 0));
break;
}
}
if (size.X > 0) { Width = size.X; }
if (size.Y > 0) { Height = size.Y; }
}
}
}
@@ -0,0 +1,405 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Barotrauma
{
public static class FileSelection
{
private static bool open;
public static bool Open
{
get
{
return open;
}
set
{
if (value && backgroundFrame == null) { Init(); }
if (!value)
{
fileSystemWatcher?.Dispose();
fileSystemWatcher = null;
}
open = value;
}
}
private static GUIFrame backgroundFrame;
private static GUIFrame window;
private static GUIListBox sidebar;
private static GUIListBox fileList;
private static GUITextBox directoryBox;
private static GUITextBox filterBox;
private static GUITextBox fileBox;
private static GUIDropDown fileTypeDropdown;
private static GUIButton openButton;
private static FileSystemWatcher fileSystemWatcher;
private static string currentFileTypePattern;
private static readonly string[] ignoredDrivePrefixes = new string[]
{
"/sys/", "/snap/"
};
private static string currentDirectory;
public static string CurrentDirectory
{
get
{
return currentDirectory;
}
set
{
string[] dirSplit = value.Replace('\\', '/').Split('/');
List<string> dirs = new List<string>();
for (int i = 0; i < dirSplit.Length; i++)
{
if (dirSplit[i].Trim() == "..")
{
if (dirs.Count > 1)
{
dirs.RemoveAt(dirs.Count - 1);
}
}
else if (dirSplit[i].Trim() != ".")
{
dirs.Add(dirSplit[i]);
}
}
currentDirectory = string.Join("/", dirs);
if (!currentDirectory.EndsWith("/"))
{
currentDirectory += "/";
}
fileSystemWatcher?.Dispose();
fileSystemWatcher = new FileSystemWatcher(currentDirectory)
{
Filter = "*",
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName
};
fileSystemWatcher.Created += OnFileSystemChanges;
fileSystemWatcher.Deleted += OnFileSystemChanges;
fileSystemWatcher.Renamed += OnFileSystemChanges;
fileSystemWatcher.EnableRaisingEvents = true;
RefreshFileList();
}
}
public static Action<string> OnFileSelected
{
get;
set;
}
private static void OnFileSystemChanges(object sender, FileSystemEventArgs e)
{
switch (e.ChangeType)
{
case WatcherChangeTypes.Created:
{
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), e.Name)
{
UserData = (bool?)Directory.Exists(e.FullPath)
};
if ((itemFrame.UserData as bool?) ?? false)
{
itemFrame.Text += "/";
}
fileList.Content.RectTransform.SortChildren(SortFiles);
}
break;
case WatcherChangeTypes.Deleted:
{
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == e.Name || tb.Text == e.Name + "/"));
if (itemFrame != null) { fileList.RemoveChild(itemFrame); }
}
break;
case WatcherChangeTypes.Renamed:
{
RenamedEventArgs renameArgs = e as RenamedEventArgs;
var itemFrame = fileList.Content.FindChild(c => (c is GUITextBlock tb) && (tb.Text == renameArgs.OldName || tb.Text == renameArgs.OldName + "/")) as GUITextBlock;
itemFrame.UserData = (bool?)Directory.Exists(e.FullPath);
itemFrame.Text = renameArgs.Name;
if ((itemFrame.UserData as bool?) ?? false)
{
itemFrame.Text += "/";
}
fileList.Content.RectTransform.SortChildren(SortFiles);
}
break;
}
}
private static int SortFiles(RectTransform r1, RectTransform r2)
{
string file1 = (r1.GUIComponent as GUITextBlock)?.Text ?? "";
string file2 = (r2.GUIComponent as GUITextBlock)?.Text ?? "";
bool dir1 = (r1.GUIComponent.UserData as bool?) ?? false;
bool dir2 = (r2.GUIComponent.UserData as bool?) ?? false;
if (dir1 && !dir2)
{
return -1;
}
else if (!dir1 && dir2)
{
return 1;
}
return string.Compare(file1, file2);
}
public static void Init()
{
backgroundFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null)
{
Color = Color.Black * 0.5f,
HoverColor = Color.Black * 0.5f,
SelectedColor = Color.Black * 0.5f,
PressedColor = Color.Black * 0.5f,
};
window = new GUIFrame(new RectTransform(Vector2.One * 0.8f, backgroundFrame.RectTransform, Anchor.Center));
var horizontalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.9f, window.RectTransform, Anchor.Center), true);
sidebar = new GUIListBox(new RectTransform(new Vector2(0.29f, 1.0f), horizontalLayout.RectTransform));
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
if (drive.DriveType == DriveType.Ram) { continue; }
if (ignoredDrivePrefixes.Any(p => drive.Name.StartsWith(p))) { continue; }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), sidebar.Content.RectTransform), drive.Name.Replace('\\','/'));
}
sidebar.OnSelected = (child, userdata) =>
{
CurrentDirectory = (child as GUITextBlock).Text;
return false;
};
//spacing between sidebar and fileListLayout
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), horizontalLayout.RectTransform), style: null);
var fileListLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.7f, 1.0f), horizontalLayout.RectTransform));
var firstRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUIButton(new RectTransform(new Vector2(0.05f, 1.0f), firstRow.RectTransform), "^")
{
OnClicked = MoveToParentDirectory
};
directoryBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), firstRow.RectTransform))
{
OverflowClip = true,
OnEnterPressed = (tb, txt) =>
{
if (Directory.Exists(txt))
{
CurrentDirectory = txt;
return true;
}
else
{
tb.Text = CurrentDirectory;
return false;
}
}
};
filterBox = new GUITextBox(new RectTransform(new Vector2(0.25f, 1.0f), firstRow.RectTransform))
{
OverflowClip = true
};
firstRow.RectTransform.MinSize = new Point(0, firstRow.RectTransform.Children.Max(c => c.MinSize.Y));
filterBox.OnTextChanged += (txtbox, txt) =>
{
RefreshFileList();
return true;
};
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
fileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), fileListLayout.RectTransform))
{
OnSelected = (child, userdata) =>
{
if (userdata == null) { return false; }
var fileName = (child as GUITextBlock).Text;
fileBox.Text = fileName;
if (PlayerInput.DoubleClicked())
{
bool isDir = (userdata as bool?).Value;
if (isDir)
{
CurrentDirectory += fileName;
}
else
{
OnFileSelected?.Invoke(CurrentDirectory + fileName);
Open = false;
}
}
return true;
}
};
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
var thirdRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
fileBox = new GUITextBox(new RectTransform(new Vector2(0.7f, 1.0f), thirdRow.RectTransform))
{
OnEnterPressed = (tb, txt) => openButton?.OnClicked?.Invoke(openButton, null) ?? false
};
fileTypeDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1.0f), thirdRow.RectTransform), dropAbove: true)
{
OnSelected = (child, userdata) =>
{
currentFileTypePattern = (child as GUITextBlock).UserData as string;
RefreshFileList();
return true;
}
};
fileTypeDropdown.Select(4);
//spacing between rows
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), fileListLayout.RectTransform), style: null);
var fourthRow = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), fileListLayout.RectTransform), true);
//padding for open/cancel buttons
new GUIFrame(new RectTransform(new Vector2(0.7f, 1.0f), fourthRow.RectTransform), style: null);
openButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("opensubbutton"))
{
OnClicked = (btn, obj) =>
{
if (Directory.Exists(Path.Combine(CurrentDirectory, fileBox.Text)))
{
CurrentDirectory += fileBox.Text;
}
if (!File.Exists(CurrentDirectory + fileBox.Text)) { return false; }
OnFileSelected?.Invoke(CurrentDirectory + fileBox.Text);
Open = false;
return false;
}
};
new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), fourthRow.RectTransform), TextManager.Get("cancel"))
{
OnClicked = (btn, obj) =>
{
Open = false;
return false;
}
};
CurrentDirectory = Directory.GetCurrentDirectory();
}
public static void ClearFileTypeFilters()
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.ClearChildren();
}
public static void AddFileTypeFilter(string name, string pattern)
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.AddItem(name + " (" + pattern + ")", pattern);
}
public static void SelectFileTypeFilter(string pattern)
{
if (backgroundFrame == null) { Init(); }
fileTypeDropdown.SelectItem(pattern);
}
public static void RefreshFileList()
{
fileList.Content.ClearChildren();
fileList.BarScroll = 0.0f;
try
{
var directories = Directory.EnumerateDirectories(currentDirectory, "*" + filterBox.Text + "*");
foreach (var directory in directories)
{
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = (bool?)true
};
var folderIcon = new GUIImage(new RectTransform(new Point((int)(itemFrame.Rect.Height * 0.8f)), itemFrame.RectTransform, Anchor.CenterLeft)
{
AbsoluteOffset = new Point((int)(itemFrame.Rect.Height * 0.25f), 0)
}, style: "OpenButton", scaleToFit: true);
itemFrame.Padding = new Vector4(folderIcon.Rect.Width * 1.5f, itemFrame.Padding.Y, itemFrame.Padding.Z, itemFrame.Padding.W);
}
IEnumerable<string> files = null;
foreach (string pattern in currentFileTypePattern.Split(','))
{
string patternTrimmed = pattern.Trim();
patternTrimmed = "*" + filterBox.Text + "*" + patternTrimmed;
if (files == null)
{
files = Directory.EnumerateFiles(currentDirectory, patternTrimmed);
}
else
{
files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
}
}
foreach (var file in files)
{
string txt = file;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = (bool?)false
};
}
}
catch (Exception e)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), "Could not list items in directory: " + e.Message)
{
CanBeFocused = false
};
}
directoryBox.Text = currentDirectory;
fileBox.Text = "";
fileList.Deselect();
}
public static bool MoveToParentDirectory(GUIButton button, object userdata)
{
string dir = CurrentDirectory;
if (dir.EndsWith("/")) { dir = dir.Substring(0, dir.Length - 1); }
int index = dir.LastIndexOf("/");
if (index < 0) { return false; }
CurrentDirectory = CurrentDirectory.Substring(0, index+1);
return false;
}
public static void AddToGUIUpdateList()
{
if (!Open) { return; }
backgroundFrame?.AddToGUIUpdateList();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,256 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
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;
public delegate bool OnPressedHandler();
public OnPressedHandler OnPressed;
public delegate bool OnButtonDownHandler();
public OnButtonDownHandler OnButtonDown;
public bool CanBeSelected = true;
public override bool Enabled
{
get
{
return enabled;
}
set
{
if (value == enabled) { return; }
enabled = frame.Enabled = textBlock.Enabled = value;
}
}
public override Color Color
{
get { return base.Color; }
set
{
base.Color = value;
frame.Color = value;
}
}
public override Color HoverColor
{
get { return base.HoverColor; }
set
{
base.HoverColor = value;
frame.HoverColor = value;
}
}
public override Color SelectedColor
{
get
{
return base.SelectedColor;
}
set
{
base.SelectedColor = value;
frame.SelectedColor = value;
}
}
public override Color PressedColor
{
get
{
return base.PressedColor;
}
set
{
base.PressedColor = value;
frame.PressedColor = value;
}
}
public override Color OutlineColor
{
get { return base.OutlineColor; }
set
{
base.OutlineColor = value;
if (frame != null) frame.OutlineColor = value;
}
}
public Color TextColor
{
get { return textBlock.TextColor; }
set { textBlock.TextColor = value; }
}
public Color HoverTextColor
{
get { return textBlock.HoverTextColor; }
set { textBlock.HoverTextColor = value; }
}
public override float FlashTimer
{
get { return Frame.FlashTimer; }
}
public override ScalableFont Font
{
get
{
return (textBlock == null) ? GUI.Font : textBlock.Font;
}
set
{
base.Font = value;
if (textBlock != null) textBlock.Font = value;
}
}
public string Text
{
get { return textBlock.Text; }
set { textBlock.Text = value; }
}
public bool ForceUpperCase
{
get { return textBlock.ForceUpperCase; }
set { textBlock.ForceUpperCase = value; }
}
public override string ToolTip
{
get
{
return base.ToolTip;
}
set
{
base.ToolTip = value;
textBlock.ToolTip = value;
}
}
public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
{
CanBeFocused = true;
HoverCursor = CursorState.Hand;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT), style) { CanBeFocused = false };
if (style != null) { GUI.Style.Apply(frame, style == "" ? "GUIButton" : style); }
if (color.HasValue)
{
this.color = frame.Color = color.Value;
}
textBlock = new GUITextBlock(new RectTransform(Vector2.One, rectT, Anchor.Center), text, textAlignment: textAlignment, style: null)
{
TextColor = this.style == null ? Color.Black : this.style.TextColor,
HoverTextColor = this.style == null ? Color.Black : this.style.HoverTextColor,
SelectedTextColor = this.style == null ? Color.Black : this.style.SelectedTextColor,
CanBeFocused = false
};
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(textBlock.Text).Y));
RectTransform.MinSize = textBlock.RectTransform.MinSize = new Point(0, System.Math.Max(rectT.MinSize.Y, Rect.Height));
TextBlock.SetTextPos();
}
GUI.Style.Apply(textBlock, "", this);
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback
if (TextManager.IsCJK(textBlock.Text) && !textBlock.Font.IsCJK)
{
textBlock.Font = GUI.CJKFont;
}
Enabled = true;
}
public override void ApplyStyle(GUIComponentStyle style)
{
base.ApplyStyle(style);
if (frame != null) { frame.ApplyStyle(style); }
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
{
Frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectInflate);
}
protected override void Draw(SpriteBatch spriteBatch)
{
//do nothing
}
protected override void Update(float deltaTime)
{
if (!Visible) return;
base.Update(deltaTime);
if (Rect.Contains(PlayerInput.MousePosition) && CanBeSelected && CanBeFocused && Enabled && GUI.IsMouseOn(this))
{
State = Selected ?
ComponentState.HoverSelected :
ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown())
{
OnButtonDown?.Invoke();
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
if (OnPressed != null)
{
if (OnPressed())
{
State = ComponentState.Pressed;
}
}
else
{
State = ComponentState.Pressed;
}
}
else if (PlayerInput.PrimaryMouseButtonClicked())
{
GUI.PlayUISound(GUISoundType.Click);
if (OnClicked != null)
{
if (OnClicked(this, UserData))
{
State = ComponentState.Selected;
}
}
else
{
Selected = !Selected;
}
}
}
else
{
State = Selected ? ComponentState.Selected : ComponentState.None;
}
foreach (GUIComponent child in Children)
{
child.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());
}
}
}
File diff suppressed because it is too large Load Diff
@@ -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, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
OnDraw?.Invoke(spriteBatch, this);
if (HideElementsOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
}
protected override void Update(float deltaTime)
{
if (Visible) OnUpdate?.Invoke(deltaTime, this);
}
}
}
@@ -0,0 +1,433 @@
using EventInput;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
public class GUIDropDown : GUIComponent, IKeyboardSubscriber
{
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
public OnSelectedHandler OnSelected;
public OnSelectedHandler OnDropped;
private readonly GUIButton button;
private readonly GUIImage icon;
private readonly GUIListBox listBox;
private RectTransform currentHighestParent;
private List<RectTransform> parentHierarchy = new List<RectTransform>();
private bool selectMultiple;
public bool Dropped { get; set; }
public object SelectedItemData
{
get
{
if (listBox.SelectedComponent == null) return null;
return listBox.SelectedComponent.UserData;
}
}
public override bool Enabled
{
get { return listBox.Enabled; }
set { listBox.Enabled = value; }
}
public bool ButtonEnabled
{
get { return button.Enabled; }
set
{
button.Enabled = value;
if (icon != null) { icon.Enabled = value; }
}
}
public GUIComponent SelectedComponent
{
get { return listBox.SelectedComponent; }
}
// TODO: fix implicit hiding
public bool Selected
{
get
{
return Dropped;
}
set
{
Dropped = value;
}
}
public GUIListBox ListBox
{
get { return listBox; }
}
public object SelectedData
{
get
{
return listBox.SelectedComponent?.UserData;
}
}
public int SelectedIndex
{
get
{
if (listBox.SelectedComponent == null) return -1;
return listBox.Content.GetChildIndex(listBox.SelectedComponent);
}
}
public Color ButtonTextColor
{
get { return button.TextColor; }
set { button.TextColor = value; }
}
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.Up:
case Keys.Down:
listBox.ReceiveSpecialInput(key);
GUI.KeyboardDispatcher.Subscriber = this;
break;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}
}
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
{
return base.ToolTip;
}
set
{
base.ToolTip = value;
button.ToolTip = value;
listBox.ToolTip = value;
}
}
public GUIDropDown(RectTransform rectT, string text = "", int elementCount = 4, string style = "", bool selectMultiple = false, bool dropAbove = false) : base(style, rectT)
{
HoverCursor = CursorState.Hand;
CanBeFocused = true;
this.selectMultiple = selectMultiple;
button = new GUIButton(new RectTransform(Vector2.One, rectT), text, Alignment.CenterLeft, style: "GUIDropDown")
{
OnClicked = OnClicked
};
GUI.Style.Apply(button, "", this);
button.TextBlock.SetTextPos();
Anchor listAnchor = dropAbove ? Anchor.TopCenter : Anchor.BottomCenter;
Pivot listPivot = dropAbove ? Pivot.BottomCenter : Pivot.TopCenter;
listBox = new GUIListBox(new RectTransform(new Point(Rect.Width, Rect.Height * MathHelper.Clamp(elementCount, 2, 10)), rectT, listAnchor, listPivot)
{ IsFixedSize = false }, style: null)
{
Enabled = !selectMultiple,
OnSelected = SelectItem
};
GUI.Style.Apply(listBox, "GUIListBox", this);
GUI.Style.Apply(listBox.ContentBackground, "GUIListBox", this);
if (button.Style.ChildStyles.ContainsKey("dropdownicon"))
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), button.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, null, scaleToFit: true);
icon.ApplyStyle(button.Style.ChildStyles["dropdownicon"]);
}
currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
rectT.ParentChanged += (RectTransform newParent) =>
{
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
if (newParent != null)
{
currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
}
};
}
/// <summary>
/// Finds the component after which the listbox should be drawn
/// //(= the component highest in the hierarchy, to get the listbox
/// //to be rendered on top of all of it's children)
/// </summary>
private RectTransform FindHighestParent()
{
parentHierarchy.Clear();
//collect entire parent hierarchy to a list
parentHierarchy = new List<RectTransform>() { RectTransform.Parent };
RectTransform parent = parentHierarchy.Last();
while (parent?.Parent != null)
{
parentHierarchy.Add(parent.Parent);
parent = parent.Parent;
}
//find the highest parent that has a guicomponent with a style
//(and so should be rendered and not just some empty parent/root element used for constructing a layout)
for (int i = parentHierarchy.Count - 1; i > 0; i--)
{
if (parentHierarchy[i] is GUICanvas ||
parentHierarchy[i].GUIComponent == null ||
parentHierarchy[i].GUIComponent.Style == null ||
parentHierarchy[i].GUIComponent == Screen.Selected?.Frame)
{
parentHierarchy.RemoveAt(i);
}
else
{
break;
}
}
return parentHierarchy.Last();
}
public void AddItem(string text, object userData = null, string 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);
// TODO: The callback is called at least twice, remove this?
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()
{
listBox.ClearChildren();
}
public IEnumerable<GUIComponent> GetChildren()
{
return listBox.Content.Children;
}
private bool SelectItem(GUIComponent component, object obj)
{
if (selectMultiple)
{
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;
}
Dropped = false;
// TODO: OnSelected can be called multiple times and when it shouldn't be called -> turn into an event so that nobody else can call it.
OnSelected?.Invoke(component, component.UserData);
return true;
}
public void SelectItem(object userData)
{
if (selectMultiple)
{
SelectItem(listBox.Content.FindChild(userData), userData);
}
else
{
listBox.Select(userData);
}
}
public void Select(int index)
{
if (selectMultiple)
{
var child = listBox.Content.GetChild(index);
if (child != null)
{
SelectItem(null, child.UserData);
}
}
else
{
listBox.Select(index);
}
}
private bool wasOpened;
private bool OnClicked(GUIComponent component, object obj)
{
if (wasOpened) return false;
wasOpened = true;
Dropped = !Dropped;
if (Dropped && Enabled)
{
OnDropped?.Invoke(this, userData);
listBox.UpdateScrollBarSize();
GUI.KeyboardDispatcher.Subscriber = this;
}
else if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
return true;
}
private void AddListBoxToGUIUpdateList(GUIComponent parent)
{
//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 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.PrimaryMouseButtonClicked())
{
Rectangle listBoxRect = listBox.Rect;
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
{
Dropped = false;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
}
}
}
}
}
@@ -0,0 +1,33 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
namespace Barotrauma
{
public class GUIFrame : GUIComponent
{
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
{
Enabled = true;
if (color.HasValue)
{
this.color = color.Value;
}
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
Color currColor = GetColor(State);
if (sprites == null || !sprites.Any(s => s.Value.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);
}
}
}
}
@@ -0,0 +1,154 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
namespace Barotrauma
{
public class GUIImage : GUIComponent
{
public float Rotation;
private Sprite sprite;
private Rectangle sourceRect;
private bool crop;
private bool scaleToFit;
public bool Crop
{
get
{
return crop;
}
set
{
crop = value;
if (crop)
{
sourceRect.Width = Math.Min(sprite.SourceRect.Width, Rect.Width);
sourceRect.Height = Math.Min(sprite.SourceRect.Height, Rect.Height);
}
}
}
public float Scale
{
get;
set;
}
public Rectangle SourceRect
{
get { return sourceRect; }
set { sourceRect = value; }
}
public Sprite Sprite
{
get { return sprite; }
set
{
if (sprite == value) return;
sprite = value;
sourceRect = sprite.SourceRect;
if (scaleToFit) RecalculateScale();
}
}
public BlendState BlendState;
public ComponentState? OverrideState = null;
public GUIImage(RectTransform rectT, string style, bool scaleToFit = false)
: this(rectT, null, null, scaleToFit, style)
{
}
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
: this(rectT, sprite, sourceRect, scaleToFit, null)
{
}
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
{
this.scaleToFit = scaleToFit;
sprite?.EnsureLazyLoaded();
Sprite = sprite;
if (sourceRect.HasValue)
{
this.sourceRect = sourceRect.Value;
}
else
{
this.sourceRect = sprite == null ? Rectangle.Empty : sprite.SourceRect;
}
if (style == null)
{
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
}
if (!scaleToFit)
{
Scale = 1.0f;
}
else
{
rectT.SizeChanged += RecalculateScale;
}
Enabled = true;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) return;
if (Parent != null) { State = Parent.State; }
if (OverrideState != null) { State = OverrideState.Value; }
Color currentColor = GetColor(State);
if (BlendState != null)
{
spriteBatch.End();
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState);
}
if (style != null)
{
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, currentColor * (currentColor.A / 255.0f), Rotation, uiSprite.Sprite.size / 2,
Scale * scale, SpriteEffects, 0.0f);
}
else
{
uiSprite.Draw(spriteBatch, Rect, currentColor * (currentColor.A / 255.0f), SpriteEffects);
}
}
}
else if (sprite?.Texture != null)
{
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, sprite.size / 2,
Scale, SpriteEffects, 0.0f);
}
if (BlendState != null)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
}
private void RecalculateScale()
{
Scale = sprite == null || 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,197 @@
using Microsoft.Xna.Framework;
using System;
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; }
set
{
if (value) { needsToRecalculate = true; }
}
}
public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
{
CanBeFocused = false;
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)
{
foreach (RectTransform child in RectTransform.Children)
{
if (child.GUIComponent.IgnoreLayoutGroups) { continue; }
if (child.ScaleBasis == ScaleBasis.BothHeight) { child.MinSize = new Point(child.Rect.Height, child.MinSize.Y); }
if (child.ScaleBasis == ScaleBasis.BothWidth) { child.MinSize = new Point(child.MinSize.X, child.Rect.Width); }
if (child.ScaleBasis == ScaleBasis.Smallest)
{
if (Rect.Width < Rect.Height)
{
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
}
else
{
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
}
}
if (child.ScaleBasis == ScaleBasis.Largest)
{
if (Rect.Width > Rect.Height)
{
child.MinSize = new Point(child.MinSize.X, child.Rect.Width);
}
else
{
child.MinSize = new Point(child.Rect.Height, child.MinSize.Y);
}
}
}
float minSize = RectTransform.Children
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
.Sum(c => isHorizontal ? (c.IsFixedSize ? c.NonScaledSize.X : c.MinSize.X) : (c.IsFixedSize ? c.NonScaledSize.Y : c.MinSize.Y));
float totalSize = RectTransform.Children
.Where(c => !c.GUIComponent.IgnoreLayoutGroups)
.Sum(c => isHorizontal ?
(c.IsFixedSize ? c.Rect.Width : MathHelper.Clamp(c.Rect.Width, c.MinSize.X, c.MaxSize.X)) :
(c.IsFixedSize ? c.Rect.Height : MathHelper.Clamp(c.Rect.Height, c.MinSize.Y, c.MaxSize.Y)));
float thisSize = (isHorizontal ? Rect.Width : Rect.Height);
totalSize +=
(RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) *
(absoluteSpacing + relativeSpacing * thisSize);
stretchFactor = totalSize <= 0.0f || minSize >= thisSize ?
1.0f :
(thisSize - minSize) / (totalSize - minSize);
}
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);
if (child.IsFixedSize)
{
absPos += child.NonScaledSize.X + absoluteSpacing;
}
else
{
absPos += (int)(MathHelper.Clamp(child.Rect.Width * stretchFactor, child.MinSize.X, child.MaxSize.X) + (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);
if (child.IsFixedSize)
{
absPos += child.NonScaledSize.Y + absoluteSpacing;
}
else
{
absPos += (int)(MathHelper.Clamp(child.Rect.Height * stretchFactor, child.MinSize.Y, child.MaxSize.Y) + (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();
}
}
}
}
@@ -0,0 +1,818 @@
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, IKeyboardSubscriber
{
protected List<GUIComponent> selected;
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
public OnSelectedHandler OnSelected;
public delegate object CheckSelectedHandler();
public CheckSelectedHandler CheckSelected;
public delegate void OnRearrangedHandler(GUIListBox listBox, object obj);
public OnRearrangedHandler OnRearranged;
/// <summary>
/// A frame drawn behind the content of the listbox
/// </summary>
public GUIFrame ContentBackground { get; private set; }
/// <summary>
/// A frame that contains the contents of the listbox. The frame itself is not rendered.
/// </summary>
public GUIFrame Content { get; private set; }
public GUIScrollBar ScrollBar { get; private set; }
private Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>();
private int totalSize;
private bool childrenNeedsRecalculation;
private bool scrollBarNeedsRecalculation;
private bool dimensionsNeedsRecalculation;
// TODO: Define in styles?
private int ScrollBarSize
{
get
{
//use the average of the "desired" size and the scaled size
//scaling the bar linearly with the resolution tends to make them too large on large resolutions
float desiredSize = 25.0f;
float scaledSize = desiredSize * GUI.Scale;
return (int)((desiredSize + scaledSize) / 2.0f);
}
}
public bool SelectMultiple;
public bool HideChildrenOutsideFrame = true;
private bool useGridLayout;
public bool UseGridLayout
{
get { return useGridLayout; }
set
{
if (useGridLayout == value) return;
useGridLayout = value;
childrenNeedsRecalculation = true;
scrollBarNeedsRecalculation = true;
}
}
private Vector4? overridePadding;
public Vector4 Padding
{
get
{
if (overridePadding.HasValue) { return overridePadding.Value; }
if (Style == null) { return Vector4.Zero; }
return Style.Padding;
}
set { overridePadding = value; }
}
public GUIComponent SelectedComponent
{
get
{
return selected.FirstOrDefault();
}
}
// TODO: fix implicit hiding
public bool Selected { get; set; }
public List<GUIComponent> AllSelected
{
get { return selected; }
}
public object SelectedData
{
get
{
return SelectedComponent?.UserData;
}
}
public int SelectedIndex
{
get
{
if (SelectedComponent == null) return -1;
return Content.RectTransform.GetChildIndex(SelectedComponent.RectTransform);
}
}
public float BarScroll
{
get { return ScrollBar.BarScroll; }
set { ScrollBar.BarScroll = value; }
}
public float BarSize
{
get { return ScrollBar.BarSize; }
}
public float TotalSize
{
get { return totalSize; }
}
public int Spacing { get; set; }
public override Color Color
{
get
{
return base.Color;
}
set
{
base.Color = value;
Content.Color = value;
}
}
/// <summary>
/// Disables the scroll bar without hiding it.
/// </summary>
public bool ScrollBarEnabled { get; set; } = true;
public bool KeepSpaceForScrollBar { get; set; }
public bool ScrollBarVisible
{
get
{
return ScrollBar.Visible;
}
set
{
if (ScrollBar.Visible == value) { return; }
ScrollBar.Visible = value;
dimensionsNeedsRecalculation = true;
}
}
/// <summary>
/// Automatically hides the scroll bar when the content fits in.
/// </summary>
public bool AutoHideScrollBar { get; set; } = true;
private bool IsScrollBarOnDefaultSide { get; set; }
public bool CanDragElements { get; set; } = false;
private GUIComponent draggedElement;
private Rectangle draggedReferenceRectangle;
private Point draggedReferenceOffset;
public GUIComponent DraggedElement => draggedElement;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true) : base(style, rectT)
{
CanBeFocused = true;
selected = new List<GUIComponent>();
ContentBackground = new GUIFrame(new RectTransform(Vector2.One, rectT), style)
{
CanBeFocused = false
};
Content = new GUIFrame(new RectTransform(Vector2.One, ContentBackground.RectTransform), style: null)
{
CanBeFocused = false
};
Content.RectTransform.ChildrenChanged += (_) =>
{
scrollBarNeedsRecalculation = true;
childrenNeedsRecalculation = true;
};
if (style != null)
{
GUI.Style.Apply(ContentBackground, "", this);
}
if (color.HasValue)
{
this.color = color.Value;
}
IsScrollBarOnDefaultSide = isScrollBarOnDefaultSide;
Point size;
Anchor anchor;
if (isHorizontal)
{
size = new Point((int)(Rect.Width - Padding.X - Padding.Z), (int)(ScrollBarSize * GUI.Scale));
anchor = isScrollBarOnDefaultSide ? Anchor.BottomCenter : Anchor.TopCenter;
}
else
{
// TODO: Should this be multiplied by the GUI.Scale as well?
size = new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W));
anchor = isScrollBarOnDefaultSide ? Anchor.CenterRight : Anchor.CenterLeft;
}
ScrollBar = new GUIScrollBar(
new RectTransform(size, rectT, anchor)
{
AbsoluteOffset = isHorizontal ?
new Point(0, IsScrollBarOnDefaultSide ? (int)Padding.W : (int)Padding.Y) :
new Point(IsScrollBarOnDefaultSide ? (int)Padding.Z : (int)Padding.X, 0)
},
isHorizontal: isHorizontal);
UpdateScrollBarSize();
Enabled = true;
ScrollBar.BarScroll = 0.0f;
RectTransform.ScaleChanged += () => dimensionsNeedsRecalculation = true;
RectTransform.SizeChanged += () => dimensionsNeedsRecalculation = true;
UpdateDimensions();
}
private void UpdateDimensions()
{
dimensionsNeedsRecalculation = false;
ContentBackground.RectTransform.Resize(Rect.Size);
bool reduceScrollbarSize = KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible;
Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size;
Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W)));
if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); }
Content.RectTransform.AbsoluteOffset = new Point(
IsScrollBarOnDefaultSide ? (int)Padding.X : (int)Padding.Z,
IsScrollBarOnDefaultSide ? (int)Padding.Y : (int)Padding.W);
ScrollBar.RectTransform.Resize(ScrollBar.IsHorizontal ?
new Point((int)(Rect.Width - Padding.X - Padding.Z), ScrollBarSize) :
new Point(ScrollBarSize, (int)(Rect.Height - Padding.Y - Padding.W)));
ScrollBar.RectTransform.AbsoluteOffset = ScrollBar.IsHorizontal ?
new Point(0, (int)Padding.W) :
new Point((int)Padding.Z, 0);
UpdateScrollBarSize();
}
public void Select(object userData, bool force = false, bool autoScroll = true)
{
var children = Content.Children;
int i = 0;
foreach (GUIComponent child in children)
{
if ((child.UserData != null && child.UserData.Equals(userData)) ||
(child.UserData == null && userData == null))
{
Select(i, force, autoScroll);
if (!SelectMultiple) return;
}
i++;
}
}
private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize)
=> isHorizontal ? new Point(Rect.Width, Rect.Height - scrollBarSize) : new Point(Rect.Width - scrollBarSize, Rect.Height);
private void RepositionChildren()
{
int x = 0, y = 0;
if (ScrollBar.BarSize < 1.0f)
{
if (ScrollBar.IsHorizontal)
{
x -= (int)((totalSize - Content.Rect.Width) * ScrollBar.BarScroll);
}
else
{
y -= (int)((totalSize - Content.Rect.Height) * ScrollBar.BarScroll);
}
}
for (int i = 0; i < Content.CountChildren; i++)
{
GUIComponent child = Content.GetChild(i);
if (!child.Visible) { continue; }
if (RectTransform != null)
{
if (child != draggedElement && (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 != draggedElement && (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 != draggedElement && (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()
{
//dragging
if (CanDragElements && draggedElement != null)
{
if (!PlayerInput.LeftButtonHeld())
{
OnRearranged?.Invoke(this, draggedElement.UserData);
draggedElement = null;
RepositionChildren();
}
else
{
draggedElement.RectTransform.AbsoluteOffset = draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y);
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform);
int currIndex = index;
while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top)
{
currIndex--;
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height;
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom)
{
currIndex++;
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height;
draggedReferenceOffset.Y += draggedReferenceRectangle.Height;
}
if (currIndex != index)
{
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
}
return;
}
}
for (int i = 0; i < Content.CountChildren; i++)
{
var child = Content.RectTransform.GetChild(i)?.GUIComponent;
if (child == null) continue;
// selecting
if (Enabled && CanBeFocused && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition))
{
child.State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked())
{
Select(i, autoScroll: false);
}
if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child)
{
draggedElement = child;
draggedReferenceRectangle = child.Rect;
draggedReferenceOffset = child.RectTransform.AbsoluteOffset;
}
}
else if (selected.Contains(child))
{
child.State = ComponentState.Selected;
if (CheckSelected != null)
{
if (CheckSelected() != child.UserData) selected.Remove(child);
}
}
else
{
child.State = ComponentState.None;
}
}
}
public override void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
if (!Visible) { return; }
if (!ignoreChildren)
{
foreach (GUIComponent child in Children)
{
if (child == Content || child == ScrollBar || child == ContentBackground) { continue; }
child.AddToGUIUpdateList(ignoreChildren, order);
}
}
foreach (GUIComponent child in Content.Children)
{
if (!childVisible.ContainsKey(child)) { childVisible[child] = child.Visible; }
if (childVisible[child] != child.Visible)
{
childVisible[child] = child.Visible;
childrenNeedsRecalculation = true;
scrollBarNeedsRecalculation = true;
break;
}
}
if (childrenNeedsRecalculation)
{
RecalculateChildren();
childVisible.Clear();
}
UpdateOrder = order;
GUI.AddToUpdateList(this);
if (ignoreChildren)
{
OnAddedToGUIUpdateList?.Invoke(this);
return;
}
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;
child.AddToGUIUpdateList(false, order);
}
if (ScrollBar.Enabled)
{
ScrollBar.AddToGUIUpdateList(false, order);
}
OnAddedToGUIUpdateList?.Invoke(this);
}
public void RecalculateChildren()
{
foreach (GUIComponent child in Content.Children)
{
ClampChildMouseRects(child);
}
RepositionChildren();
childrenNeedsRecalculation = false;
}
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);
}
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
UpdateChildrenRect();
RepositionChildren();
if (scrollBarNeedsRecalculation)
{
UpdateScrollBarSize();
}
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0)
{
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
}
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
if (AutoHideScrollBar)
{
ScrollBarVisible = ScrollBar.Enabled;
}
if (dimensionsNeedsRecalculation)
{
UpdateDimensions();
}
}
public void SelectNext(bool force = false, bool autoScroll = true)
{
int index = SelectedIndex + 1;
while (index < Content.CountChildren)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index++;
}
}
public void SelectPrevious(bool force = false, bool autoScroll = true)
{
int index = SelectedIndex - 1;
while (index >= 0)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index--;
}
}
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)
{
// TODO: The callback is called twice, fix this!
wasSelected = force || OnSelected(child, child.UserData);
}
if (!wasSelected) { return; }
if (SelectMultiple)
{
if (selected.Contains(child))
{
selected.Remove(child);
}
else
{
selected.Add(child);
}
}
else
{
selected.Clear();
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()
{
scrollBarNeedsRecalculation = false;
if (Content == null) { return; }
totalSize = 0;
var children = Content.Children.Where(c => c.Visible);
if (useGridLayout)
{
int pos = 0;
foreach (GUIComponent child in children)
{
if (ScrollBar.IsHorizontal)
{
if (pos + child.Rect.Height + Spacing > Content.Rect.Height)
{
pos = 0;
totalSize += child.Rect.Width + Spacing;
}
pos += child.Rect.Height + Spacing;
if (child == children.Last())
{
totalSize += child.Rect.Width + Spacing;
}
}
else
{
if (pos + child.Rect.Width + Spacing > Content.Rect.Width)
{
pos = 0;
totalSize += child.Rect.Height + Spacing;
}
pos += child.Rect.Width + Spacing;
if (child == children.Last())
{
totalSize += child.Rect.Height + Spacing;
}
}
}
}
else
{
foreach (GUIComponent child in children)
{
totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
}
totalSize += Content.CountChildren * Spacing;
}
float minScrollBarSize = 20.0f;
ScrollBar.BarSize = ScrollBar.IsHorizontal ?
Math.Max(Math.Min(Content.Rect.Width / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Width) :
Math.Max(Math.Min(Content.Rect.Height / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Height);
}
public override void ClearChildren()
{
Content.ClearChildren();
selected.Clear();
}
public override void RemoveChild(GUIComponent child)
{
if (child == null) { return; }
child.RectTransform.Parent = null;
if (selected.Contains(child)) selected.Remove(child);
UpdateScrollBarSize();
}
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; }
ContentBackground.DrawManually(spriteBatch, alsoChildren: false);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
RasterizerState prevRasterizerState = spriteBatch.GraphicsDevice.RasterizerState;
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Content.Rect);
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
var children = Content.Children;
int lastVisible = 0;
int i = 0;
foreach (GUIComponent child in Content.Children)
{
if (!child.Visible) continue;
if (!IsChildInsideFrame(child))
{
if (lastVisible > 0) break;
continue;
}
lastVisible = i;
child.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
i++;
}
if (HideChildrenOutsideFrame)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: prevRasterizerState);
}
if (ScrollBarVisible)
{
ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
}
}
private bool IsChildInsideFrame(GUIComponent child)
{
if (child == null) { return false; }
if (ScrollBar.IsHorizontal)
{
if (child.Rect.Right < Content.Rect.X) { return false; }
if (child.Rect.X > Content.Rect.Right) { return false; }
}
else
{
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;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}
}
}
}
@@ -0,0 +1,97 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class GUIMessage
{
private ColoredText coloredText;
private Vector2 pos;
private float lifeTime;
private Vector2 size;
public readonly bool WorldSpace;
public string Text
{
get { return coloredText.Text; }
}
public Color Color
{
get { return coloredText.Color; }
}
public Vector2 Pos
{
get { return pos; }
set { pos = value; }
}
public Vector2 Velocity
{
get;
private set;
}
public Vector2 Size
{
get { return size; }
}
public Vector2 Origin;
public float Timer;
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);
this.lifeTime = lifeTime;
Timer = lifeTime;
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.Bottom))
Origin.Y += size.Y * 0.5f;
}
}
}
@@ -0,0 +1,306 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
public class GUIMessageBox : GUIFrame
{
public static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
private static int DefaultWidth
{
get { return Math.Max(400, 400 * (GameMain.GraphicsWidth / 1920)); }
}
private float inGameCloseTimer = 0.0f;
private const float inGameCloseTime = 15f;
public enum Type
{
Default,
InGame
}
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 string Tag { get; private set; }
public GUIImage Icon
{
get;
private set;
}
public Color IconColor
{
get { return Icon == null ? Color.White : Icon.Color; }
set
{
if (Icon == null) { return; }
Icon.Color = value;
}
}
private bool alwaysVisible;
private float openState;
private bool closing;
private Type type;
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
public GUIMessageBox(string headerText, string text, Vector2? relativeSize = null, Point? minSize = null)
: this(headerText, text, new string[] { "OK" }, relativeSize, minSize)
{
this.Buttons[0].OnClicked = Close;
}
public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null)
: base(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
{
int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0;
if (relativeSize.HasValue)
{
width = (int)(GameMain.GraphicsWidth * relativeSize.Value.X);
height = (int)(GameMain.GraphicsHeight * relativeSize.Value.Y);
}
if (minSize.HasValue)
{
width = Math.Max(width, minSize.Value.X);
if (height > 0)
{
height = Math.Max(height, minSize.Value.Y);
}
}
InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, type == Type.InGame ? Anchor.TopCenter : Anchor.Center) { IsFixedSize = false }, style: null);
GUI.Style.Apply(InnerFrame, "", this);
this.type = type;
Tag = tag;
if (type == Type.Default)
{
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform),
headerText, font: GUI.SubHeadingFont, textAlignment: Alignment.Center, wrap: true);
GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text))
{
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true;
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), Content.RectTransform, Anchor.BottomCenter), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = 5,
IgnoreLayoutGroups = true
};
int buttonSize = 35;
var buttonStyle = GUI.Style.GetComponentStyle("GUIButton");
if (buttonStyle != null && buttonStyle.Height.HasValue)
{
buttonSize = buttonStyle.Height.Value;
}
buttonContainer.RectTransform.NonScaledSize = buttonContainer.RectTransform.MinSize = buttonContainer.RectTransform.MaxSize =
new Point(buttonContainer.Rect.Width, (int)((buttonSize + 5) * buttons.Length));
buttonContainer.RectTransform.IsFixedSize = true;
if (height == 0)
{
height += Header.Rect.Height + Content.AbsoluteSpacing;
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
height += buttonContainer.Rect.Height + 20;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize =
new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + (int)(50 * GUI.yScale)));
Content.RectTransform.NonScaledSize =
new Point(Content.Rect.Width, height);
}
Buttons = new List<GUIButton>(buttons.Length);
for (int i = 0; i < buttons.Length; i++)
{
var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]);
Buttons.Add(button);
}
}
else if (type == Type.InGame)
{
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
alwaysVisible = true;
CanBeFocused = false;
GUI.Style.Apply(InnerFrame, "", this);
var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.02f
};
if (icon != null)
{
Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), icon, scaleToFit: true);
}
Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
Buttons = new List<GUIButton>(1)
{
new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
style: "GUIButtonHorizontalArrow")
{
OnClicked = Close
}
};
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
GUI.Style.Apply(Header, "", this);
Header.RectTransform.MinSize = new Point(0, Header.Rect.Height);
if (!string.IsNullOrWhiteSpace(text))
{
Text = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), text, textAlignment: textAlignment, wrap: true);
GUI.Style.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true;
}
if (height == 0)
{
height += Header.Rect.Height + Content.AbsoluteSpacing;
height += (Text == null ? 0 : Text.Rect.Height) + Content.AbsoluteSpacing;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize =
new Point(InnerFrame.Rect.Width, (int)Math.Max(height / Content.RectTransform.RelativeSize.Y, height + (int)(50 * GUI.yScale)));
Content.RectTransform.NonScaledSize =
new Point(Content.Rect.Width, height);
}
Buttons[0].RectTransform.MaxSize = new Point(Math.Min(Buttons[0].Rect.Width, Buttons[0].Rect.Height));
}
MessageBoxes.Add(this);
}
public static void AddActiveToGUIUpdateList()
{
for (int i = 0; i < MessageBoxes.Count; i++)
{
if (MessageBoxes[i] is GUIMessageBox alwaysVisibleMsgBox && alwaysVisibleMsgBox.alwaysVisible)
{
alwaysVisibleMsgBox.AddToGUIUpdateList();
break;
}
}
for (int i = MessageBoxes.Count - 1; i >= 0; i--)
{
if (MessageBoxes[i].UserData as string == "verificationprompt" ||
MessageBoxes[i].UserData as string == "bugreporter")
{
continue;
}
if (!(MessageBoxes[i] is GUIMessageBox msgBox) || !msgBox.alwaysVisible)
{
MessageBoxes[i].AddToGUIUpdateList();
break;
}
}
}
protected override void Update(float deltaTime)
{
if (type == Type.InGame)
{
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
/*for (int i = MessageBoxes.IndexOf(this); i >= 0; i--)
{
if (MessageBoxes[i] is GUIMessageBox otherMsgBox && otherMsgBox != this && otherMsgBox.type == type && !otherMsgBox.closing)
{
defaultPos = new Vector2(
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.X + 10 * GUI.Scale, defaultPos.X),
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.Y + 10 * GUI.Scale, defaultPos.Y));
}
}*/
if (!closing)
{
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
inGameCloseTimer += deltaTime;
if (inGameCloseTimer >= inGameCloseTime)
{
Close();
}
}
else
{
openState += deltaTime * 2.0f;
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint();
if (openState >= 2.0f)
{
if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}
}
}
}
public void Close()
{
if (type == Type.InGame)
{
closing = true;
}
else
{
if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}
}
public bool Close(GUIButton button, object obj)
{
Close();
return true;
}
public static void CloseAll()
{
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 });
}
}
}
@@ -0,0 +1,417 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Linq;
namespace Barotrauma
{
class GUINumberInput : GUIComponent
{
public enum NumberType
{
Int, Float
}
public delegate void OnValueChangedHandler(GUINumberInput numberInput);
public OnValueChangedHandler OnValueChanged;
public GUITextBox TextBox { get; private set; }
private GUIButton plusButton, minusButton;
private NumberType inputType;
public NumberType InputType
{
get { return inputType; }
set
{
if (inputType == value) { return; }
inputType = value;
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
private float? minValueFloat, maxValueFloat;
public float? MinValueFloat
{
get { return minValueFloat; }
set
{
minValueFloat = value;
ClampFloatValue();
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
public float? MaxValueFloat
{
get { return maxValueFloat; }
set
{
maxValueFloat = value;
ClampFloatValue();
if (inputType == NumberType.Int ||
(inputType == NumberType.Float && MinValueFloat > float.MinValue && MaxValueFloat < float.MaxValue))
{
ShowPlusMinusButtons();
}
else
{
HidePlusMinusButtons();
}
}
}
private float floatValue;
public float FloatValue
{
get { return floatValue; }
set
{
if (MathUtils.NearlyEqual(value, floatValue)) return;
floatValue = value;
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();
}
}
private int? minValueInt, maxValueInt;
public int? MinValueInt
{
get { return minValueInt; }
set
{
minValueInt = value;
ClampIntValue();
}
}
public int? MaxValueInt
{
get { return maxValueInt; }
set
{
maxValueInt = value;
ClampIntValue();
}
}
private int intValue;
public int IntValue
{
get { return intValue; }
set
{
if (value == intValue) return;
intValue = value;
UpdateText();
}
}
public override bool Enabled
{
get => base.Enabled;
set
{
plusButton.Enabled = true;
minusButton.Enabled = true;
if (InputType == NumberType.Int) { ClampIntValue(); } else { ClampFloatValue(); }
TextBox.Enabled = value;
if (!value)
{
plusButton.Enabled = false;
minusButton.Enabled = false;
}
}
}
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
if (TextBox != null) { TextBox.Font = value; }
}
}
public GUILayoutGroup LayoutGroup
{
get;
private set;
}
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, float? relativeButtonAreaWidth = null) : base(style, rectT)
{
LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
float _relativeButtonAreaWidth = relativeButtonAreaWidth ?? MathHelper.Clamp(Rect.Height / (float)Rect.Width, 0.1f, 0.25f);
TextBox = new GUITextBox(new RectTransform(new Vector2(1.0f - _relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform), textAlignment: textAlignment, style: "GUITextBoxNoIcon")
{
ClampText = false
};
TextBox.CaretColor = TextBox.TextColor;
TextBox.OnTextChanged += TextChanged;
var buttonArea = new GUIFrame(new RectTransform(new Vector2(_relativeButtonAreaWidth, 1.0f), LayoutGroup.RectTransform, Anchor.CenterRight), style: null);
plusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform), style: null);
GUI.Style.Apply(plusButton, "PlusButton", this);
plusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
plusButton.OnClicked += (button, data) =>
{
IncreaseValue();
return true;
};
plusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
IncreaseValue();
}
return true;
};
minusButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.5f), buttonArea.RectTransform, Anchor.BottomRight), style: null);
GUI.Style.Apply(minusButton, "MinusButton", this);
minusButton.OnButtonDown += () =>
{
pressedTimer = pressedDelay;
return true;
};
minusButton.OnClicked += (button, data) =>
{
ReduceValue();
return true;
};
minusButton.OnPressed += () =>
{
if (!IsPressedTimerRunning)
{
ReduceValue();
}
return true;
};
if (inputType != NumberType.Int)
{
HidePlusMinusButtons();
}
if (inputType == NumberType.Int)
{
UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
UpdateText();
TextBox.Deselect();
return true;
};
TextBox.OnDeselected += (txtBox, key) => UpdateText();
}
else if (inputType == NumberType.Float)
{
UpdateText();
TextBox.OnDeselected += (txtBox, key) => UpdateText();
TextBox.OnEnterPressed += (txtBox, txt) =>
{
UpdateText();
TextBox.Deselect();
return true;
};
}
InputType = inputType;
switch (InputType)
{
case NumberType.Int:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c) || c == '-').ToArray());
break;
case NumberType.Float:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());
break;
}
RectTransform.MinSize = TextBox.RectTransform.MinSize;
LayoutGroup.Recalculate();
}
private void HidePlusMinusButtons()
{
plusButton.Parent.Visible = false;
plusButton.Parent.IgnoreLayoutGroups = true;
TextBox.RectTransform.RelativeSize = Vector2.One;
LayoutGroup.Recalculate();
}
private void ShowPlusMinusButtons()
{
plusButton.Parent.Visible = true;
plusButton.Parent.IgnoreLayoutGroups = false;
TextBox.RectTransform.RelativeSize = new Vector2(1.0f - plusButton.Parent.RectTransform.RelativeSize.X, 1.0f);
LayoutGroup.Recalculate();
}
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)
{
switch (InputType)
{
case NumberType.Int:
int newIntValue = IntValue;
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
intValue = 0;
}
else if (int.TryParse(text, out newIntValue))
{
intValue = newIntValue;
}
ClampIntValue();
break;
case NumberType.Float:
float newFloatValue = FloatValue;
if (string.IsNullOrWhiteSpace(text) || text == "-")
{
floatValue = 0;
}
else if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out newFloatValue))
{
floatValue = newFloatValue;
}
ClampFloatValue();
break;
}
OnValueChanged?.Invoke(this);
return true;
}
private void ClampFloatValue()
{
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;
}
}
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;
}
}
}
}
@@ -0,0 +1,168 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
namespace Barotrauma
{
public class GUIProgressBar : GUIComponent
{
private bool isHorizontal;
private readonly GUIFrame frame, slider;
private float barSize;
private readonly bool showFrame;
public delegate float ProgressGetterHandler();
public ProgressGetterHandler ProgressGetter;
public bool IsHorizontal
{
get { return isHorizontal; }
set { isHorizontal = value; }
}
public float BarSize
{
get { return barSize; }
set
{
if (!MathUtils.IsValid(value))
{
GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.BarSize_setter",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace);
return;
}
barSize = MathHelper.Clamp(value, 0.0f, 1.0f);
//UpdateRect();
}
}
public GUIProgressBar(RectTransform rectT, float barSize, Color? color = null, string style = "", bool showFrame = true) : base(style, rectT)
{
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 RectTransform(Vector2.One, rectT));
GUI.Style.Apply(slider, "Slider", this);
this.showFrame = showFrame;
this.barSize = barSize;
Enabled = true;
}
/// <summary>
/// Get the area the slider should be drawn inside
/// </summary>
/// <param name="fillAmount">0 = empty, 1 = full</param>
public Rectangle GetSliderRect(float fillAmount)
{
Rectangle sliderArea = new Rectangle(
frame.Rect.X + (int)style.Padding.X,
frame.Rect.Y + (int)style.Padding.Y,
(int)(frame.Rect.Width - style.Padding.X - style.Padding.Z),
(int)(frame.Rect.Height - style.Padding.Y - style.Padding.W));
Vector4 sliceBorderSizes = Vector4.Zero;
if (slider.sprites.ContainsKey(slider.State) && (slider.sprites[slider.State].First()?.Slice ?? false))
{
var slices = slider.sprites[slider.State].First().Slices;
sliceBorderSizes = new Vector4(slices[0].Width, slices[0].Height, slices[8].Width, slices[8].Height);
sliceBorderSizes *= slider.sprites[slider.State].First().GetSliceBorderScale(sliderArea.Size);
}
Rectangle sliderRect = IsHorizontal ?
new Rectangle(
sliderArea.X + (int)sliceBorderSizes.X,
sliderArea.Y,
(int)((sliderArea.Width - sliceBorderSizes.X - sliceBorderSizes.Z) * fillAmount),
sliderArea.Height)
:
new Rectangle(
sliderArea.X,
(int)(sliderArea.Bottom - (sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount - sliceBorderSizes.W),
sliderArea.Width,
(int)((sliderArea.Height - sliceBorderSizes.Y - sliceBorderSizes.W) * fillAmount));
sliderRect.Width = Math.Max(sliderRect.Width, 1);
sliderRect.Height = Math.Max(sliderRect.Height, 1);
return sliderRect;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
if (ProgressGetter != null)
{
float newSize = MathHelper.Clamp(ProgressGetter(), 0.0f, 1.0f);
if (!MathUtils.IsValid(newSize))
{
GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.Draw:GetProgress",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace);
}
else
{
BarSize = newSize;
}
}
var sliderRect = GetSliderRect(barSize);
slider.RectTransform.AbsoluteOffset = new Point((int)style.Padding.X, (int)style.Padding.Y);
slider.RectTransform.MaxSize = new Point(
(int)(Rect.Width - style.Padding.X + style.Padding.Z),
(int)(Rect.Height - style.Padding.Y + style.Padding.W));
frame.Visible = showFrame;
slider.Visible = true;
if (showFrame)
{
if (AutoDraw)
{
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, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
Color currColor = GetColor(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;
}
}
}
}
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public class GUIRadioButtonGroup : GUIComponent
{
private Dictionary<int, GUITickBox> radioButtons; //TODO: use children list instead?
public GUIRadioButtonGroup() : base(null)
{
radioButtons = new Dictionary<int, GUITickBox>();
selected = null;
}
public override bool Enabled
{
get => base.Enabled;
set
{
base.Enabled = value;
foreach(KeyValuePair<int, GUITickBox> rbPair in radioButtons)
{
rbPair.Value.Enabled = value;
}
}
}
public void AddRadioButton(int key, GUITickBox radioButton)
{
if (selected == key) radioButton.Selected = true;
else if (radioButton.Selected) selected = key;
radioButton.SetRadioButtonGroup(this);
radioButtons.Add((int)key, radioButton);
}
public delegate void RadioButtonGroupDelegate(GUIRadioButtonGroup rbg, int? val);
public RadioButtonGroupDelegate OnSelect = null;
public void SelectRadioButton(GUITickBox radioButton)
{
foreach (KeyValuePair<int, GUITickBox> rbPair in radioButtons)
{
if (radioButton == rbPair.Value)
{
Selected = rbPair.Key;
return;
}
}
}
// intentional hiding?
private new int? selected;
public new int? Selected
{
get
{
return selected;
}
set
{
OnSelect?.Invoke(this, value);
if (selected != null && selected.Equals(value)) { return; }
selected = value;
foreach (KeyValuePair<int, GUITickBox> radioButton in radioButtons)
{
if (radioButton.Key.Equals(value))
{
radioButton.Value.Selected = true;
}
else if (radioButton.Value.Selected)
{
radioButton.Value.Selected = false;
}
}
}
}
public GUITickBox SelectedRadioButton
{
get
{
return selected.HasValue ? radioButtons[selected.Value] : null;
}
}
}
}
@@ -0,0 +1,339 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace Barotrauma
{
public class GUIScrollBar : GUIComponent
{
public static GUIScrollBar DraggingBar
{
get; private set;
}
private bool isHorizontal;
public GUIFrame Frame { get; private set; }
public GUIButton Bar { get; private set; }
private float barSize;
private float barScroll;
private float step;
private Vector2? dragStartPos;
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
public OnMovedHandler OnMoved;
public OnMovedHandler OnReleased;
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
{
if (isHorizontal == value) return;
isHorizontal = value;
UpdateRect();
}*/
}
public override bool Enabled
{
get { return enabled; }
set
{
enabled = value;
Bar.Enabled = value;
Children.ForEach(c => c.Enabled = value);
if (!enabled)
{
Bar.Selected = false;
}
}
}
public Vector4 Padding
{
get
{
if (Frame?.Style == null) return Vector4.Zero;
return Frame.Style.Padding;
}
}
private Vector2 range;
public Vector2 Range
{
get
{
return range;
}
set
{
float oldBarScrollValue = BarScrollValue;
range = value;
BarScrollValue = oldBarScrollValue;
}
}
public delegate float ScrollConversion(GUIScrollBar scrollBar, float f);
public ScrollConversion ScrollToValue = null;
public ScrollConversion ValueToScroll = null;
public float BarScrollValue
{
get
{
if (ScrollToValue == null) return (BarScroll * (Range.Y - Range.X)) + Range.X;
return ScrollToValue(this, BarScroll);
}
set
{
if (ValueToScroll == null) BarScroll = (value - Range.X) / (Range.Y - Range.X);
else BarScroll = ValueToScroll(this, value);
}
}
public float BarScroll
{
get { return step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step); }
set
{
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)(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)(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.RectTransform.AbsoluteOffset = new Point(newX, newY);
}
}
public float Step
{
get
{
return step;
}
set
{
step = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
public float StepValue
{
get
{
return step * (Range.Y - Range.X);
}
set
{
Step = value / (Range.Y - Range.X);
}
}
public float BarSize
{
get { return barSize; }
set
{
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
UpdateRect();
}
}
public GUIScrollBar(RectTransform rectT, float barSize = 1, Color? color = null, string style = "", bool? isHorizontal = null) : base(style, rectT)
{
CanBeFocused = true;
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 RectTransform(Vector2.One, rectT, IsHorizontal ? Anchor.CenterLeft : Anchor.TopCenter), color: color, style: null);
switch (style)
{
case "":
HoverCursor = CursorState.Default;
Bar.HoverCursor = CursorState.Default;
break;
case "GUISlider":
HoverCursor = CursorState.Default;
Bar.HoverCursor = CursorState.Hand;
break;
default:
HoverCursor = CursorState.Hand;
Bar.HoverCursor = CursorState.Hand;
break;
}
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()
{
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;
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
if (!enabled) { return; }
Frame.State = GUI.MouseOn == Frame ? ComponentState.Hover : ComponentState.None;
if (Frame.State == ComponentState.Hover && PlayerInput.PrimaryMouseButtonHeld())
{
Frame.State = ComponentState.Pressed;
}
if (IsBooleanSwitch &&
(!PlayerInput.PrimaryMouseButtonHeld() || (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)
{
GUI.ForceMouseOn(this);
if (dragStartPos == null) { dragStartPos = PlayerInput.MousePosition; }
if (!PlayerInput.PrimaryMouseButtonHeld())
{
if (IsBooleanSwitch && GUI.MouseOn == Bar && Vector2.Distance(dragStartPos.Value, PlayerInput.MousePosition) < 5)
{
BarScroll = BarScroll > 0.5f ? 0.0f : 1.0f;
OnMoved?.Invoke(this, BarScroll);
}
OnReleased?.Invoke(this, BarScroll);
DraggingBar = null;
dragStartPos = 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.PrimaryMouseButtonClicked())
{
DraggingBar?.OnReleased?.Invoke(DraggingBar, DraggingBar.BarScroll);
if (IsBooleanSwitch)
{
MoveButton(new Vector2(
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Rect.Width,
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Rect.Height));
}
else
{
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));
}
}
}
}
private bool SelectBar()
{
if (!enabled || !PlayerInput.PrimaryMouseButtonDown()) { return false; }
if (barSize >= 1.0f) { return false; }
DraggingBar = this;
return true;
}
public void MoveButton(Vector2 moveAmount)
{
float newScroll = barScroll;
if (isHorizontal)
{
moveAmount.Y = 0.0f;
newScroll += moveAmount.X / (Frame.Rect.Width - Bar.Rect.Width - Padding.X - Padding.Z);
}
else
{
moveAmount.X = 0.0f;
newScroll += moveAmount.Y / (Frame.Rect.Height - Bar.Rect.Height - Padding.Y - Padding.W);
}
BarScroll = newScroll;
if (moveAmount != Vector2.Zero && OnMoved != null) { OnMoved(this, BarScroll); }
}
}
}
@@ -0,0 +1,431 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
public class GUIStyle
{
private Dictionary<string, GUIComponentStyle> componentStyles;
private XElement configElement;
private GraphicsDevice graphicsDevice;
private ScalableFont defaultFont;
public ScalableFont Font { get; private set; }
public ScalableFont GlobalFont { get; private set; }
public ScalableFont UnscaledSmallFont { get; private set; }
public ScalableFont SmallFont { get; private set; }
public ScalableFont LargeFont { get; private set; }
public ScalableFont SubHeadingFont { get; private set; }
public ScalableFont DigitalFont { get; private set; }
public ScalableFont HotkeyFont { get; private set; }
public Dictionary<ScalableFont, bool> ForceFontUpperCase
{
get;
private set;
} = new Dictionary<ScalableFont, bool>();
public readonly Sprite[] CursorSprite = new Sprite[7];
public UISprite UIGlow { get; private set; }
public UISprite UIGlowCircular { get; private set; }
public SpriteSheet FocusIndicator { get; private set; }
/// <summary>
/// General green color used for elements whose colors are set from code
/// </summary>
public Color Green { get; private set; } = Color.LightGreen;
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public Color Orange { get; private set; } = Color.Orange;
/// <summary>
/// General red color used for elements whose colors are set from code
/// </summary>
public Color Red { get; private set; } = Color.Red;
/// <summary>
/// General blue color used for elements whose colors are set from code
/// </summary>
public Color Blue { get; private set; } = Color.Blue;
public Color ColorInventoryEmpty { get; private set; } = Color.Red;
public Color ColorInventoryHalf { get; private set; } = Color.Orange;
public Color ColorInventoryFull { get; private set; } = Color.LightGreen;
public Color ColorInventoryBackground { get; private set; } = Color.Gray;
public Color TextColor { get; private set; } = Color.White * 0.8f;
public Color TextColorBright { get; private set; } = Color.White * 0.9f;
public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f;
// Inventory
public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64);
// Health HUD
public Color BuffColorLow { get; private set; } = Color.LightGreen;
public Color BuffColorMedium { get; private set; } = Color.Green;
public Color BuffColorHigh { get; private set; } = Color.DarkGreen;
public Color DebuffColorLow { get; private set; } = Color.DarkSalmon;
public Color DebuffColorMedium { get; private set; } = Color.Red;
public Color DebuffColorHigh { get; private set; } = Color.DarkRed;
public Color HealthBarColorLow { get; private set; } = Color.Red;
public Color HealthBarColorMedium { get; private set; } = Color.Orange;
public Color HealthBarColorHigh { get; private set; } = new Color(78, 114, 88);
public Color EquipmentIndicatorNotEquipped { get; private set; } = Color.Gray;
public Color EquipmentIndicatorEquipped { get; private set; } = new Color(105, 202, 125);
public Color EquipmentIndicatorRunningOut { get; private set; } = new Color(202, 105, 105);
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
public GUIStyle(XElement element, GraphicsDevice graphicsDevice)
{
this.graphicsDevice = graphicsDevice;
componentStyles = new Dictionary<string, GUIComponentStyle>();
configElement = element;
foreach (XElement subElement in configElement.Elements())
{
var name = subElement.Name.ToString().ToLowerInvariant();
switch (name)
{
case "cursor":
if (subElement.HasElements)
{
foreach (var children in subElement.Descendants())
{
var index = children.GetAttributeInt("state", (int)CursorState.Default);
CursorSprite[index] = new Sprite(children);
}
}
else
{
CursorSprite[(int)CursorState.Default] = new Sprite(subElement);
}
break;
case "green":
Green = subElement.GetAttributeColor("color", Green);
break;
case "orange":
Orange = subElement.GetAttributeColor("color", Orange);
break;
case "red":
Red = subElement.GetAttributeColor("color", Red);
break;
case "blue":
Blue = subElement.GetAttributeColor("color", Blue);
break;
case "colorinventoryempty":
ColorInventoryEmpty = subElement.GetAttributeColor("color", ColorInventoryEmpty);
break;
case "colorinventoryhalf":
ColorInventoryHalf = subElement.GetAttributeColor("color", ColorInventoryHalf);
break;
case "colorinventoryfull":
ColorInventoryFull = subElement.GetAttributeColor("color", ColorInventoryFull);
break;
case "colorinventorybackground":
ColorInventoryBackground = subElement.GetAttributeColor("color", ColorInventoryBackground);
break;
case "textcolordark":
TextColorDark = subElement.GetAttributeColor("color", TextColorDark);
break;
case "textcolorbright":
TextColorBright = subElement.GetAttributeColor("color", TextColorBright);
break;
case "textcolordim":
TextColorDim = subElement.GetAttributeColor("color", TextColorDim);
break;
case "textcolornormal":
case "textcolor":
TextColor = subElement.GetAttributeColor("color", TextColor);
break;
case "equipmentsloticoncolor":
EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor);
break;
case "buffcolorlow":
BuffColorLow = subElement.GetAttributeColor("color", BuffColorLow);
break;
case "buffcolormedium":
BuffColorMedium = subElement.GetAttributeColor("color", BuffColorMedium);
break;
case "buffcolorhigh":
BuffColorHigh = subElement.GetAttributeColor("color", BuffColorHigh);
break;
case "debuffcolorlow":
DebuffColorLow = subElement.GetAttributeColor("color", DebuffColorLow);
break;
case "debuffcolormedium":
DebuffColorMedium = subElement.GetAttributeColor("color", DebuffColorMedium);
break;
case "debuffcolorhigh":
DebuffColorHigh = subElement.GetAttributeColor("color", DebuffColorHigh);
break;
case "healthbarcolorlow":
HealthBarColorLow = subElement.GetAttributeColor("color", HealthBarColorLow);
break;
case "healthbarcolormedium":
HealthBarColorMedium = subElement.GetAttributeColor("color", HealthBarColorMedium);
break;
case "healthbarcolorhigh":
HealthBarColorHigh = subElement.GetAttributeColor("color", HealthBarColorHigh);
break;
case "equipmentindicatornotequipped":
EquipmentIndicatorNotEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorNotEquipped);
break;
case "equipmentindicatorequipped":
EquipmentIndicatorEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorEquipped);
break;
case "equipmentindicatorrunningout":
EquipmentIndicatorRunningOut = subElement.GetAttributeColor("color", EquipmentIndicatorRunningOut);
break;
case "uiglow":
UIGlow = new UISprite(subElement);
break;
case "uiglowcircular":
UIGlowCircular = new UISprite(subElement);
break;
case "focusindicator":
FocusIndicator = new SpriteSheet(subElement);
break;
case "font":
Font = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[Font] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "globalfont":
GlobalFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[GlobalFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "unscaledsmallfont":
UnscaledSmallFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[UnscaledSmallFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "smallfont":
SmallFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[SmallFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "largefont":
LargeFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[LargeFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "digitalfont":
DigitalFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[DigitalFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "hotkeyfont":
HotkeyFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[HotkeyFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
case "objectivetitle":
case "subheading":
SubHeadingFont = LoadFont(subElement, graphicsDevice);
ForceFontUpperCase[SubHeadingFont] = subElement.GetAttributeBool("forceuppercase", false);
break;
default:
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement, this);
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
break;
}
}
if (GlobalFont == null)
{
GlobalFont = Font;
DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange);
}
GameMain.Instance.OnResolutionChanged += () => { RescaleElements(); };
}
/// <summary>
/// Returns the default font of the currently selected language
/// </summary>
public ScalableFont LoadCurrentDefaultFont()
{
defaultFont?.Dispose();
defaultFont = null;
foreach (XElement subElement in configElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "font":
defaultFont = LoadFont(subElement, graphicsDevice);
break;
}
}
return defaultFont;
}
private void RescaleElements()
{
if (configElement == null) { return; }
if (configElement.Elements() == null) { return; }
foreach (XElement subElement in configElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "font":
if (Font == null) { continue; }
Font.Size = GetFontSize(subElement);
break;
case "smallfont":
if (SmallFont == null) { continue; }
SmallFont.Size = GetFontSize(subElement);
break;
case "largefont":
if (LargeFont == null) { continue; }
LargeFont.Size = GetFontSize(subElement);
break;
case "hotkeyfont":
if (HotkeyFont == null) { continue; }
HotkeyFont.Size = GetFontSize(subElement);
break;
case "objectivetitle":
case "subheading":
if (SubHeadingFont == null) { continue; }
SubHeadingFont.Size = GetFontSize(subElement);
break;
}
}
foreach (var componentStyle in componentStyles.Values)
{
componentStyle.GetSize(componentStyle.Element);
foreach (var childStyle in componentStyle.ChildStyles.Values)
{
childStyle.GetSize(childStyle.Element);
}
}
}
private ScalableFont LoadFont(XElement element, GraphicsDevice graphicsDevice)
{
string file = GetFontFilePath(element);
uint size = GetFontSize(element);
bool dynamicLoading = GetFontDynamicLoading(element);
bool isCJK = GetIsCJK(element);
return new ScalableFont(file, size, graphicsDevice, dynamicLoading, isCJK);
}
private uint GetFontSize(XElement element, uint defaultSize = 14)
{
//check if any of the language override fonts want to override the font size as well
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
uint overrideFontSize = GetFontSize(subElement, 0);
if (overrideFontSize > 0) { return overrideFontSize; }
}
}
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{
return (uint)subElement.GetAttributeInt("size", 14);
}
}
return defaultSize;
}
private string GetFontFilePath(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeString("file", "");
}
}
return element.GetAttributeString("file", "");
}
private bool GetFontDynamicLoading(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeBool("dynamicloading", false);
}
}
return element.GetAttributeBool("dynamicloading", false);
}
private bool GetIsCJK(XElement element)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
{
return subElement.GetAttributeBool("iscjk", false);
}
}
return element.GetAttributeBool("iscjk", false);
}
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)
{
string parentStyleName = parent.GetType().Name.ToLowerInvariant();
if (!componentStyles.TryGetValue(parentStyleName, out parentStyle))
{
DebugConsole.ThrowError("Couldn't find a GUI style \""+ parentStyleName + "\"");
return;
}
}
string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName;
parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle);
}
else
{
if (string.IsNullOrEmpty(styleName))
{
styleName = targetComponent.GetType().Name;
}
if (!componentStyles.TryGetValue(styleName.ToLowerInvariant(), out componentStyle))
{
DebugConsole.ThrowError("Couldn't find a GUI style \""+ styleName+"\"");
return;
}
}
targetComponent.ApplyStyle(componentStyle);
}
}
}
@@ -0,0 +1,535 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
public class GUITextBlock : GUIComponent
{
protected string text;
protected Alignment textAlignment;
private float textScale = 1;
protected Vector2 textPos;
protected Vector2 origin;
protected Color textColor, disabledTextColor, selectedTextColor;
private string wrappedText;
private string censoredText;
public delegate string TextGetterHandler();
public TextGetterHandler TextGetter;
public bool Wrap;
private bool playerInput;
public bool RoundToNearestPixel = true;
private bool overflowClipActive;
public bool OverflowClip;
public bool OverflowClipActive
{
get { return overflowClipActive; }
}
private float textDepth;
private ScalableFont originalFont;
public Vector2 TextOffset { get; set; }
private Vector4 padding;
public Vector4 Padding
{
get { return padding; }
set
{
padding = value;
SetTextPos();
}
}
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
if (base.Font == value) { return; }
base.Font = originalFont = value;
if (text != null && GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font])
{
Text = text.ToUpper();
}
SetTextPos();
}
}
public string Text
{
get { return text; }
set
{
string newText = forceUpperCase || (GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]) || (style != null && style.ForceUpperCase) ?
value?.ToUpper() :
value;
if (Text == newText) { return; }
//reset scale, it gets recalculated in SetTextPos
if (autoScaleHorizontal || autoScaleVertical) { textScale = 1.0f; }
text = newText;
wrappedText = newText;
if (TextManager.IsCJK(text))
{
//switch to fallback CJK font
if (!Font.IsCJK) { base.Font = GUI.CJKFont; }
}
else
{
if (Font == GUI.CJKFont) { base.Font = originalFont; }
}
SetTextPos();
}
}
public string WrappedText
{
get { return wrappedText; }
}
public float TextDepth
{
get { return textDepth; }
set { textDepth = MathHelper.Clamp(value, 0.0f, 1.0f); }
}
public Vector2 TextPos
{
get { return textPos; }
set { textPos = value; }
}
public float TextScale
{
get { return textScale; }
set
{
if (value != textScale)
{
textScale = value;
SetTextPos();
}
}
}
private bool autoScaleHorizontal, autoScaleVertical;
/// <summary>
/// When enabled, the text is automatically scaled down to fit the textblock horizontally.
/// </summary>
public bool AutoScaleHorizontal
{
get { return autoScaleHorizontal; }
set
{
if (autoScaleHorizontal == value) { return; }
autoScaleHorizontal = value;
if (autoScaleHorizontal)
{
SetTextPos();
}
}
}
/// <summary>
/// When enabled, the text is automatically scaled down to fit the textblock vertically.
/// </summary>
public bool AutoScaleVertical
{
get { return autoScaleVertical; }
set
{
if (autoScaleVertical == value) { return; }
autoScaleVertical = value;
if (autoScaleVertical)
{
SetTextPos();
}
}
}
private bool forceUpperCase;
public bool ForceUpperCase
{
get { return forceUpperCase; }
set
{
if (forceUpperCase == value) { return; }
forceUpperCase = value;
if (forceUpperCase ||
(style != null && style.ForceUpperCase) ||
(GUI.Style.ForceFontUpperCase.ContainsKey(Font) && GUI.Style.ForceFontUpperCase[Font]))
{
Text = text?.ToUpper();
}
}
}
public Vector2 Origin
{
get { return origin; }
}
public Vector2 TextSize
{
get;
private set;
}
public Color TextColor
{
get { return textColor; }
set { textColor = value; }
}
private Color? hoverTextColor;
public Color HoverTextColor
{
get { return hoverTextColor ?? textColor; }
set { hoverTextColor = value; }
}
public Color SelectedTextColor
{
get { return selectedTextColor; }
set { selectedTextColor = value; }
}
public Alignment TextAlignment
{
get { return textAlignment; }
set
{
if (textAlignment == value) return;
textAlignment = value;
SetTextPos();
}
}
public bool Censor
{
get;
set;
}
public string CensoredText
{
get { return censoredText; }
}
private List<ColorData> colorData = null;
private bool hasColorHighlight = false;
/// <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, bool playerInput = false)
: base(style, rectT)
{
if (color.HasValue)
{
this.color = color.Value;
}
if (textColor.HasValue)
{
OverrideTextColor(textColor.Value);
}
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
//use the default CJK font as a fallback
var selectedFont = originalFont = font ?? GUI.Font;
if (TextManager.IsCJK(text) && !selectedFont.IsCJK)
{
selectedFont = GUI.CJKFont;
}
this.Font = selectedFont;
this.textAlignment = textAlignment;
this.Wrap = wrap;
this.Text = text ?? "";
this.playerInput = playerInput;
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{
CalculateHeightFromText();
}
SetTextPos();
RectTransform.ScaleChanged += SetTextPos;
RectTransform.SizeChanged += SetTextPos;
Enabled = true;
Censor = false;
}
public GUITextBlock(RectTransform rectT, List<ColorData> colorData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput)
{
this.colorData = colorData;
hasColorHighlight = colorData != null;
}
public void CalculateHeightFromText(int padding = 0)
{
if (wrappedText == null) { return; }
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y + padding));
}
public override void ApplyStyle(GUIComponentStyle componentStyle)
{
if (componentStyle == null) { return; }
base.ApplyStyle(componentStyle);
padding = componentStyle.Padding;
textColor = componentStyle.TextColor;
hoverTextColor = componentStyle.HoverTextColor;
disabledTextColor = componentStyle.DisabledTextColor;
selectedTextColor = componentStyle.SelectedTextColor;
switch (componentStyle.Font)
{
case "font":
Font = componentStyle.Style.Font;
break;
case "smallfont":
Font = componentStyle.Style.SmallFont;
break;
case "largefont":
Font = componentStyle.Style.LargeFont;
break;
case "objectivetitle":
case "subheading":
Font = componentStyle.Style.SubHeadingFont;
break;
}
}
public void SetTextPos()
{
if (text == null) { return; }
censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
var rect = Rect;
overflowClipActive = false;
wrappedText = text;
TextSize = MeasureText(text);
if (Wrap && rect.Width > 0)
{
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale, playerInput);
TextSize = MeasureText(wrappedText);
}
else if (OverflowClip)
{
overflowClipActive = TextSize.X > rect.Width - padding.X - padding.Z;
}
Vector2 minSize = new Vector2(
Math.Max(rect.Width - padding.X - padding.Z, 5.0f),
Math.Max(rect.Height - padding.Y - padding.W, 5.0f));
if (!autoScaleHorizontal) { minSize.X = float.MaxValue; }
if (!Wrap && !autoScaleVertical) { minSize.Y = float.MaxValue; }
if ((autoScaleHorizontal || autoScaleVertical) && textScale > 0.1f &&
(TextSize.X * textScale > minSize.X || TextSize.Y * textScale > minSize.Y))
{
TextScale = Math.Max(0.1f, Math.Min(minSize.X / TextSize.X, minSize.Y / TextSize.Y)) - 0.01f;
return;
}
textPos = new Vector2(padding.X + (rect.Width - padding.Z - padding.X) / 2.0f, padding.Y + (rect.Height - padding.Y - padding.W) / 2.0f);
origin = TextSize * 0.5f;
if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive)
{
textPos.X = padding.X;
origin.X = 0;
}
if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive)
{
textPos.X = rect.Width - padding.Z;
origin.X = TextSize.X;
}
if (textAlignment.HasFlag(Alignment.Top))
{
textPos.Y = padding.Y;
origin.Y = 0;
}
if (textAlignment.HasFlag(Alignment.Bottom))
{
textPos.Y = rect.Height - padding.W;
origin.Y = TextSize.Y;
}
origin.X = (int)(origin.X);
origin.Y = (int)(origin.Y);
textPos.X = (int)textPos.X;
textPos.Y = (int)textPos.Y;
}
private Vector2 MeasureText(string text)
{
if (Font == null) return Vector2.Zero;
if (string.IsNullOrEmpty(text))
{
return Font.MeasureString(" ");
}
Vector2 size = Vector2.Zero;
while (size == Vector2.Zero)
{
try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); }
catch { text = text.Substring(0, text.Length - 1); }
}
return size;
}
protected override void SetAlpha(float a)
{
base.SetAlpha(a);
textColor = new Color(textColor.R, textColor.G, textColor.B, a);
}
/// <summary>
/// Overrides the color for all the states.
/// </summary>
public void OverrideTextColor(Color color)
{
textColor = color;
hoverTextColor = color;
selectedTextColor = color;
disabledTextColor = color;
}
protected override void Draw(SpriteBatch spriteBatch)
{
if (!Visible) { return; }
Color currColor = GetColor(State);
var rect = Rect;
base.Draw(spriteBatch);
if (TextGetter != null) { Text = TextGetter(); }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (overflowClipActive)
{
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, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
if (!string.IsNullOrEmpty(text))
{
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
if (RoundToNearestPixel)
{
pos.X = (int)pos.X;
pos.Y = (int)pos.Y;
}
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor;
if (!enabled)
{
currentTextColor = disabledTextColor;
}
else if (State == ComponentState.Selected)
{
currentTextColor = selectedTextColor;
}
if (!hasColorHighlight)
{
Font.DrawString(spriteBatch,
Censor ? censoredText : (Wrap ? wrappedText : text),
pos,
currentTextColor * (currentTextColor.A / 255.0f),
0.0f, origin, TextScale,
SpriteEffects.None, textDepth);
}
else
{
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, colorData);
}
}
if (overflowClipActive)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
{
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>());
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(bool scaleHorizontal = true, bool scaleVertical = false, params GUITextBlock[] textBlocks)
{
AutoScaleAndNormalize(textBlocks.AsEnumerable<GUITextBlock>(), scaleHorizontal, scaleVertical);
}
/// <summary>
/// Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text within the block.
/// </summary>
public static void AutoScaleAndNormalize(IEnumerable<GUITextBlock> textBlocks, bool scaleHorizontal = true, bool scaleVertical = false, float? defaultScale = null)
{
if (!textBlocks.Any()) { return; }
float minScale = Math.Max(textBlocks.First().TextScale, 1.0f);
foreach (GUITextBlock textBlock in textBlocks)
{
if (defaultScale.HasValue) { textBlock.TextScale = defaultScale.Value; }
textBlock.AutoScaleHorizontal = scaleHorizontal;
textBlock.AutoScaleVertical = scaleVertical;
minScale = Math.Min(textBlock.TextScale, minScale);
}
foreach (GUITextBlock textBlock in textBlocks)
{
textBlock.AutoScaleHorizontal = false;
textBlock.AutoScaleVertical = false;
textBlock.TextScale = minScale;
}
}
}
}
@@ -0,0 +1,956 @@
using EventInput;
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
{
public delegate void TextBoxEvent(GUITextBox sender, Keys key);
public class GUITextBox : GUIComponent, IKeyboardSubscriber
{
public event TextBoxEvent OnSelected;
public event TextBoxEvent OnDeselected;
bool caretVisible;
float caretTimer;
private readonly GUIFrame frame;
private readonly GUITextBlock textBlock;
private readonly GUIImage icon;
public Func<string, string> textFilterFunction;
public delegate bool OnEnterHandler(GUITextBox textBox, string text);
public OnEnterHandler OnEnterPressed;
public event TextBoxEvent OnKeyHit;
public delegate bool OnTextChangedHandler(GUITextBox textBox, string text);
/// <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 { get; set; }
public Color? CaretColor { get; set; }
public bool DeselectAfterMessage = true;
private int? maxTextLength;
private int _caretIndex;
private int CaretIndex
{
get { return _caretIndex; }
set
{
_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 int selectedCharacters;
private int selectionStartIndex;
private int selectionEndIndex;
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
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; }
}
private new bool selected;
public new bool Selected
{
get
{
return selected;
}
set
{
if (!selected && value)
{
Select();
}
else if (selected && !value)
{
Deselect();
}
}
}
public bool Wrap
{
get { return textBlock.Wrap; }
set
{
textBlock.Wrap = value;
}
}
public GUITextBlock TextBlock
{
get { return textBlock; }
}
//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
{
get { return maxTextLength; }
set
{
textBlock.OverflowClip = value != null;
maxTextLength = value;
}
}
public bool OverflowClip
{
get { return textBlock.OverflowClip; }
set { textBlock.OverflowClip = value; }
}
public override bool Enabled
{
get { return enabled; }
set
{
enabled = frame.Enabled = textBlock.Enabled = value;
if (icon != null) { icon.Enabled = value; }
if (!enabled && Selected)
{
Deselect();
}
}
}
public bool Censor
{
get { return textBlock.Censor; }
set { textBlock.Censor = value; }
}
public override string ToolTip
{
get
{
return base.ToolTip;
}
set
{
base.ToolTip = value;
textBlock.ToolTip = value;
}
}
public override ScalableFont Font
{
get { return textBlock?.Font ?? base.Font; }
set
{
base.Font = value;
if (textBlock == null) { return; }
textBlock.Font = value;
}
}
public override Color Color
{
get { return color; }
set
{
color = value;
textBlock.Color = color;
}
}
public Color TextColor
{
get { return textBlock.TextColor; }
set { textBlock.TextColor = value; }
}
public override Color HoverColor
{
get
{
return base.HoverColor;
}
set
{
base.HoverColor = value;
textBlock.HoverColor = value;
}
}
public Vector4 Padding
{
get { return textBlock.Padding; }
set { textBlock.Padding = value; }
}
// TODO: should this be defined in the stylesheet?
public Color SelectionColor { get; set; } = Color.White * 0.25f;
public string Text
{
get
{
return textBlock.Text;
}
set
{
SetText(value, store: false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
}
public string WrappedText
{
get { return textBlock.WrappedText; }
}
public bool Readonly { get; set; }
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false)
: base(style, rectT)
{
HoverCursor = CursorState.IBeam;
CanBeFocused = true;
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.CenterLeft), text, textColor, font, textAlignment, wrap, playerInput: true);
GUI.Style.Apply(textBlock, "", this);
CaretEnabled = true;
caretPosDirty = true;
new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection);
int clearButtonWidth = 0;
if (createClearButton)
{
var clearButton = new GUIButton(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5, 0) }, style: "GUICancelButton")
{
OnClicked = (bt, userdata) =>
{
Text = "";
frame.Flash(Color.White);
return true;
}
};
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - clearButton.Rect.Height - clearButton.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
clearButtonWidth = (int)(clearButton.Rect.Width * 1.2f);
}
if (this.style != null && this.style.ChildStyles.ContainsKey("textboxicon"))
{
icon = new GUIImage(new RectTransform(new Vector2(0.6f, 0.6f), frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(5 + clearButtonWidth, 0) }, null, scaleToFit: true);
icon.ApplyStyle(this.style.ChildStyles["textboxicon"]);
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
}
Font = textBlock.Font;
Enabled = true;
rectT.SizeChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
caretPosDirty = true;
};
rectT.ScaleChanged += () =>
{
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
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
{
while (ClampText && textBlock.Text.Length>0 && 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()
{
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
if (textDrawn.Contains("\n"))
{
string[] lines = textDrawn.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(textDrawn.Substring(0, totalIndex)).Y;
caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin;
break;
}
}
}
else
{
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex));
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin;
}
caretPosDirty = false;
}
protected List<Tuple<Vector2, int>> GetAllPositions()
{
float halfHeight = Font.MeasureString("T").Y * 0.5f;
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n"))
{
string[] lines = textDrawn.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(textDrawn.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 - halfHeight);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
}
index = totalIndex;
}
}
else
{
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
for (int i = 0; i <= textBlock.Text.Length; i++)
{
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i));
Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y - halfHeight) + textBlock.TextPos - textBlock.Origin;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(indexPos, i));
}
}
return positions;
}
public int GetCaretIndexFromScreenPos(Vector2 pos)
{
return GetCaretIndexFromLocalPos(pos - textBlock.Rect.Location.ToVector2());
}
public int GetCaretIndexFromLocalPos(Vector2 pos)
{
var positions = GetAllPositions();
if (positions.Count==0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f;
var currPosition = positions[0];
for (int i=1;i<positions.Count;i++)
{
var p1 = positions[i];
var p2 = currPosition;
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f)
{
currPosition = p1; continue;
}
else if (diffY > 3.0f)
{
continue;
}
else
{
diffY = Math.Abs(p1.Item1.Y - pos.Y);
if (diffY < halfHeight)
{
//we are on this line, select the nearest character
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
if (diffX < -1.0f)
{
currPosition = p1; continue;
}
else
{
continue;
}
}
else
{
//we are on a different line, preserve order
if (p1.Item2 < p2.Item2)
{
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
}
else if (p1.Item2 > p2.Item2)
{
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
}
continue;
}
}
}
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
}
public void Select(int forcedCaretIndex = -1)
{
if (memento.Current == null)
{
memento.Store(Text);
}
CaretIndex = forcedCaretIndex == - 1 ? GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
ClearSelection();
selected = true;
GUI.KeyboardDispatcher.Subscriber = this;
OnSelected?.Invoke(this, Keys.None);
}
public void Deselect()
{
memento.Clear();
selected = false;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
OnDeselected?.Invoke(this, Keys.None);
}
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
{
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
}
protected override void Update(float deltaTime)
{
if (!Visible) return;
if (flashTimer > 0.0f) flashTimer -= deltaTime;
if (!Enabled) { return; }
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this))))
{
State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown())
{
Select();
}
else
{
isSelecting = PlayerInput.PrimaryMouseButtonHeld();
}
if (PlayerInput.DoubleClicked())
{
SelectAll();
}
if (isSelecting)
{
if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0))
{
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
CalculateCaretPos();
CalculateSelection();
}
}
}
else
{
if ((PlayerInput.LeftButtonClicked() || PlayerInput.RightButtonClicked()) && selected) Deselect();
isSelecting = false;
State = ComponentState.None;
}
if (!isSelecting)
{
isSelecting = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
}
if (CaretEnabled)
{
if (textBlock.OverflowClipActive)
{
if (CaretScreenPos.X < textBlock.Rect.X + textBlock.Padding.X)
{
textBlock.TextPos = new Vector2(textBlock.TextPos.X + ((textBlock.Rect.X + textBlock.Padding.X) - CaretScreenPos.X), textBlock.TextPos.Y);
CalculateCaretPos();
}
else if (CaretScreenPos.X > textBlock.Rect.Right - textBlock.Padding.Z)
{
textBlock.TextPos = new Vector2(textBlock.TextPos.X - (CaretScreenPos.X - (textBlock.Rect.Right - textBlock.Padding.Z)), textBlock.TextPos.Y);
CalculateCaretPos();
}
}
caretTimer += deltaTime;
caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
if (caretVisible && caretPosDirty)
{
CalculateCaretPos();
}
}
if (GUI.KeyboardDispatcher.Subscriber == this)
{
State = ComponentState.Selected;
Character.DisableControls = true;
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
{
OnEnterPressed(this, Text);
}
}
else if (Selected)
{
Deselect();
}
textBlock.State = State;
}
private void DrawCaretAndSelection(SpriteBatch spriteBatch, GUICustomComponent customComponent)
{
if (!Visible) { return; }
if (Selected)
{
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()}", GUI.Style.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 80), $"caret pos: {caretPos.ToString()}", GUI.Style.Red, Color.Black);
//GUI.DrawString(spriteBatch, new Vector2(100, 100), $"caret screen pos: {CaretScreenPos.ToString()}", GUI.Style.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)
{
ReceiveTextInput(inputChar.ToString());
}
public void ReceiveTextInput(string input)
{
if (Readonly) { return; }
if (selectedCharacters > 0)
{
RemoveSelectedText();
}
Vector2 textPos = textBlock.TextPos;
bool wasOverflowClipActive = textBlock.OverflowClipActive;
if (SetText(Text.Insert(CaretIndex, input)))
{
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
OnTextChanged?.Invoke(this, Text);
if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos))
{
textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X;
}
}
}
public void ReceiveCommandInput(char command)
{
if (Text == null) Text = "";
switch (command)
{
case '\b' when !Readonly: //backspace
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
{
SetText(string.Empty, false);
CaretIndex = Text.Length;
}
else 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 when !Readonly: // 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();
if (!Readonly)
{
RemoveSelectedText();
}
break;
case (char)0x1: // ctrl-a
SelectAll();
break;
case (char)0x1A when !Readonly: // ctrl-z
text = memento.Undo();
if (text != Text)
{
ClearSelection();
SetText(text, false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
break;
case (char)0x12 when !Readonly: // ctrl-r
text = memento.Redo();
if (text != Text)
{
ClearSelection();
SetText(text, false);
CaretIndex = Text.Length;
OnTextChanged?.Invoke(this, Text);
}
break;
}
}
public void ReceiveSpecialInput(Keys 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("T").Y;
int newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y-lineHeight));
CaretIndex = newIndex;
caretTimer = 0;
HandleSelection();
break;
case Keys.Down:
if (isSelecting)
{
InitSelectionStart();
}
lineHeight = Font.MeasureString("T").Y;
newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y+lineHeight));
CaretIndex = newIndex;
caretTimer = 0;
HandleSelection();
break;
case Keys.Delete when !Readonly:
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<GUITextBox>().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 void SelectAll()
{
CaretIndex = 0;
CalculateCaretPos();
selectionStartPos = caretPos;
selectionStartIndex = 0;
CaretIndex = Text.Length;
CalculateSelection();
}
private void CopySelectedText()
{
Clipboard.SetText(selectedText);
}
private void ClearSelection()
{
selectedCharacters = 0;
selectionStartIndex = -1;
selectionEndIndex = -1;
selectedText = string.Empty;
}
private string GetCopiedText()
{
string t;
t = Clipboard.GetText();
return t;
}
private void RemoveSelectedText()
{
if (selectedText.Length == 0) { return; }
selectionStartIndex = Math.Max(0, Math.Min(selectionEndIndex, Math.Min(selectionStartIndex, Text.Length - 1)));
int selectionLength = Math.Min(Text.Length - selectionStartIndex, selectedText.Length);
SetText(Text.Remove(selectionStartIndex, selectionLength));
CaretIndex = Math.Min(Text.Length, selectionStartIndex);
ClearSelection();
OnTextChanged?.Invoke(this, Text);
}
private void InitSelectionStart()
{
if (caretPosDirty)
{
CalculateCaretPos();
}
if (selectionStartIndex == -1)
{
selectionStartIndex = CaretIndex;
selectionStartPos = caretPos;
}
}
private void CalculateSelection()
{
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
InitSelectionStart();
selectionEndIndex = CaretIndex;
selectionEndPos = caretPos;
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
if (IsLeftToRight)
{
selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters));
}
else
{
selectedText = Text.Substring(selectionEndIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters));
}
}
}
}
@@ -0,0 +1,224 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
public class GUITickBox : GUIComponent
{
private GUILayoutGroup layoutGroup;
private GUIFrame box;
private GUITextBlock text;
public delegate bool OnSelectedHandler(GUITickBox obj);
public OnSelectedHandler OnSelected;
public static int size = 20;
private GUIRadioButtonGroup radioButtonGroup;
public override bool Selected
{
get { return selected; }
set
{
if (value == selected) { return; }
if (radioButtonGroup != null && radioButtonGroup.SelectedRadioButton == this)
{
selected = true;
return;
}
selected = value;
State = selected ? ComponentState.Selected : ComponentState.None;
if (value && radioButtonGroup != null)
{
radioButtonGroup.SelectRadioButton(this);
}
OnSelected?.Invoke(this);
}
}
public override ComponentState State
{
get
{
return base.State;
}
set
{
base.State = value;
box.State = TextBlock.State = value;
}
}
public override bool Enabled
{
get
{
return enabled;
}
set
{
if (value == enabled) { return; }
enabled = box.Enabled = TextBlock.Enabled = value;
}
}
public Color TextColor
{
get { return text.TextColor; }
set { text.TextColor = value; }
}
/*public override Rectangle MouseRect
{
get
{
if (!CanBeFocused) return Rectangle.Empty;
Rectangle union = Rectangle.Union(box.Rect, TextBlock.Rect);
Vector2 textPos = TextBlock.Rect.Location.ToVector2() + TextBlock.TextPos + TextBlock.TextOffset;
Vector2 textSize = TextBlock.Font.MeasureString(TextBlock.Text);
union = Rectangle.Union(union, new Rectangle(textPos.ToPoint(), textSize.ToPoint()));
union = Rectangle.Union(union, Rect);
return ClampMouseRectToParent ? ClampRect(union) : union;
}
}*/
public override ScalableFont Font
{
get
{
return base.Font;
}
set
{
base.Font = value;
if (text != null) text.Font = value;
}
}
public GUIFrame Box
{
get { return box; }
}
public GUITextBlock TextBlock
{
get { return text; }
}
public override string ToolTip
{
get { return base.ToolTip; }
set
{
base.ToolTip = value;
box.ToolTip = value;
text.ToolTip = value;
}
}
public string Text
{
get { return text.Text; }
set { text.Text = value; }
}
public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT)
{
CanBeFocused = true;
HoverCursor = CursorState.Hand;
layoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), true);
box = new GUIFrame(new RectTransform(Vector2.One, layoutGroup.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight)
{
IsFixedSize = true
}, string.Empty, Color.DarkGray)
{
HoverColor = Color.Gray,
SelectedColor = Color.DarkGray,
CanBeFocused = false
};
GUI.Style.Apply(box, style == "" ? "GUITickBox" : style);
if (box.RectTransform.MinSize.Y > 0)
{
RectTransform.MinSize = box.RectTransform.MinSize;
RectTransform.MaxSize = box.RectTransform.MaxSize;
RectTransform.Resize(new Point(RectTransform.NonScaledSize.X, RectTransform.MinSize.Y));
box.RectTransform.MinSize = new Point(box.RectTransform.MinSize.Y);
box.RectTransform.Resize(box.RectTransform.MinSize);
}
Vector2 textBlockScale = new Vector2((float)(Rect.Width - Rect.Height) / (float)Math.Max(Rect.Width, 1.0), 1.0f);
text = new GUITextBlock(new RectTransform(textBlockScale, layoutGroup.RectTransform, Anchor.CenterLeft), label, font: font, textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
GUI.Style.Apply(text, "GUITextBlock", this);
Enabled = true;
ResizeBox();
rectT.ScaleChanged += ResizeBox;
rectT.SizeChanged += ResizeBox;
}
public void SetRadioButtonGroup(GUIRadioButtonGroup rbg)
{
radioButtonGroup = rbg;
}
private void ResizeBox()
{
Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f);
text.RectTransform.RelativeSize = textBlockScale;
box.RectTransform.MinSize = new Point(Rect.Height);
box.RectTransform.Resize(box.RectTransform.MinSize);
text.SetTextPos();
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
base.Update(deltaTime);
if (GUI.MouseOn == this && Enabled)
{
State = Selected ?
ComponentState.HoverSelected :
ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonHeld())
{
State = ComponentState.Selected;
}
if (PlayerInput.PrimaryMouseButtonClicked())
{
if (radioButtonGroup == null)
{
Selected = !Selected;
}
else if (!selected)
{
Selected = true;
}
}
}
else if (selected)
{
State = ComponentState.Selected;
}
else
{
State = ComponentState.None;
}
}
}
}
@@ -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,190 @@
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 CrewArea
{
get; private set;
}
public static Rectangle ChatBoxArea
{
get; private set;
}
public static Rectangle ObjectiveAnchor
{
get; private set;
}
public static Rectangle InventoryAreaLower
{
get; private set;
}
/*public static Rectangle HealthBarAreaRight
{
get; private set;
}*/
public static Rectangle HealthBarArea
{
get; private set;
}
public static Rectangle BottomRightInfoArea
{
get; private set;
}
public static Rectangle AfflictionAreaLeft
{
get; private set;
}
public static Rectangle HealthWindowAreaLeft
{
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();
CharacterInfo.Init();
}
}
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)(11 * GUI.Scale);
if (inventoryTopY == 0) { inventoryTopY = GameMain.GraphicsHeight - 30; }
//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 infoAreaWidth = (int)(142 * GUI.Scale);
int infoAreaHeight = (int)(98 * GUI.Scale);
int portraitSize = (int)(infoAreaHeight * 0.95f);
BottomRightInfoArea = new Rectangle(GameMain.GraphicsWidth - Padding * 2 - infoAreaWidth, GameMain.GraphicsHeight - Padding * 2 - infoAreaHeight, infoAreaWidth, infoAreaHeight);
PortraitArea = new Rectangle(GameMain.GraphicsWidth - portraitSize, BottomRightInfoArea.Bottom - portraitSize + Padding / 2, portraitSize, portraitSize);
//horizontal slices at the corners of the screen for health bar and affliction icons
int afflictionAreaHeight = (int)(50 * GUI.Scale);
int healthBarWidth = BottomRightInfoArea.Width + CharacterInventory.SlotSize.X + CharacterInventory.Spacing * 2 + CharacterInventory.HideButtonWidth;
int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.X - (healthBarWidth - BottomRightInfoArea.Width) + (int)(2 * GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + (int)(10 * GUI.Scale), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
//HealthBarAreaRight = new Rectangle(Padding, GameMain.GraphicsHeight - healthBarHeight - Padding, healthBarWidth, healthBarHeight);
/*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 + healthBarHeight + Padding, healthBarWidth, afflictionAreaHeight);
int messageAreaWidth = GameMain.GraphicsWidth / 3;
MessageAreaTop = new Rectangle((GameMain.GraphicsWidth - messageAreaWidth) / 2, ButtonAreaTop.Bottom, messageAreaWidth, ButtonAreaTop.Height);
bool isFourByThree = GUI.IsFourByThree();
int chatBoxWidth = !isFourByThree ? (int)(475 * GUI.Scale) : (int)(375 * GUI.Scale);
int chatBoxHeight = (int)Math.Max(GameMain.GraphicsHeight * 0.25f, 150);
ChatBoxArea = new Rectangle(Padding, GameMain.GraphicsHeight - Padding - chatBoxHeight, chatBoxWidth, chatBoxHeight);
int objectiveAnchorWidth = (int)(250 * GUI.Scale);
int objectiveAnchorOffsetY = (int)(150 * GUI.Scale);
ObjectiveAnchor = new Rectangle(Padding, ChatBoxArea.Y - objectiveAnchorOffsetY, objectiveAnchorWidth, 0);
CrewArea = new Rectangle(Padding, Padding, (int)Math.Max(400 * GUI.Scale, 220), ObjectiveAnchor.Top - Padding * 2);
InventoryAreaLower = new Rectangle(Padding, inventoryTopY, GameMain.GraphicsWidth - Padding * 2, GameMain.GraphicsHeight - inventoryTopY);
int healthWindowWidth = (int)(GameMain.GraphicsWidth * 0.5f);
int healthWindowHeight = (int)(GameMain.GraphicsWidth * 0.5f * 0.65f);
int healthWindowX = GameMain.GraphicsWidth / 2 - healthWindowWidth / 2;
int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2;
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
}
public static void Draw(SpriteBatch spriteBatch)
{
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f);
GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUI.Style.Orange * 0.5f);
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f);
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f);
}
}
public static class HUD
{
public static bool CloseHUD(Rectangle rect)
{
// Always close when hitting escape
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { return true; }
//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.PrimaryMouseButtonDown() || PlayerInput.SecondaryMouseButtonClicked();
return input && !rect.Contains(PlayerInput.MousePosition);
}
}
}
@@ -0,0 +1,387 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Media;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
class LoadingScreen
{
private readonly Texture2D defaultBackgroundTexture, overlay;
private readonly SpriteSheet decorativeGraph, decorativeMap;
private Texture2D currentBackgroundTexture;
private Sprite noiseSprite;
private string randText = "";
private Sprite languageSelectionCursor;
private ScalableFont languageSelectionFont, languageSelectionFontCJK;
private Video currSplashScreen;
private DateTime videoStartTime;
public struct PendingSplashScreen
{
public string Filename;
public float Gain;
public PendingSplashScreen(string filename, float gain)
{
Filename = filename;
Gain = gain;
}
}
private Queue<PendingSplashScreen> pendingSplashScreens = new Queue<PendingSplashScreen>();
/// <summary>
/// Triplet.first = filepath, Triplet.second = resolution, Triplet.third = audio gain
/// </summary>
public Queue<PendingSplashScreen> PendingSplashScreens
{
get
{
lock (loadMutex)
{
return pendingSplashScreens;
}
}
set
{
lock (loadMutex)
{
pendingSplashScreens = value;
}
}
}
public bool PlayingSplashScreen
{
get
{
lock (loadMutex)
{
return currSplashScreen != null || pendingSplashScreens.Count > 0;
}
}
}
private string selectedTip;
private readonly object loadMutex = new object();
private float? loadState;
public float? LoadState
{
get
{
lock (loadMutex)
{
return loadState;
}
}
set
{
lock (loadMutex)
{
loadState = value;
DrawLoadingText = true;
}
}
}
public bool DrawLoadingText
{
get;
set;
}
public bool WaitForLanguageSelection
{
get;
set;
}
public LoadingScreen(GraphicsDevice graphics)
{
defaultBackgroundTexture = TextureLoader.FromFile("Content/Map/LocationPortraits/AlienRuins.png");
decorativeMap = new SpriteSheet("Content/Map/MapHUD.png", 6, 5, Vector2.Zero, sourceRect: new Rectangle(0, 0, 2048, 640));
decorativeGraph = new SpriteSheet("Content/Map/MapHUD.png", 4, 10, Vector2.Zero, sourceRect: new Rectangle(1025, 1259, 1024, 732));
overlay = TextureLoader.FromFile("Content/UI/LoadingScreenOverlay.png");
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
DrawLoadingText = true;
selectedTip = TextManager.Get("LoadingScreenTip", true);
}
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
{
if (GameMain.Config.EnableSplashScreen)
{
try
{
DrawSplashScreen(spriteBatch, graphics);
if (currSplashScreen != null || PendingSplashScreens.Count > 0) { return; }
}
catch (Exception e)
{
DebugConsole.ThrowError("Playing splash screen video failed", e);
GameMain.Config.EnableSplashScreen = false;
}
}
var titleStyle = GUI.Style?.GetComponentStyle("TitleText");
Sprite titleSprite = null;
if (!WaitForLanguageSelection && titleStyle != null && titleStyle.Sprites.ContainsKey(GUIComponent.ComponentState.None))
{
titleSprite = titleStyle.Sprites[GUIComponent.ComponentState.None].First()?.Sprite;
}
drawn = true;
currentBackgroundTexture ??= defaultBackgroundTexture;
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
float scale = (GameMain.GraphicsWidth / (float)currentBackgroundTexture.Width) * 1.2f;
float paddingX = currentBackgroundTexture.Width * scale - GameMain.GraphicsWidth;
float paddingY = currentBackgroundTexture.Height * scale - GameMain.GraphicsHeight;
double noiseT = (Timing.TotalTime * 0.02f);
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
spriteBatch.Draw(currentBackgroundTexture,
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, new Vector2(currentBackgroundTexture.Width / 2, currentBackgroundTexture.Height / 2),
scale, SpriteEffects.None, 0.0f);
spriteBatch.Draw(overlay, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), null, Color.White, 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)),
color: Color.White * noiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale);
titleSprite?.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth * 0.05f, GameMain.GraphicsHeight * 0.125f),
Color.White, origin: new Vector2(0.0f, titleSprite.SourceRect.Height / 2.0f),
scale: GameMain.GraphicsHeight / 2000.0f);
if (WaitForLanguageSelection)
{
DrawLanguageSelectionPrompt(spriteBatch, graphics);
}
else if (DrawLoadingText)
{
if (TextManager.Initialized)
{
string loadText;
if (LoadState == 100.0f)
{
#if DEBUG
if (GameMain.Config.AutomaticQuickStartEnabled && GameMain.FirstLoad)
{
loadText = "QUICKSTARTING ...";
}
else
{
#endif
loadText = TextManager.Get("PressAnyKey");
#if DEBUG
}
#endif
}
else
{
loadText = TextManager.Get("Loading");
if (LoadState != null)
{
loadText += " " + (int)LoadState + " %";
}
}
if (GUI.LargeFont != null)
{
GUI.LargeFont.DrawString(spriteBatch, loadText.ToUpper(),
new Vector2(GameMain.GraphicsWidth / 2.0f - GUI.LargeFont.MeasureString(loadText.ToUpper()).X / 2.0f, GameMain.GraphicsHeight * 0.75f),
Color.White);
}
}
if (GUI.Font != null && selectedTip != null)
{
string wrappedTip = ToolBox.WrapText(selectedTip, GameMain.GraphicsWidth * 0.5f, GUI.Font);
string[] lines = wrappedTip.Split('\n');
float lineHeight = GUI.Font.MeasureString(selectedTip).Y;
for (int i = 0; i < lines.Length; i++)
{
GUI.Font.DrawString(spriteBatch, lines[i],
new Vector2((int)(GameMain.GraphicsWidth / 2.0f - GUI.Font.MeasureString(lines[i]).X / 2.0f), (int)(GameMain.GraphicsHeight * 0.8f + i * lineHeight)), Color.White);
}
}
}
spriteBatch.End();
spriteBatch.Begin(blendState: BlendState.Additive);
Vector2 decorativeScale = new Vector2(GameMain.GraphicsHeight / 1080.0f);
float noiseVal = (float)PerlinNoise.CalculatePerlin(Timing.TotalTime * 0.25f, Timing.TotalTime * 0.5f, 0);
decorativeGraph.Draw(spriteBatch, (int)(decorativeGraph.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.001f, GameMain.GraphicsHeight * 0.24f),
Color.White, Vector2.Zero, 0.0f, decorativeScale, SpriteEffects.FlipVertically);
decorativeMap.Draw(spriteBatch, (int)(decorativeMap.FrameCount * noiseVal),
new Vector2(GameMain.GraphicsWidth * 0.99f, GameMain.GraphicsHeight * 0.66f),
Color.White, decorativeMap.FrameSize.ToVector2(), 0.0f, decorativeScale);
if (noiseVal < 0.2f)
{
//SCP-CB reference
randText = (new string[] { "NIL", "black white gray", "Sometimes we would have had time to scream", "e8m106]af", "NO" }).GetRandom();
}
else if (noiseVal < 0.3f)
{
randText = ToolBox.RandomSeed(9);
}
else if (noiseVal < 0.5f)
{
randText =
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0') + " " +
Rand.Int(100).ToString().PadLeft(2, '0');
}
GUI.LargeFont?.DrawString(spriteBatch, randText,
new Vector2(GameMain.GraphicsWidth - decorativeMap.FrameSize.X * decorativeScale.X * 0.8f, GameMain.GraphicsHeight * 0.57f),
Color.White * (1.0f - noiseVal));
spriteBatch.End();
}
private void DrawLanguageSelectionPrompt(SpriteBatch spriteBatch, GraphicsDevice graphicsDevice)
{
if (languageSelectionFont == null)
{
languageSelectionFont = new ScalableFont("Content/Fonts/NotoSans/NotoSans-Bold.ttf",
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice);
}
if (languageSelectionFontCJK == null)
{
languageSelectionFontCJK = new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
(uint)(30 * (GameMain.GraphicsHeight / 1080.0f)), graphicsDevice, dynamicLoading: true);
}
if (languageSelectionCursor == null)
{
languageSelectionCursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
}
Vector2 textPos = new Vector2(GameMain.GraphicsWidth / 2, GameMain.GraphicsHeight * 0.3f);
Vector2 textSpacing = new Vector2(0.0f, (GameMain.GraphicsHeight * 0.5f) / TextManager.AvailableLanguages.Count());
foreach (string language in TextManager.AvailableLanguages)
{
string localizedLanguageName = TextManager.GetTranslatedLanguageName(language);
var font = TextManager.IsCJK(localizedLanguageName) ? languageSelectionFontCJK : languageSelectionFont;
Vector2 textSize = font.MeasureString(localizedLanguageName);
bool hover =
Math.Abs(PlayerInput.MousePosition.X - textPos.X) < textSize.X / 2 &&
Math.Abs(PlayerInput.MousePosition.Y - textPos.Y) < textSpacing.Y / 2;
font.DrawString(spriteBatch, localizedLanguageName, textPos - textSize / 2,
hover ? Color.White : Color.White * 0.6f);
if (hover && PlayerInput.PrimaryMouseButtonClicked())
{
GameMain.Config.Language = language;
//reload tip in the selected language
selectedTip = TextManager.Get("LoadingScreenTip", true);
GameMain.Config.SetDefaultBindings(legacy: false);
GameMain.Config.CheckBindings(useDefaults: true);
WaitForLanguageSelection = false;
languageSelectionFont?.Dispose(); languageSelectionFont = null;
languageSelectionFontCJK?.Dispose(); languageSelectionFontCJK = null;
break;
}
textPos += textSpacing;
}
languageSelectionCursor.Draw(spriteBatch, PlayerInput.LatestMousePosition, scale: 0.5f);
}
private void DrawSplashScreen(SpriteBatch spriteBatch, GraphicsDevice graphics)
{
if (currSplashScreen == null && PendingSplashScreens.Count == 0) { return; }
if (currSplashScreen == null)
{
var newSplashScreen = PendingSplashScreens.Dequeue();
string fileName = newSplashScreen.Filename;
try
{
currSplashScreen = Video.Load(graphics, GameMain.SoundManager, fileName);
currSplashScreen.AudioGain = newSplashScreen.Gain;
videoStartTime = DateTime.Now;
}
catch (Exception e)
{
GameMain.Config.EnableSplashScreen = false;
DebugConsole.ThrowError("Playing the splash screen \"" + fileName + "\" failed.", e);
PendingSplashScreens.Clear();
currSplashScreen = null;
}
}
if (currSplashScreen.IsPlaying)
{
spriteBatch.Begin();
spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown()))
{
currSplashScreen.Dispose(); currSplashScreen = null;
}
}
else if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 1500))
{
currSplashScreen.Dispose(); currSplashScreen = null;
}
}
bool drawn;
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
{
drawn = false;
LoadState = null;
selectedTip = TextManager.Get("LoadingScreenTip", true);
currentBackgroundTexture = LocationType.List.GetRandom()?.GetPortrait(Rand.Int(int.MaxValue))?.Texture;
while (!drawn)
{
yield return CoroutineStatus.Running;
}
CoroutineManager.StartCoroutine(loader);
yield return CoroutineStatus.Running;
while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
{
yield return CoroutineStatus.Running;
}
LoadState = 100.0f;
yield return CoroutineStatus.Success;
}
}
}
@@ -0,0 +1,57 @@
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 GUIComponent Parent { get; private set; }
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, 1f), GUI.Canvas) { MinSize = new Point(340, GameMain.GraphicsHeight) };
rectT.SetPosition(Anchor.TopRight);
Parent = new GUIFrame(rectT, null, Color);
EditorBox = new GUIListBox(new RectTransform(Vector2.One * 0.98f, rectT, Anchor.Center), color: Color.Black, style: null)
{
Spacing = 10,
AutoHideScrollBar = true,
KeepSpaceForScrollBar = true
};
return EditorBox;
}
public void Clear()
{
EditorBox.ClearChildren();
}
public ParamsEditor(RectTransform rectT = null)
{
EditorBox = CreateEditorBox();
}
public static Color Color = new Color(20, 20, 20, 255);
}
}
@@ -0,0 +1,851 @@
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 enum ScaleBasis
{
Normal,
BothWidth, BothHeight,
Smallest, Largest
}
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);
Parent.ChildrenChanged?.Invoke(this);
}
ParentChanged?.Invoke(parent);
}
}
private readonly 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();
}
}
private ScaleBasis _scaleBasis;
public ScaleBasis ScaleBasis
{
get { return _scaleBasis; }
set
{
_scaleBasis = value;
RecalculateAbsoluteSize();
}
}
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, ScaleBasis scaleBasis = ScaleBasis.Normal)
{
Init(parent, anchor, pivot);
_scaleBasis = scaleBasis;
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 scale with the parent.
/// This can be changed by setting IsFixedSize to true.
/// </summary>
public RectTransform(Point absoluteSize, RectTransform parent = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, ScaleBasis scaleBasis = ScaleBasis.Normal, bool isFixedSize = false)
{
Init(parent, anchor, pivot);
_scaleBasis = scaleBasis;
this.nonScaledSize = absoluteSize;
RecalculateScale();
RecalculateRelativeSize();
if (scaleBasis != ScaleBasis.Normal)
{
RecalculateAbsoluteSize();
}
RecalculateAnchorPoint();
RecalculatePivotOffset();
IsFixedSize = isFixedSize;
parent?.ChildrenChanged?.Invoke(this);
}
public static RectTransform Load(XElement element, RectTransform parent, Anchor defaultAnchor = Anchor.TopLeft)
{
Enum.TryParse(element.GetAttributeString("anchor", defaultAnchor.ToString()), out Anchor anchor);
Enum.TryParse(element.GetAttributeString("pivot", anchor.ToString()), out Pivot pivot);
Point? minSize = null, maxSize = null;
ScaleBasis scaleBasis = ScaleBasis.Normal;
if (element.Attribute("minsize") != null)
{
minSize = element.GetAttributePoint("minsize", Point.Zero);
}
if (element.Attribute("maxsize") != null)
{
maxSize = element.GetAttributePoint("maxsize", new Point(1000, 1000));
}
string sb = element.GetAttributeString("scalebasis", null);
if (sb != null)
{
Enum.TryParse(sb, ignoreCase: true, out scaleBasis);
}
RectTransform rectTransform;
if (element.Attribute("absolutesize") != null)
{
rectTransform = new RectTransform(element.GetAttributePoint("absolutesize", new Point(1000, 1000)), parent, anchor, pivot, scaleBasis)
{
minSize = minSize,
maxSize = maxSize
};
}
else
{
rectTransform = new RectTransform(element.GetAttributeVector2("relativesize", Vector2.One), parent, anchor, pivot, minSize, maxSize, scaleBasis);
}
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()
{
Point size = NonScaledParentRect.Size;
switch (ScaleBasis)
{
case ScaleBasis.BothWidth:
size.Y = size.X;
break;
case ScaleBasis.BothHeight:
size.X = size.Y;
break;
case ScaleBasis.Smallest:
if (size.X < size.Y)
{
size.Y = size.X;
}
else
{
size.X = size.Y;
}
break;
case ScaleBasis.Largest:
if (size.X > size.Y)
{
size.Y = size.X;
}
else
{
size.X = size.Y;
}
break;
}
size = size.Multiply(RelativeSize);
nonScaledSize = size.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)
{
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.Clamp(MinSize, MaxSize);
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.Concat(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, false, 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);
}
}
public void MatchPivotToAnchor() => MatchPivotToAnchor(Anchor);
private Point? animTargetPos;
public Point AnimTargetPos
{
get { return animTargetPos ?? AbsoluteOffset; }
}
public void MoveOverTime(Point targetPos, float duration)
{
animTargetPos = targetPos;
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration));
}
public void ScaleOverTime(Point targetSize, float duration)
{
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
}
private IEnumerable<object> DoMoveAnimation(Point targetPos, float duration)
{
Vector2 startPos = AbsoluteOffset.ToVector2();
float t = 0.0f;
while (t < duration && duration > 0.0f)
{
t += CoroutineManager.DeltaTime;
AbsoluteOffset = Vector2.SmoothStep(startPos, targetPos.ToVector2(), t / duration).ToPoint();
yield return CoroutineStatus.Running;
}
AbsoluteOffset = targetPos;
animTargetPos = null;
yield return CoroutineStatus.Success;
}
private IEnumerable<object> DoScaleAnimation(Point targetSize, float duration)
{
Vector2 startSize = NonScaledSize.ToVector2();
float t = 0.0f;
while (t < duration && duration > 0.0f)
{
t += CoroutineManager.DeltaTime;
NonScaledSize = Vector2.SmoothStep(startSize, targetSize.ToVector2(), t / duration).ToPoint();
yield return CoroutineStatus.Running;
}
NonScaledSize = targetSize;
yield return CoroutineStatus.Success;
}
#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,327 @@
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)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
_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(GetTexture(spriteBatch), point1, null, color, angle, Vector2.Zero, scale, SpriteEffects.None, 0);
}
/// <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, null, color, 0.0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0);
}
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,173 @@
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;
}
/// <summary>
/// How much the borders of a sliced sprite are allowed to scale
/// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions
/// </summary>
private readonly float minBorderScale = 0.1f, maxBorderScale = 10.0f;
public bool CrossFadeIn { get; private set; } = true;
public bool CrossFadeOut { get; private set; } = true;
public TransitionMode TransitionMode { get; private set; }
public UISprite(XElement element)
{
Sprite = new Sprite(element);
MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false);
Tile = element.GetAttributeBool("tile", true);
CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn);
CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut);
string transitionMode = element.GetAttributeString("transition", string.Empty);
if (Enum.TryParse(transitionMode, ignoreCase: true, out TransitionMode transition))
{
TransitionMode = transition;
}
Vector4 sliceVec = element.GetAttributeVector4("slice", Vector4.Zero);
if (sliceVec != Vector4.Zero)
{
minBorderScale = element.GetAttributeFloat("minborderscale", 0.1f);
maxBorderScale = element.GetAttributeFloat("minborderscale", 10.0f);
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);
}
}
/// <summary>
/// Get the scale of the sliced sprite's borders when it's draw inside an area of a specific size
/// </summary>
public float GetSliceBorderScale(Point drawSize)
{
if (!Slice) { return 1.0f; }
Vector2 scale = new Vector2(
MathHelper.Clamp((float)drawSize.X / (Slices[0].Height + Slices[6].Height), 0, 1),
MathHelper.Clamp((float)drawSize.Y / (Slices[0].Width + Slices[2].Width), 0, 1));
return MathHelper.Clamp(Math.Min(Math.Min(scale.X, scale.Y), GUI.SlicedSpriteScale), minBorderScale, maxBorderScale);
}
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);
float scale = GetSliceBorderScale(rect.Size);
int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale);
int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale);
for (int x = 0; x < 3; x++)
{
int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale);
if (width <= 0) { continue; }
for (int y = 0; y < 3; y++)
{
int height = (int)(y == 1 ? centerHeight : Slices[x + y * 3].Height * scale);
if (height <= 0) { continue; }
spriteBatch.Draw(Sprite.Texture,
new Rectangle((int)pos.X, (int)pos.Y, width, 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,290 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
using Barotrauma.Media;
using System.IO;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
class VideoPlayer
{
public bool IsPlaying;
private Video currentVideo;
private string filePath;
private GUIFrame background, videoFrame, textFrame;
private GUITextBlock title, textContent, objectiveTitle, objectiveText;
private GUICustomComponent videoView;
private GUIButton okButton;
private Color backgroundColor = new Color(0f, 0f, 0f, 0.8f);
private Action callbackOnStop;
private Point scaledVideoResolution;
private readonly int borderSize = 20;
private readonly Point buttonSize = new Point(120, 30);
private readonly int titleHeight = 30;
private readonly int objectiveFrameHeight = 60;
private readonly int textHeight = 25;
private bool useTextOnRightSide = false;
public class TextSettings
{
public string Text;
public int Width;
public TextSettings(XElement element)
{
Text = TextManager.GetFormatted(element.GetAttributeString("text", string.Empty), true);
Width = element.GetAttributeInt("width", 450);
}
}
public class VideoSettings
{
public string File;
public VideoSettings(XElement element)
{
File = element.GetAttributeString("file", string.Empty);
}
}
public VideoPlayer() // GUI elements with size set to Point.Zero are resized based on content
{
int screenWidth = (int)(GameMain.GraphicsWidth * 0.55f);
scaledVideoResolution = new Point(screenWidth, (int)(screenWidth / 16f * 9f));
int width = scaledVideoResolution.X;
int height = scaledVideoResolution.Y;
background = new GUIFrame(new RectTransform(Point.Zero, GUI.Canvas, Anchor.Center), style: null, color: backgroundColor);
videoFrame = new GUIFrame(new RectTransform(Point.Zero, background.RectTransform, Anchor.Center, Pivot.Center), style: "InnerFrame");
if (useTextOnRightSide)
{
textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), "TextFrame");
}
else
{
textFrame = new GUIFrame(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), "TextFrame");
}
videoView = new GUICustomComponent(new RectTransform(Point.Zero, videoFrame.RectTransform, Anchor.Center), (spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); });
title = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.LargeFont, textColor: new Color(253, 174, 0), textAlignment: Alignment.Left);
textContent = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft), string.Empty, font: GUI.Font, textAlignment: Alignment.TopLeft);
objectiveTitle = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight, textColor: Color.White);
objectiveTitle.Text = TextManager.Get("Tutorial.NewObjective");
objectiveText = new GUITextBlock(new RectTransform(Point.Zero, textFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter), string.Empty, font: GUI.SubHeadingFont, textColor: new Color(4, 180, 108), textAlignment: Alignment.CenterRight);
objectiveTitle.Visible = objectiveText.Visible = false;
}
public void Play()
{
IsPlaying = true;
}
public void Stop()
{
IsPlaying = false;
if (currentVideo == null) return;
currentVideo.Dispose();
currentVideo = null;
}
private bool DisposeVideo(GUIButton button, object userData)
{
Stop();
callbackOnStop?.Invoke();
return true;
}
public void Update()
{
if (currentVideo == null) return;
if (currentVideo.IsPlaying) return;
currentVideo.Play();
}
public void AddToGUIUpdateList(bool ignoreChildren = false, int order = 0)
{
if (!IsPlaying) return;
background.AddToGUIUpdateList(ignoreChildren, order);
}
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, string contentId, bool startPlayback, string objective = "", Action callback = null)
{
callbackOnStop = callback;
filePath = contentPath + videoSettings.File;
if (!File.Exists(filePath))
{
DebugConsole.ThrowError("No video found at: " + filePath);
DisposeVideo(null, null);
return;
}
if (currentVideo != null)
{
currentVideo.Dispose();
currentVideo = null;
}
currentVideo = CreateVideo();
title.Text = textSettings != null ? TextManager.Get(contentId) : string.Empty;
textContent.Text = textSettings != null ? textSettings.Text : string.Empty;
objectiveText.Text = objective;
AdjustFrames(videoSettings, textSettings);
if (startPlayback) Play();
}
private void AdjustFrames(VideoSettings videoSettings, TextSettings textSettings)
{
int screenWidth = (int)(GameMain.GraphicsWidth * 0.55f);
scaledVideoResolution = new Point(screenWidth, (int)(screenWidth / 16f * 9f));
background.RectTransform.NonScaledSize = Point.Zero;
videoFrame.RectTransform.NonScaledSize = Point.Zero;
videoView.RectTransform.NonScaledSize = Point.Zero;
title.RectTransform.NonScaledSize = Point.Zero;
textFrame.RectTransform.NonScaledSize = Point.Zero;
textContent.RectTransform.NonScaledSize = Point.Zero;
objectiveText.RectTransform.NonScaledSize = Point.Zero;
title.TextScale = textContent.TextScale = objectiveText.TextScale = objectiveTitle.TextScale = GUI.Scale;
int scaledBorderSize = (int)(borderSize * GUI.Scale);
int scaledTextWidth = 0;
if (textSettings != null) scaledTextWidth = useTextOnRightSide ? (int)(textSettings.Width * GUI.Scale) : scaledVideoResolution.X / 2;
int scaledTitleHeight = (int)(titleHeight * GUI.Scale);
int scaledTextHeight = (int)(textHeight * GUI.Scale);
int scaledObjectiveFrameHeight = (int)(objectiveFrameHeight * GUI.Scale);
Point scaledButtonSize = new Point((int)(buttonSize.X * GUI.Scale), (int)(buttonSize.Y * GUI.Scale));
background.RectTransform.NonScaledSize = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
videoFrame.RectTransform.NonScaledSize = scaledVideoResolution + new Point(scaledBorderSize, scaledBorderSize);
videoView.RectTransform.NonScaledSize = scaledVideoResolution;
title.RectTransform.NonScaledSize = new Point(scaledTextWidth, scaledTitleHeight);
title.RectTransform.AbsoluteOffset = new Point((int)(5 * GUI.Scale), (int)(10 * GUI.Scale));
if (textSettings != null && !string.IsNullOrEmpty(textSettings.Text))
{
textSettings.Text = ToolBox.WrapText(textSettings.Text, scaledTextWidth, GUI.Font);
int wrappedHeight = textSettings.Text.Split('\n').Length * scaledTextHeight;
textFrame.RectTransform.NonScaledSize = new Point(scaledTextWidth + scaledBorderSize, wrappedHeight + scaledBorderSize + scaledButtonSize.Y + scaledTitleHeight);
if (useTextOnRightSide)
{
textFrame.RectTransform.AbsoluteOffset = new Point(scaledVideoResolution.X + scaledBorderSize * 2, 0);
}
else
{
textFrame.RectTransform.AbsoluteOffset = new Point(0, scaledVideoResolution.Y + scaledBorderSize * 2);
}
textContent.RectTransform.NonScaledSize = new Point(scaledTextWidth, wrappedHeight);
textContent.RectTransform.AbsoluteOffset = new Point(0, scaledBorderSize + scaledTitleHeight);
}
if (!string.IsNullOrEmpty(objectiveText.Text))
{
int scaledXOffset = (int)(-10 * GUI.Scale);
objectiveTitle.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + (int)(scaledTextHeight * 1.95f));
objectiveText.RectTransform.AbsoluteOffset = new Point(scaledXOffset, textContent.RectTransform.Rect.Height + objectiveTitle.Rect.Height + (int)(scaledTextHeight * 2.25f));
textFrame.RectTransform.NonScaledSize += new Point(0, scaledObjectiveFrameHeight);
objectiveText.RectTransform.NonScaledSize = new Point(textFrame.Rect.Width, scaledTextHeight);
objectiveTitle.Visible = objectiveText.Visible = true;
}
else
{
textFrame.RectTransform.NonScaledSize += new Point(0, scaledBorderSize);
objectiveTitle.Visible = objectiveText.Visible = false;
}
if (okButton != null)
{
textFrame.RemoveChild(okButton);
okButton = null;
}
if (textSettings != null)
{
if (useTextOnRightSide)
{
int totalFrameWidth = videoFrame.Rect.Width + textFrame.Rect.Width + scaledBorderSize * 2;
int xOffset = videoFrame.Rect.Width / 2 + scaledBorderSize - (videoFrame.Rect.Width / 2 - textFrame.Rect.Width / 2);
videoFrame.RectTransform.AbsoluteOffset = new Point(-xOffset, (int)(50 * GUI.Scale));
}
else
{
int totalFrameHeight = videoFrame.Rect.Height + textFrame.Rect.Height + scaledBorderSize * 2;
int yOffset = videoFrame.Rect.Height / 2 + scaledBorderSize - (videoFrame.Rect.Height / 2 - textFrame.Rect.Height / 2);
videoFrame.RectTransform.AbsoluteOffset = new Point(0, -yOffset);
}
okButton = new GUIButton(new RectTransform(scaledButtonSize, textFrame.RectTransform, Anchor.BottomRight, Pivot.BottomRight) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("OK"))
{
OnClicked = DisposeVideo
};
}
else
{
videoFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(100 * GUI.Scale));
okButton = new GUIButton(new RectTransform(scaledButtonSize, videoFrame.RectTransform, Anchor.TopLeft, Pivot.TopLeft) { AbsoluteOffset = new Point(scaledBorderSize, scaledBorderSize) }, TextManager.Get("Back"))
{
OnClicked = DisposeVideo
};
}
}
private Video CreateVideo()
{
Video video = null;
try
{
video = Video.Load(GameMain.Instance.GraphicsDevice, GameMain.SoundManager, filePath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading video content " + filePath + "!", e);
}
return video;
}
private void DrawVideo(SpriteBatch spriteBatch, Rectangle rect)
{
if (!IsPlaying) return;
spriteBatch.Draw(currentVideo.GetTexture(), rect, Color.White);
}
public void Remove()
{
if (currentVideo != null)
{
currentVideo.Dispose();
currentVideo = null;
}
}
}
}
@@ -0,0 +1,192 @@
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 = GUI.Style.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 bool RequireMouseOn = true;
public Action refresh;
public object data;
public bool IsSelected => enabled && selectedWidgets.Contains(this);
public bool IsControlled => IsSelected && PlayerInput.PrimaryMouseButtonHeld();
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 || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
{
Hovered?.Invoke();
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
{
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.PrimaryMouseButtonDown())
{
MouseDown?.Invoke();
}
if (PlayerInput.PrimaryMouseButtonHeld())
{
MouseHeld?.Invoke(deltaTime);
}
if (PlayerInput.PrimaryMouseButtonClicked())
{
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);
}
}
}