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